mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-26 05:50:39 +00:00
Fix continent zoom compounding
- Build independent continent generators before applying zoom. - Share continent construction across runtime contexts. - Cover repeated construction and shared-generator isolation.
This commit is contained in:
7 files changed
+314
-5
No files matched your search
@@ -5,6 +5,7 @@ import art.arcane.iris.generation.stream.GenerationStreams;
|
||||
import art.arcane.iris.configuration.IrisSettings;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.iris.generation.image.IrisImageMapRuntime;
|
||||
import art.arcane.iris.generation.terrain.ContinentGenerator;
|
||||
import art.arcane.iris.generation.terrain.InferredType;
|
||||
import art.arcane.iris.generation.biome.IrisBiome;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
@@ -258,9 +259,7 @@ public final class DimensionTerrainContext implements DataProvider {
|
||||
inferredStreams.put(InferredType.SHORE, shoreBiomeStream);
|
||||
|
||||
ProceduralStream<InferredType> bridgeStream = focusBiome == null
|
||||
? dimension.getContinentalStyle()
|
||||
.create(rng.nextParallelRNG(234234565), dimensionData)
|
||||
.bake().scale(1D / dimension.getContinentZoom()).bake().stream()
|
||||
? ContinentGenerator.create(dimension, rng, dimensionData).stream()
|
||||
.convert(value -> value >= dimension.getLandChance()
|
||||
? InferredType.SEA
|
||||
: InferredType.LAND)
|
||||
|
||||
@@ -52,6 +52,7 @@ import art.arcane.iris.generation.decoration.IrisDecorationPart;
|
||||
import art.arcane.iris.generation.decoration.IrisDecorator;
|
||||
import art.arcane.iris.generation.noise.IrisGenerator;
|
||||
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||
import art.arcane.iris.generation.terrain.ContinentGenerator;
|
||||
import art.arcane.iris.generation.terrain.IrisDimension;
|
||||
import art.arcane.iris.generation.context.IrisContext;
|
||||
import art.arcane.iris.generation.context.ChunkContext;
|
||||
@@ -390,8 +391,7 @@ public class IrisComplex implements DataProvider {
|
||||
.convertAware2D(ProceduralStream::get), "shoreBiomeStream", engine, cacheSize);
|
||||
inferredStreams.put(InferredType.SHORE, shoreBiomeStream);
|
||||
bridgeStream = focusedBiomes != null ? focusedBiomes.convert(IrisBiome::getInferredType) :
|
||||
GenerationStreams.cache2D(engine.getDimension().getContinentalStyle().create(rng.nextParallelRNG(234234565), getData())
|
||||
.bake().scale(1D / engine.getDimension().getContinentZoom()).bake().stream()
|
||||
GenerationStreams.cache2D(ContinentGenerator.create(engine.getDimension(), rng, getData()).stream()
|
||||
.convert((v) -> v >= engine.getDimension().getLandChance() ? InferredType.SEA : InferredType.LAND), "bridgeStream", engine, cacheSize);
|
||||
ProceduralStream<IrisBiome> proceduralBaseBiomeStream = focusedBiomes != null ? focusedBiomes :
|
||||
GenerationStreams.cache2D(bridgeStream.convertAware2D((t, x, z) -> inferredStreams.get(t).get(x, z))
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package art.arcane.iris.generation.terrain;
|
||||
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.noise.CNG;
|
||||
|
||||
public final class ContinentGenerator {
|
||||
private static final int CONTINENT_SEED_SALT = 234234565;
|
||||
|
||||
private ContinentGenerator() {
|
||||
}
|
||||
|
||||
public static CNG create(IrisDimension dimension, RNG parentSeedSource, IrisData packData) {
|
||||
return scaleByContinentZoom(createContinentalStyleGenerator(dimension, parentSeedSource, packData), dimension);
|
||||
}
|
||||
|
||||
private static CNG createContinentalStyleGenerator(IrisDimension dimension, RNG parentSeedSource, IrisData packData) {
|
||||
return dimension.getContinentalStyle().createNoCache(deriveContinentSeed(parentSeedSource), packData);
|
||||
}
|
||||
|
||||
static RNG deriveContinentSeed(RNG parentSeedSource) {
|
||||
return parentSeedSource.nextParallelRNG(CONTINENT_SEED_SALT);
|
||||
}
|
||||
|
||||
private static CNG scaleByContinentZoom(CNG generator, IrisDimension dimension) {
|
||||
return generator.bake().scale(1D / dimension.getContinentZoom()).bake();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package art.arcane.iris.generation.terrain;
|
||||
|
||||
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
|
||||
import art.arcane.iris.generation.noise.NoiseStyle;
|
||||
import art.arcane.iris.generation.runtime.Engine;
|
||||
import art.arcane.iris.pack.loading.IrisData;
|
||||
import art.arcane.volmlib.util.math.RNG;
|
||||
import art.arcane.volmlib.util.noise.CNG;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertArrayEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class ContinentGeneratorTest {
|
||||
private static final long WORLD_SEED = 7L;
|
||||
private static final double POWER_OF_TWO_CONTINENT_ZOOM = 4D;
|
||||
private static final double SQUARED_CONTINENT_ZOOM = POWER_OF_TWO_CONTINENT_ZOOM * POWER_OF_TWO_CONTINENT_ZOOM;
|
||||
private static final double OVERWORLD_STYLE_ZOOM = 6D;
|
||||
private static final double[][] SAMPLE_BLOCKS = {{1000D, -2000D}, {12.5D, -7.25D}, {-517D, 333D}};
|
||||
private static final double[][] BLOCKS_SHRUNK_BY_CONTINENT_ZOOM = shrinkByContinentZoom(SAMPLE_BLOCKS);
|
||||
private static final double[] CONTINENT_MAP_OF_EXISTING_WORLDS =
|
||||
{0.21889892332042843D, 0.6984934456740721D, 0.2708759584352327D};
|
||||
|
||||
private final IrisData packData = createPackDataThatBelongsToOneRunningEngine();
|
||||
private final IrisDimension baseZoomDimension = createDimensionWithContinentZoom(POWER_OF_TWO_CONTINENT_ZOOM);
|
||||
private final IrisDimension squaredZoomDimension = createDimensionWithContinentZoom(SQUARED_CONTINENT_ZOOM);
|
||||
private final IrisDimension overworldStyleDimension = createDimensionWithOverworldContinentalStyle();
|
||||
|
||||
@Test
|
||||
public void doesTheContinentMapAtWorldStartMatchExistingWorlds() {
|
||||
assertArrayEquals(CONTINENT_MAP_OF_EXISTING_WORLDS, sampleContinentMapAtWorldStart(baseZoomDimension), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesSquaredContinentZoomStretchTheContinentMapByTheBaseZoom() {
|
||||
assertArrayEquals(sampleBaseContinentMapStretchedByBaseZoom(),
|
||||
sampleContinentMapAtWorldStart(squaredZoomDimension), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesRebuildingTheSameWorldZoomItsContinentMapOnlyOnce() {
|
||||
assertArrayEquals(CONTINENT_MAP_OF_EXISTING_WORLDS, sampleContinentMapAfterRebuild(baseZoomDimension), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesRebuildingTheSameWorldTwiceZoomItsContinentMapOnlyOnce() {
|
||||
assertArrayEquals(CONTINENT_MAP_OF_EXISTING_WORLDS, sampleContinentMapAfterSecondRebuild(baseZoomDimension), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesRebuildingAWorldWithTheOverworldContinentalStyleZoomItsContinentMapOnlyOnce() {
|
||||
assertArrayEquals(sampleOverworldContinentMapAtWorldStart(), sampleContinentMapAfterRebuild(overworldStyleDimension), 0D);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesBuildingTheContinentMapLeaveTheSharedStyleGeneratorUnscaled() {
|
||||
assertArrayEquals(sampleUntouchedSharedStyleGenerator(), sampleSharedStyleGeneratorAfterContinentMapBuilt(baseZoomDimension), 0D);
|
||||
}
|
||||
|
||||
private double[] sampleContinentMapAtWorldStart(IrisDimension dimension) {
|
||||
return sampleContinentMap(dimension, SAMPLE_BLOCKS);
|
||||
}
|
||||
|
||||
private double[] sampleBaseContinentMapStretchedByBaseZoom() {
|
||||
return sampleContinentMap(baseZoomDimension, BLOCKS_SHRUNK_BY_CONTINENT_ZOOM);
|
||||
}
|
||||
|
||||
private double[] sampleContinentMapAfterRebuild(IrisDimension dimension) {
|
||||
sampleContinentMapAtWorldStart(dimension);
|
||||
return sampleContinentMapAtWorldStart(dimension);
|
||||
}
|
||||
|
||||
private double[] sampleContinentMapAfterSecondRebuild(IrisDimension dimension) {
|
||||
sampleContinentMapAtWorldStart(dimension);
|
||||
return sampleContinentMapAfterRebuild(dimension);
|
||||
}
|
||||
|
||||
private double[] sampleOverworldContinentMapAtWorldStart() {
|
||||
return sampleContinentMapAtWorldStart(createDimensionWithOverworldContinentalStyle());
|
||||
}
|
||||
|
||||
private double[] sampleSharedStyleGeneratorAfterContinentMapBuilt(IrisDimension dimension) {
|
||||
sampleContinentMapAtWorldStart(dimension);
|
||||
return sampleSharedStyleGenerator(dimension.getContinentalStyle());
|
||||
}
|
||||
|
||||
private double[] sampleUntouchedSharedStyleGenerator() {
|
||||
return sampleSharedStyleGenerator(createSimplexStyle());
|
||||
}
|
||||
|
||||
private double[] sampleSharedStyleGenerator(IrisGeneratorStyle style) {
|
||||
return sampleAtBlocks(style.create(ContinentGenerator.deriveContinentSeed(new RNG(WORLD_SEED)), packData), SAMPLE_BLOCKS);
|
||||
}
|
||||
|
||||
private double[] sampleContinentMap(IrisDimension dimension, double[][] blocks) {
|
||||
return sampleAtBlocks(ContinentGenerator.create(dimension, new RNG(WORLD_SEED), packData), blocks);
|
||||
}
|
||||
|
||||
private static double[] sampleAtBlocks(CNG continentGenerator, double[][] blocks) {
|
||||
return Arrays.stream(blocks).mapToDouble(block -> sampleAt(continentGenerator, block)).toArray();
|
||||
}
|
||||
|
||||
private static double sampleAt(CNG continentGenerator, double[] block) {
|
||||
return continentGenerator.noise(block[0], block[1]);
|
||||
}
|
||||
|
||||
private static double[][] shrinkByContinentZoom(double[][] blocks) {
|
||||
return Arrays.stream(blocks).map(block -> shrinkByContinentZoom(block)).toArray(double[][]::new);
|
||||
}
|
||||
|
||||
private static double[] shrinkByContinentZoom(double[] block) {
|
||||
return new double[]{block[0] / POWER_OF_TWO_CONTINENT_ZOOM, block[1] / POWER_OF_TWO_CONTINENT_ZOOM};
|
||||
}
|
||||
|
||||
private static IrisDimension createDimensionWithContinentZoom(double zoom) {
|
||||
return new IrisDimension().setContinentalStyle(createSimplexStyle()).setContinentZoom(zoom);
|
||||
}
|
||||
|
||||
private static IrisGeneratorStyle createSimplexStyle() {
|
||||
return new IrisGeneratorStyle(NoiseStyle.SIMPLEX);
|
||||
}
|
||||
|
||||
private static IrisDimension createDimensionWithOverworldContinentalStyle() {
|
||||
return createDimensionWithContinentZoom(POWER_OF_TWO_CONTINENT_ZOOM).setContinentalStyle(createOverworldContinentalStyle());
|
||||
}
|
||||
|
||||
private static IrisGeneratorStyle createOverworldContinentalStyle() {
|
||||
return new IrisGeneratorStyle(NoiseStyle.NOWHERE_CELLULAR).setZoom(OVERWORLD_STYLE_ZOOM).setFracture(createSmokeFracture());
|
||||
}
|
||||
|
||||
private static IrisGeneratorStyle createSmokeFracture() {
|
||||
return new IrisGeneratorStyle(NoiseStyle.FRACTAL_SMOKE).setFracture(new IrisGeneratorStyle(NoiseStyle.STATIC));
|
||||
}
|
||||
|
||||
private static IrisData createPackDataThatBelongsToOneRunningEngine() {
|
||||
IrisData newPackData = mock(IrisData.class);
|
||||
bindToNewRunningEngine(newPackData);
|
||||
return newPackData;
|
||||
}
|
||||
|
||||
private static void bindToNewRunningEngine(IrisData packData) {
|
||||
when(packData.getEngine()).thenReturn(mock(Engine.class));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
package art.arcane.iris.testsupport;
|
||||
|
||||
import org.junit.AssumptionViolatedException;
|
||||
import org.junit.Test;
|
||||
import org.junit.function.ThrowingRunnable;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import static org.junit.Assert.assertSame;
|
||||
import static org.junit.Assert.assertThrows;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class ExpectedToFailUntilFixedRuleTest {
|
||||
private static final String KNOWN_BUG = "known bug";
|
||||
private static final String BUG_STILL_PRESENT = "bug still present";
|
||||
private static final String REMOVAL_REMINDER = "remove @ExpectedToFailUntilFixed: " + KNOWN_BUG;
|
||||
private static final Statement PASSING = new Statement() {
|
||||
@Override
|
||||
public void evaluate() {
|
||||
}
|
||||
};
|
||||
|
||||
private final ExpectedToFailUntilFixedRule rule = new ExpectedToFailUntilFixedRule();
|
||||
private final AssertionError bugStillPresent = new AssertionError(BUG_STILL_PRESENT);
|
||||
private final IllegalStateException crash = new IllegalStateException(BUG_STILL_PRESENT);
|
||||
|
||||
@Test
|
||||
public void doesAMarkedTestThatStillFailsGetReportedAsSkippedWithItsFailure() {
|
||||
assertSame(bugStillPresent, assertThrows(AssumptionViolatedException.class, runMarked(failingWith(bugStillPresent))).getCause());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesAMarkedTestThatPassesFailWithARemovalReminder() {
|
||||
assertTrue(messageOfAssertionFailure(runMarked(PASSING)).contains(REMOVAL_REMINDER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesACrashInAMarkedTestStillFail() {
|
||||
assertSame(crash, assertThrows(IllegalStateException.class, runMarked(failingWith(crash))));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesAnUnmarkedFailingTestStillFail() {
|
||||
assertSame(bugStillPresent, assertThrows(AssertionError.class, runUnmarked(failingWith(bugStillPresent))));
|
||||
}
|
||||
|
||||
@ExpectedToFailUntilFixed(KNOWN_BUG)
|
||||
private void markedTestTemplate() {
|
||||
}
|
||||
|
||||
private ThrowingRunnable runMarked(Statement test) {
|
||||
return () -> rule.apply(test, describe(markerOfTemplate())).evaluate();
|
||||
}
|
||||
|
||||
private ThrowingRunnable runUnmarked(Statement test) {
|
||||
return () -> rule.apply(test, describe()).evaluate();
|
||||
}
|
||||
|
||||
private ExpectedToFailUntilFixed markerOfTemplate() throws NoSuchMethodException {
|
||||
return getClass().getDeclaredMethod("markedTestTemplate").getAnnotation(ExpectedToFailUntilFixed.class);
|
||||
}
|
||||
|
||||
private static String messageOfAssertionFailure(ThrowingRunnable test) {
|
||||
return assertThrows(AssertionError.class, test).getMessage();
|
||||
}
|
||||
|
||||
private static Description describe(Annotation... annotations) {
|
||||
return Description.createTestDescription(ExpectedToFailUntilFixedRuleTest.class, "example", annotations);
|
||||
}
|
||||
|
||||
private static Statement failingWith(Throwable failure) {
|
||||
return new Statement() {
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
throw failure;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package art.arcane.iris.testsupport;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface ExpectedToFailUntilFixed {
|
||||
String value();
|
||||
}
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
package art.arcane.iris.testsupport;
|
||||
|
||||
import org.junit.AssumptionViolatedException;
|
||||
import org.junit.rules.TestRule;
|
||||
import org.junit.runner.Description;
|
||||
import org.junit.runners.model.Statement;
|
||||
|
||||
public final class ExpectedToFailUntilFixedRule implements TestRule {
|
||||
@Override
|
||||
public Statement apply(Statement test, Description description) {
|
||||
ExpectedToFailUntilFixed marker = markerOf(description);
|
||||
return marker == null ? test : new ExpectedFailure(test, marker.value());
|
||||
}
|
||||
|
||||
private static ExpectedToFailUntilFixed markerOf(Description description) {
|
||||
return description.getAnnotation(ExpectedToFailUntilFixed.class);
|
||||
}
|
||||
|
||||
private static final class ExpectedFailure extends Statement {
|
||||
private final Statement test;
|
||||
private final String reason;
|
||||
|
||||
private ExpectedFailure(Statement test, String reason) {
|
||||
this.test = test;
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void evaluate() throws Throwable {
|
||||
try {
|
||||
test.evaluate();
|
||||
} catch (AssertionError stillFailing) {
|
||||
throw reportAsSkipped(stillFailing);
|
||||
}
|
||||
throw new AssertionError("Passes now, remove @ExpectedToFailUntilFixed: " + reason);
|
||||
}
|
||||
|
||||
private AssumptionViolatedException reportAsSkipped(AssertionError stillFailing) {
|
||||
return new AssumptionViolatedException("Still failing until fixed: " + reason, stillFailing);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user