diff --git a/core/src/main/java/art/arcane/iris/generation/biome/IrisBiome.java b/core/src/main/java/art/arcane/iris/generation/biome/IrisBiome.java index b1b448b9a..9a5e69c05 100644 --- a/core/src/main/java/art/arcane/iris/generation/biome/IrisBiome.java +++ b/core/src/main/java/art/arcane/iris/generation/biome/IrisBiome.java @@ -93,6 +93,7 @@ import java.util.Objects; @EqualsAndHashCode(callSuper = false) public class IrisBiome extends IrisRegistrant implements Rarity { private static final int BIOME_GENERATOR_CACHE_SIZE = 8; + private static final int CHILDREN_SEED_SALT = 2137; private final transient AtomicCache> genCache = new AtomicCache<>(); private final transient AtomicCache> genCacheMax = new AtomicCache<>(); @@ -511,7 +512,15 @@ public class IrisBiome extends IrisRegistrant implements Rarity { } public CNG getChildrenGenerator(RNG random, int sig, double scale) { - return childrenCell.aquire(() -> getChildStyle().create(random.nextParallelRNG(sig * 2137), getLoader()).bake().scale(scale).bake()); + return childrenCell.aquire(() -> createChildrenGenerator(random, sig, scale)); + } + + private CNG createChildrenGenerator(RNG random, int sig, double scale) { + return getChildStyle().createScaledGenerator(deriveChildStyleSeed(random, sig), getLoader(), scale); + } + + static RNG deriveChildStyleSeed(RNG random, int signature) { + return random.nextParallelRNG(signature * CHILDREN_SEED_SALT); } public KList generateLayers(IrisDimension dim, double wx, double wz, RNG random, int maxDepth, int height, IrisData rdata, IrisComplex complex) { diff --git a/core/src/main/java/art/arcane/iris/generation/decoration/IrisDecorator.java b/core/src/main/java/art/arcane/iris/generation/decoration/IrisDecorator.java index 3cfdfac5a..a0e8fc629 100644 --- a/core/src/main/java/art/arcane/iris/generation/decoration/IrisDecorator.java +++ b/core/src/main/java/art/arcane/iris/generation/decoration/IrisDecorator.java @@ -151,15 +151,19 @@ public class IrisDecorator { public CNG getVarianceGenerator(RNG rng, IrisData data) { CNG cached = varianceGenerator.getIfPresent(); + return cached != null ? cached : buildVarianceGeneratorOnce(rng, data); + } - if (cached != null) { - return cached; - } + private CNG buildVarianceGeneratorOnce(RNG rng, IrisData data) { + return varianceGenerator.aquire(() -> createVarianceGenerator(rng, data)); + } - return varianceGenerator.aquire(() -> - variance.create( - rng.nextParallelRNG(getBlockData(data).size()), data) - .scale(1D / variance.getZoom())); + private CNG createVarianceGenerator(RNG rng, IrisData data) { + return variance.createNoCache(deriveVarianceSeed(rng, data), data).scale(1D / variance.getZoom()); + } + + RNG deriveVarianceSeed(RNG rng, IrisData data) { + return rng.nextParallelRNG(getBlockData(data).size()); } public KList add(String b) { diff --git a/core/src/main/java/art/arcane/iris/generation/noise/IrisGeneratorStyle.java b/core/src/main/java/art/arcane/iris/generation/noise/IrisGeneratorStyle.java index ac3ad0426..7ae67270a 100644 --- a/core/src/main/java/art/arcane/iris/generation/noise/IrisGeneratorStyle.java +++ b/core/src/main/java/art/arcane/iris/generation/noise/IrisGeneratorStyle.java @@ -102,6 +102,10 @@ public class IrisGeneratorStyle { return createNoCache(rng, data, 1, 0, false, resolveEngine(data)); } + public CNG createScaledGenerator(RNG rng, IrisData data, double scaleFactor) { + return createNoCache(rng, data).bake().scale(scaleFactor).bake(); + } + public CNG createForLayer(RNG rng, IrisData data, int octaveMultiplier) { return createNoCache(rng, data, Math.clamp(octaveMultiplier, 1, 16), 0, false, resolveEngine(data)); } diff --git a/core/src/main/java/art/arcane/iris/generation/terrain/ContinentGenerator.java b/core/src/main/java/art/arcane/iris/generation/terrain/ContinentGenerator.java index 4423d81b9..0d95de5ea 100644 --- a/core/src/main/java/art/arcane/iris/generation/terrain/ContinentGenerator.java +++ b/core/src/main/java/art/arcane/iris/generation/terrain/ContinentGenerator.java @@ -11,18 +11,18 @@ public final class ContinentGenerator { } public static CNG create(IrisDimension dimension, RNG parentSeedSource, IrisData packData) { - return scaleByContinentZoom(createContinentalStyleGenerator(dimension, parentSeedSource, packData), dimension); + return createScaledContinentalStyleGenerator(dimension, deriveContinentSeed(parentSeedSource), packData); } - private static CNG createContinentalStyleGenerator(IrisDimension dimension, RNG parentSeedSource, IrisData packData) { - return dimension.getContinentalStyle().createNoCache(deriveContinentSeed(parentSeedSource), packData); + private static CNG createScaledContinentalStyleGenerator(IrisDimension dimension, RNG continentSeed, IrisData packData) { + return dimension.getContinentalStyle().createScaledGenerator(continentSeed, packData, inverseContinentZoom(dimension)); } 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(); + private static double inverseContinentZoom(IrisDimension dimension) { + return 1D / dimension.getContinentZoom(); } } diff --git a/core/src/main/java/art/arcane/iris/generation/terrain/IrisDimensionCarvingEntry.java b/core/src/main/java/art/arcane/iris/generation/terrain/IrisDimensionCarvingEntry.java index 6c7ff23e5..6fa49dbfc 100644 --- a/core/src/main/java/art/arcane/iris/generation/terrain/IrisDimensionCarvingEntry.java +++ b/core/src/main/java/art/arcane/iris/generation/terrain/IrisDimensionCarvingEntry.java @@ -31,6 +31,8 @@ import java.util.concurrent.ConcurrentHashMap; @Description("Dimension-level cave biome override with absolute world Y bounds.") @Data public class IrisDimensionCarvingEntry { + private static final int CHILDREN_SEED_SALT = 2137; + private static final double MINIMUM_CHILD_SHRINK_FACTOR = 0.0001D; private final transient AtomicCache realBiome = new AtomicCache<>(true); private final transient Map childGenerators = new ConcurrentHashMap<>(); @@ -81,13 +83,27 @@ public class IrisDimensionCarvingEntry { } public CNG getChildrenGenerator(long seed, IrisData data) { + return childGenerators.computeIfAbsent(deriveChildrenGeneratorSeed(seed), key -> createChildrenGenerator(key, data)); + } + + private CNG createChildrenGenerator(long childrenGeneratorSeed, IrisData data) { + return getChildStyle().createScaledGenerator(deriveChildStyleSeed(childrenGeneratorSeed), data, childShrinkFactor()); + } + + long deriveChildrenGeneratorSeed(long worldSeed) { + return worldSeed ^ (hashOfId() << 32) ^ CHILDREN_SEED_SALT; + } + + static RNG deriveChildStyleSeed(long childrenGeneratorSeed) { + return new RNG(childrenGeneratorSeed).nextParallelRNG(CHILDREN_SEED_SALT); + } + + private double childShrinkFactor() { + return Math.max(MINIMUM_CHILD_SHRINK_FACTOR, getChildShrinkFactor()); + } + + private long hashOfId() { String entryId = getId(); - long idHash = entryId == null ? 0L : entryId.trim().hashCode(); - long generatorSeed = seed ^ (idHash << 32) ^ 2137L; - return childGenerators.computeIfAbsent(generatorSeed, key -> { - double scale = Math.max(0.0001D, getChildShrinkFactor()); - RNG random = new RNG(key); - return getChildStyle().create(random.nextParallelRNG(2137), data).bake().scale(scale).bake(); - }); + return entryId == null ? 0L : entryId.trim().hashCode(); } } diff --git a/core/src/test/java/art/arcane/iris/generation/biome/IrisBiomeChildrenGeneratorTest.java b/core/src/test/java/art/arcane/iris/generation/biome/IrisBiomeChildrenGeneratorTest.java new file mode 100644 index 000000000..f556b2278 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/generation/biome/IrisBiomeChildrenGeneratorTest.java @@ -0,0 +1,57 @@ +package art.arcane.iris.generation.biome; + +import art.arcane.iris.generation.noise.IrisGeneratorStyle; +import art.arcane.iris.generation.noise.NoiseStyle; +import art.arcane.iris.pack.loading.IrisData; +import art.arcane.iris.testsupport.RunningEnginePackData; +import art.arcane.volmlib.util.math.RNG; +import org.junit.Test; + +import static art.arcane.iris.testsupport.GeneratorSampling.SAMPLE_BLOCKS; +import static art.arcane.iris.testsupport.GeneratorSampling.sampleSharedGenerator; +import static org.junit.Assert.assertArrayEquals; + +public class IrisBiomeChildrenGeneratorTest { + private static final long WORLD_SEED = 7L; + private static final int CHILDREN_SIGNATURE = 123; + private static final double CHILD_SHRINK_FACTOR = 4D; + + private final IrisData packData = RunningEnginePackData.create(); + private final IrisBiome biome = createBiomeWithSimplexChildStyle(packData); + + @Test + public void doesBuildingTheChildrenGeneratorLeaveTheSharedChildStyleGeneratorUnscaled() { + assertArrayEquals(sampleUntouchedSharedChildStyleGenerator(), sampleSharedChildStyleGeneratorAfterChildrenGeneratorBuilt(), 0D); + } + + private double[] sampleSharedChildStyleGeneratorAfterChildrenGeneratorBuilt() { + buildChildrenGenerator(); + return sampleSharedChildStyleGenerator(biome.getChildStyle()); + } + + private void buildChildrenGenerator() { + biome.getChildrenGenerator(new RNG(WORLD_SEED), CHILDREN_SIGNATURE, CHILD_SHRINK_FACTOR); + } + + private double[] sampleUntouchedSharedChildStyleGenerator() { + return sampleSharedChildStyleGenerator(createSimplexStyle()); + } + + private double[] sampleSharedChildStyleGenerator(IrisGeneratorStyle childStyle) { + return sampleSharedGenerator(childStyle, deriveSharedChildStyleSeed(), packData); + } + + private static RNG deriveSharedChildStyleSeed() { + return IrisBiome.deriveChildStyleSeed(new RNG(WORLD_SEED), CHILDREN_SIGNATURE); + } + + private static IrisBiome createBiomeWithSimplexChildStyle(IrisData packData) { + IrisBiome biome = new IrisBiome().setChildStyle(createSimplexStyle()); + biome.setLoader(packData); + return biome; + } + + private static IrisGeneratorStyle createSimplexStyle() { + return new IrisGeneratorStyle(NoiseStyle.SIMPLEX); + } +} diff --git a/core/src/test/java/art/arcane/iris/generation/decoration/IrisDecoratorVarianceGeneratorTest.java b/core/src/test/java/art/arcane/iris/generation/decoration/IrisDecoratorVarianceGeneratorTest.java new file mode 100644 index 000000000..a5cb2be26 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/generation/decoration/IrisDecoratorVarianceGeneratorTest.java @@ -0,0 +1,48 @@ +package art.arcane.iris.generation.decoration; + +import art.arcane.iris.generation.block.IrisBlockData; +import art.arcane.iris.generation.noise.IrisGeneratorStyle; +import art.arcane.iris.generation.noise.NoiseStyle; +import art.arcane.iris.pack.loading.IrisData; +import art.arcane.iris.testsupport.RunningEnginePackData; +import art.arcane.volmlib.util.collection.KList; +import art.arcane.volmlib.util.math.RNG; +import org.junit.Test; + +import static art.arcane.iris.testsupport.GeneratorSampling.SAMPLE_BLOCKS; +import static art.arcane.iris.testsupport.GeneratorSampling.sampleSharedGenerator; +import static org.junit.Assert.assertArrayEquals; + +public class IrisDecoratorVarianceGeneratorTest { + private static final long WORLD_SEED = 7L; + private static final double VARIANCE_ZOOM = 4D; + + private final IrisData packData = RunningEnginePackData.create(); + private final IrisDecorator decorator = createDecoratorWithZoomedSimplexVariance(); + + @Test + public void doesBuildingTheVarianceGeneratorLeaveTheSharedVarianceStyleGeneratorUnscaled() { + assertArrayEquals(sampleUntouchedSharedVarianceStyleGenerator(), sampleSharedVarianceStyleGeneratorAfterVarianceBuilt(), 0D); + } + + private double[] sampleSharedVarianceStyleGeneratorAfterVarianceBuilt() { + decorator.getVarianceGenerator(new RNG(WORLD_SEED), packData); + return sampleSharedVarianceStyleGenerator(decorator.getVariance()); + } + + private double[] sampleUntouchedSharedVarianceStyleGenerator() { + return sampleSharedVarianceStyleGenerator(createZoomedSimplexStyle()); + } + + private double[] sampleSharedVarianceStyleGenerator(IrisGeneratorStyle varianceStyle) { + return sampleSharedGenerator(varianceStyle, decorator.deriveVarianceSeed(new RNG(WORLD_SEED), packData), packData); + } + + private static IrisDecorator createDecoratorWithZoomedSimplexVariance() { + return new IrisDecorator().setPalette(new KList()).setVariance(createZoomedSimplexStyle()); + } + + private static IrisGeneratorStyle createZoomedSimplexStyle() { + return new IrisGeneratorStyle(NoiseStyle.SIMPLEX).setZoom(VARIANCE_ZOOM); + } +} diff --git a/core/src/test/java/art/arcane/iris/generation/terrain/ContinentGeneratorTest.java b/core/src/test/java/art/arcane/iris/generation/terrain/ContinentGeneratorTest.java index 5b98f12c4..de74d930d 100644 --- a/core/src/test/java/art/arcane/iris/generation/terrain/ContinentGeneratorTest.java +++ b/core/src/test/java/art/arcane/iris/generation/terrain/ContinentGeneratorTest.java @@ -2,29 +2,28 @@ 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.iris.testsupport.RunningEnginePackData; import art.arcane.volmlib.util.math.RNG; -import art.arcane.volmlib.util.noise.CNG; import org.junit.Test; import java.util.Arrays; +import static art.arcane.iris.testsupport.GeneratorSampling.SAMPLE_BLOCKS; +import static art.arcane.iris.testsupport.GeneratorSampling.sampleAtBlocks; +import static art.arcane.iris.testsupport.GeneratorSampling.sampleSharedGenerator; 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 IrisData packData = RunningEnginePackData.create(); private final IrisDimension baseZoomDimension = createDimensionWithContinentZoom(POWER_OF_TWO_CONTINENT_ZOOM); private final IrisDimension squaredZoomDimension = createDimensionWithContinentZoom(SQUARED_CONTINENT_ZOOM); private final IrisDimension overworldStyleDimension = createDimensionWithOverworldContinentalStyle(); @@ -92,21 +91,13 @@ public class ContinentGeneratorTest { } private double[] sampleSharedStyleGenerator(IrisGeneratorStyle style) { - return sampleAtBlocks(style.create(ContinentGenerator.deriveContinentSeed(new RNG(WORLD_SEED)), packData), SAMPLE_BLOCKS); + return sampleSharedGenerator(style, ContinentGenerator.deriveContinentSeed(new RNG(WORLD_SEED)), packData); } 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); } @@ -134,14 +125,4 @@ public class ContinentGeneratorTest { 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)); - } } diff --git a/core/src/test/java/art/arcane/iris/generation/terrain/IrisDimensionCarvingEntryTest.java b/core/src/test/java/art/arcane/iris/generation/terrain/IrisDimensionCarvingEntryTest.java new file mode 100644 index 000000000..0cd2b7977 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/generation/terrain/IrisDimensionCarvingEntryTest.java @@ -0,0 +1,55 @@ +package art.arcane.iris.generation.terrain; + +import art.arcane.iris.generation.noise.IrisGeneratorStyle; +import art.arcane.iris.generation.noise.NoiseStyle; +import art.arcane.iris.pack.loading.IrisData; +import art.arcane.iris.testsupport.RunningEnginePackData; +import art.arcane.volmlib.util.math.RNG; +import org.junit.Test; + +import static art.arcane.iris.testsupport.GeneratorSampling.SAMPLE_BLOCKS; +import static art.arcane.iris.testsupport.GeneratorSampling.sampleSharedGenerator; +import static org.junit.Assert.assertArrayEquals; + +public class IrisDimensionCarvingEntryTest { + private static final long WORLD_SEED = 7L; + private static final String CARVING_ENTRY_ID = "deep-caves"; + private static final double CHILD_SHRINK_FACTOR = 4D; + + private final IrisData packData = RunningEnginePackData.create(); + private final IrisDimensionCarvingEntry carvingEntry = createCarvingEntry(); + + @Test + public void doesBuildingTheChildrenGeneratorLeaveTheSharedChildStyleGeneratorUnscaled() { + assertArrayEquals(sampleUntouchedSharedChildStyleGenerator(), sampleSharedChildStyleGeneratorAfterChildrenGeneratorBuilt(), 0D); + } + + private double[] sampleSharedChildStyleGeneratorAfterChildrenGeneratorBuilt() { + carvingEntry.getChildrenGenerator(WORLD_SEED, packData); + return sampleSharedChildStyleGenerator(carvingEntry.getChildStyle()); + } + + private double[] sampleUntouchedSharedChildStyleGenerator() { + return sampleSharedChildStyleGenerator(createSimplexStyle()); + } + + private double[] sampleSharedChildStyleGenerator(IrisGeneratorStyle childStyle) { + return sampleSharedGenerator(childStyle, deriveSharedChildStyleSeed(), packData); + } + + private RNG deriveSharedChildStyleSeed() { + return IrisDimensionCarvingEntry.deriveChildStyleSeed(carvingEntry.deriveChildrenGeneratorSeed(WORLD_SEED)); + } + + private static IrisDimensionCarvingEntry createCarvingEntry() { + return withShrinkingSimplexChildStyle(new IrisDimensionCarvingEntry().setId(CARVING_ENTRY_ID)); + } + + private static IrisDimensionCarvingEntry withShrinkingSimplexChildStyle(IrisDimensionCarvingEntry carvingEntry) { + return carvingEntry.setChildStyle(createSimplexStyle()).setChildShrinkFactor(CHILD_SHRINK_FACTOR); + } + + private static IrisGeneratorStyle createSimplexStyle() { + return new IrisGeneratorStyle(NoiseStyle.SIMPLEX); + } +} diff --git a/core/src/test/java/art/arcane/iris/testsupport/GeneratorSampling.java b/core/src/test/java/art/arcane/iris/testsupport/GeneratorSampling.java new file mode 100644 index 000000000..79370d729 --- /dev/null +++ b/core/src/test/java/art/arcane/iris/testsupport/GeneratorSampling.java @@ -0,0 +1,27 @@ +package art.arcane.iris.testsupport; + +import art.arcane.iris.generation.noise.IrisGeneratorStyle; +import art.arcane.iris.pack.loading.IrisData; +import art.arcane.volmlib.util.math.RNG; +import art.arcane.volmlib.util.noise.CNG; + +import java.util.Arrays; + +public final class GeneratorSampling { + public static final double[][] SAMPLE_BLOCKS = {{1000D, -2000D}, {12.5D, -7.25D}, {-517D, 333D}}; + + private GeneratorSampling() { + } + + public static double[] sampleSharedGenerator(IrisGeneratorStyle style, RNG seed, IrisData packData) { + return sampleAtBlocks(style.create(seed, packData), SAMPLE_BLOCKS); + } + + public static double[] sampleAtBlocks(CNG generator, double[][] blocks) { + return Arrays.stream(blocks).mapToDouble(block -> sampleAt(generator, block)).toArray(); + } + + private static double sampleAt(CNG generator, double[] block) { + return generator.noise(block[0], block[1]); + } +} diff --git a/core/src/test/java/art/arcane/iris/testsupport/RunningEnginePackData.java b/core/src/test/java/art/arcane/iris/testsupport/RunningEnginePackData.java new file mode 100644 index 000000000..e6bbefd8e --- /dev/null +++ b/core/src/test/java/art/arcane/iris/testsupport/RunningEnginePackData.java @@ -0,0 +1,18 @@ +package art.arcane.iris.testsupport; + +import art.arcane.iris.generation.runtime.Engine; +import art.arcane.iris.pack.loading.IrisData; + +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +public final class RunningEnginePackData { + private RunningEnginePackData() { + } + + public static IrisData create() { + IrisData packData = mock(IrisData.class); + when(packData.getEngine()).thenReturn(mock(Engine.class)); + return packData; + } +}