Isolate scaled noise generators

- Isolate biome-child and carving-child noise generators.
- Isolate decorator variance generators.
- Share fresh scaled-generator construction.
- Test shared-generator isolation at each caller.
This commit is contained in:
Brian Fopiano
2026-10-02 07:48:01 -04:00
committed by GitHub
11 changed files with 264 additions and 45 deletions
@@ -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<KMap<String, IrisBiomeGeneratorLink>> genCache = new AtomicCache<>();
private final transient AtomicCache<KMap<String, Integer>> 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<NativeBlockState> generateLayers(IrisDimension dim, double wx, double wz, RNG random, int maxDepth, int height, IrisData rdata, IrisComplex complex) {
@@ -151,15 +151,19 @@ public class IrisDecorator {
public CNG getVarianceGenerator(RNG rng, IrisData data) {
CNG cached = varianceGenerator.getIfPresent();
if (cached != null) {
return cached;
return cached != null ? cached : buildVarianceGeneratorOnce(rng, data);
}
return varianceGenerator.aquire(() ->
variance.create(
rng.nextParallelRNG(getBlockData(data).size()), data)
.scale(1D / variance.getZoom()));
private CNG buildVarianceGeneratorOnce(RNG rng, IrisData data) {
return varianceGenerator.aquire(() -> createVarianceGenerator(rng, data));
}
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<IrisBlockData> add(String b) {
@@ -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));
}
@@ -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();
}
}
@@ -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<IrisBiome> realBiome = new AtomicCache<>(true);
private final transient Map<Long, CNG> 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();
}
}
@@ -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);
}
}
@@ -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<IrisBlockData>()).setVariance(createZoomedSimplexStyle());
}
private static IrisGeneratorStyle createZoomedSimplexStyle() {
return new IrisGeneratorStyle(NoiseStyle.SIMPLEX).setZoom(VARIANCE_ZOOM);
}
}
@@ -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));
}
}
@@ -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);
}
}
@@ -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]);
}
}
@@ -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;
}
}