Unboxed biome selection

- Rarity selection streams pick from precomputed cumulative tables
- Implode and surface biome fixes take primitive coordinates
- Child selection reuses one derived child seed instead of a new RNG per level
- Base biome selection reads bridge and inferred streams directly
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 23:27:01 -04:00
1 parent 1acda886b3
commit 7408d02bbe
6 files changed
+113 -29

No files matched your search

@@ -515,7 +515,10 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
}
public CNG getChildrenGenerator(RNG random, int sig, double scale) {
long seed = deriveChildStyleSeed(random, sig).getSeed();
return getChildrenGenerator(deriveChildStyleSeed(random, sig).getSeed(), scale);
}
public CNG getChildrenGenerator(long seed, double scale) {
IrisData data = getLoader();
Engine engine = data == null ? null : data.getEngine();
CachedChildGenerator recent = recentChildGenerator;
@@ -533,6 +536,10 @@ public class IrisBiome extends IrisRegistrant implements Rarity {
return random.nextParallelRNG(signature * CHILDREN_SEED_SALT);
}
public static long childStyleSeed(RNG random, int signature) {
return deriveChildStyleSeed(random, signature).getSeed();
}
public KList<NativeBlockState> generateLayers(IrisDimension dim, double wx, double wz, RNG random, int maxDepth, int height, IrisData rdata, IrisComplex complex) {
return IrisBiomeLayerGenerator.generateLayers(this, dim, wx, wz, random, maxDepth, height, rdata, complex);
}
@@ -33,6 +33,7 @@ import art.arcane.iris.generation.stream.BakedDoubleStream;
import art.arcane.iris.generation.stream.BakedHeightStream;
import art.arcane.iris.generation.stream.BakedStream;
import art.arcane.iris.generation.stream.GenerationStreams;
import art.arcane.iris.generation.stream.RarityStream;
import art.arcane.iris.generation.stream.CachedDoubleStream2D;
import art.arcane.iris.generation.stream.CachedStream2D;
import art.arcane.iris.generation.stream.ProvisionalSampling;
@@ -132,8 +133,8 @@ import java.nio.file.Path;
import java.util.function.BiFunction;
@Data
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "sparseBaseHeights", "biomeRegistry", "regionRegistry", "blockRegistry", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
@ToString(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "sparseBaseHeights", "biomeRegistry", "regionRegistry", "blockRegistry", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
@EqualsAndHashCode(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "sparseBaseHeights", "biomeRegistry", "regionRegistry", "blockRegistry", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
@ToString(exclude = {"data", "gridBoundsCache", "sharedCornerBounds", "terrainProgram", "bakeCache", "sparseBaseHeights", "biomeRegistry", "regionRegistry", "blockRegistry", "regionSource", "baseBiomeSource", "caveSource", "rockSource", "fluidSource", "childStyleSeed", "inferredByType", "naturalHeightSource", "naturalTrueBiomeSource", "trueBiomeSource", "frozenInterpolators", "frozenGenerators", "inferredBiomeStreams", "hydrologyRuntime", "imageMapRuntime", "terrainEngine", "terrain3D", "hydrologyBanks3D"})
public class IrisComplex implements DataProvider {
private static final NoiseBounds ZERO_NOISE_BOUNDS = new NoiseBounds(0D, 0D);
private static final AtomicLong lastBoundsFailureLog = new AtomicLong(0L);
@@ -182,6 +183,10 @@ public class IrisComplex implements DataProvider {
@Getter(AccessLevel.NONE)
private final transient BakeRegistry<NativeBlockState> blockRegistry = new BakeRegistry<>();
@Getter(AccessLevel.NONE)
private final transient long childStyleSeed;
@Getter(AccessLevel.NONE)
private final transient Map<InferredType, ProceduralStream<IrisBiome>> inferredByType;
@Getter(AccessLevel.NONE)
private final transient ProceduralStream<IrisRegion> regionSource;
@Getter(AccessLevel.NONE)
private final transient ProceduralStream<IrisBiome> baseBiomeSource;
@@ -321,6 +326,7 @@ public class IrisComplex implements DataProvider {
IrisBiome emptyBiome = new IrisBiome().setInferredType(InferredType.CAVE);
UUID focusUUID = UUID.nameUUIDFromBytes("focus".getBytes());
this.rng = new RNG(engine.getSeedManager().getComplex());
childStyleSeed = IrisBiome.childStyleSeed(rng, 123);
this.data = engine.getData();
imageMapRuntime = IrisImageMapRuntime.compile(engine);
double height = engine.getMaxHeight();
@@ -334,7 +340,8 @@ public class IrisComplex implements DataProvider {
boolean buffetActive = biomeBuffet != null && !biomeBuffet.cells().isEmpty();
focusBiome = buffetActive ? null : compatUsable(engine.getFocus());
focusRegion = buffetActive ? null : compatUsable(engine.getFocusRegion());
Map<InferredType, ProceduralStream<IrisBiome>> inferredStreams = new HashMap<>();
Map<InferredType, ProceduralStream<IrisBiome>> inferredStreams = new EnumMap<>(InferredType.class);
inferredByType = inferredStreams;
KList<IrisRegion> preparedRegions = new KList<>();
if (focusBiome != null) {
@@ -442,9 +449,10 @@ public class IrisComplex implements DataProvider {
bridgeStream = focusedBiomes != null ? focusedBiomes.convert(IrisBiome::getInferredType) :
ContinentGenerator.create(engine.getDimension(), rng, getData()).stream()
.convert((v) -> v >= engine.getDimension().getLandChance() ? InferredType.SEA : InferredType.LAND);
ProceduralStream<IrisBiome> proceduralBaseBiomeStream = focusedBiomes != null ? focusedBiomes :
bridgeStream.convertAware2D((t, x, z) -> inferredStreams.get(t).get(x, z))
.convertAware2D(this::implode);
ProceduralStream<IrisBiome> proceduralBaseBiomeStream = focusedBiomes != null ? focusedBiomes
: ProceduralStream.of(this::proceduralBaseBiome,
(x, y, z) -> implode(inferredByType.get(bridgeStream.get(x, y, z)).get(x, z), x, z),
Interpolated.of(value -> 0D, value -> null));
baseBiomeSource = focusedBiomes != null || !imageMapRuntime.has(IrisImageMapApplication.BIOME)
? proceduralBaseBiomeStream : ProceduralStream.of((x, z) -> {
IrisBiome mapped = imageMapRuntime.sampleBiome(x, z);
@@ -1103,6 +1111,10 @@ public class IrisComplex implements DataProvider {
}
}
private IrisBiome proceduralBaseBiome(double x, double z) {
return implode(inferredByType.get(bridgeStream.get(x, z)).get(x, z), x, z);
}
private boolean bakedColumn(BakePlane plane, int blockX, int blockZ) {
ChunkPlanes.Plane values = bakeCache.planeIfPresent(plane, blockX >> 4, blockZ >> 4);
return values != null && values.ready(((blockZ & 15) << 4) | (blockX & 15));
@@ -1367,29 +1379,29 @@ public class IrisComplex implements DataProvider {
IrisBiome emptyBiome
) {
return switch (inferredType) {
case CAVE -> engine.getDimension().getCaveBiomeStyle()
case CAVE -> new RarityStream<>(engine.getDimension().getCaveBiomeStyle()
.create(rng.nextParallelRNG(InferredType.CAVE.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
.zoom(region.getCaveBiomeZoom())
.selectRarity(loadInferredBiomes(region.getCaveBiomes(), InferredType.CAVE))
.zoom(region.getCaveBiomeZoom()),
loadInferredBiomes(region.getCaveBiomes(), InferredType.CAVE))
.onNull(emptyBiome);
case LAND -> engine.getDimension().getLandBiomeStyle()
case LAND -> new RarityStream<>(engine.getDimension().getLandBiomeStyle()
.create(rng.nextParallelRNG(InferredType.LAND.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
.zoom(engine.getDimension().getLandZoom())
.zoom(region.getLandBiomeZoom())
.selectRarity(loadInferredBiomes(region.getLandBiomes(), InferredType.LAND));
case SEA -> engine.getDimension().getSeaBiomeStyle()
.zoom(region.getLandBiomeZoom()),
loadInferredBiomes(region.getLandBiomes(), InferredType.LAND));
case SEA -> new RarityStream<>(engine.getDimension().getSeaBiomeStyle()
.create(rng.nextParallelRNG(InferredType.SEA.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
.zoom(engine.getDimension().getSeaZoom())
.zoom(region.getSeaBiomeZoom())
.selectRarity(loadInferredBiomes(region.getSeaBiomes(), InferredType.SEA));
case SHORE -> engine.getDimension().getShoreBiomeStyle()
.zoom(region.getSeaBiomeZoom()),
loadInferredBiomes(region.getSeaBiomes(), InferredType.SEA));
case SHORE -> new RarityStream<>(engine.getDimension().getShoreBiomeStyle()
.create(rng.nextParallelRNG(InferredType.SHORE.ordinal()), getData()).stream()
.zoom(engine.getDimension().getBiomeZoom())
.zoom(region.getShoreBiomeZoom())
.selectRarity(loadInferredBiomes(region.getShoreBiomes(), InferredType.SHORE));
.zoom(region.getShoreBiomeZoom()),
loadInferredBiomes(region.getShoreBiomes(), InferredType.SHORE));
};
}
@@ -2020,7 +2032,7 @@ public class IrisComplex implements DataProvider {
return biomeKey == null ? sample.parentBiomeKey() : biomeKey;
}
private IrisBiome fixBiomeType(Double height, IrisBiome biome, IrisRegion region, Double x, Double z, double fluidHeight) {
private IrisBiome fixBiomeType(double height, IrisBiome biome, IrisRegion region, double x, double z, double fluidHeight) {
IrisBiome resolved = resolveSurfaceBiome(
height,
biome,
@@ -2633,7 +2645,7 @@ public class IrisComplex implements DataProvider {
return computed;
}
private IrisBiome implode(IrisBiome b, Double x, Double z) {
private IrisBiome implode(IrisBiome b, double x, double z) {
if (b.getChildren().isEmpty()) {
return b;
}
@@ -2641,7 +2653,7 @@ public class IrisComplex implements DataProvider {
return implode(b, x, z, 3);
}
private IrisBiome implode(IrisBiome b, Double x, Double z, int max) {
private IrisBiome implode(IrisBiome b, double x, double z, int max) {
if (max < 0) {
return b;
}
@@ -2650,7 +2662,7 @@ public class IrisComplex implements DataProvider {
return b;
}
CNG childCell = b.getChildrenGenerator(rng, 123, b.getChildShrinkFactor());
CNG childCell = b.getChildrenGenerator(childStyleSeed, b.getChildShrinkFactor());
ChildSelectionPlan childSelectionPlan = resolveChildSelectionPlan(b);
IrisBiome biome = childSelectionPlan.select(childCell, x, z).withInferredType(b.getInferredType());
if (biome == b && hasFixedNoise(b.getChildStyle())) {
@@ -0,0 +1,64 @@
package art.arcane.iris.generation.stream;
import art.arcane.volmlib.util.math.Rarity;
import art.arcane.volmlib.util.stream.BasicStream;
import art.arcane.volmlib.util.stream.ProceduralStream;
import java.util.List;
import java.util.Objects;
public final class RarityStream<T extends Rarity> extends BasicStream<T> {
private final ProceduralStream<?> noise;
private final Object[] options;
private final double[] cumulative;
public RarityStream(ProceduralStream<?> noise, List<T> options) {
super();
this.noise = Objects.requireNonNull(noise, "Rarity noise");
this.options = options.toArray();
this.cumulative = new double[this.options.length];
double buffer = 0;
for (int index = 0; index < this.options.length; index++) {
buffer += 1d / Rarity.get(this.options[index]);
cumulative[index] = buffer;
}
}
@Override
public T get(double x, double z) {
return pick(noise.getDouble(x, z));
}
@Override
public T get(double x, double y, double z) {
return pick(noise.getDouble(x, y, z));
}
@Override
public double toDouble(T t) {
return 0;
}
@Override
public T fromDouble(double d) {
return null;
}
@SuppressWarnings("unchecked")
public T pick(double noiseValue) {
int count = options.length;
if (count == 0) {
return null;
}
if (count == 1) {
return (T) options[0];
}
double threshold = cumulative[count - 1] * noiseValue;
for (int index = 0; index < count; index++) {
if (cumulative[index] >= threshold) {
return (T) options[index];
}
}
return (T) options[count - 1];
}
}
@@ -3,6 +3,7 @@ package art.arcane.iris.generation.terrain;
import art.arcane.iris.generation.biome.IrisBiome;
import art.arcane.iris.generation.image.IrisImageMapRuntime;
import art.arcane.iris.generation.image.IrisImageMapApplication;
import art.arcane.iris.generation.stream.RarityStream;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.studio.generation.BiomeBuffetLayout;
import art.arcane.volmlib.util.collection.KList;
@@ -24,7 +25,7 @@ public final class RegionSelection {
regions.add(region);
}
}
ProceduralStream<IrisRegion> selected = options.style().selectRarity(regions);
ProceduralStream<IrisRegion> selected = new RarityStream<>(options.style(), regions);
if (!options.imageMaps().has(IrisImageMapApplication.REGION)) {
return selected;
}
@@ -83,7 +83,7 @@ public class IrisComplexSelfSelectionTest {
Field field = IrisComplex.class.getDeclaredField("childSelectionPlans");
field.setAccessible(true);
field.set(complex, plans);
implode = IrisComplex.class.getDeclaredMethod("implode", IrisBiome.class, Double.class, Double.class, int.class);
implode = IrisComplex.class.getDeclaredMethod("implode", IrisBiome.class, double.class, double.class, int.class);
implode.setAccessible(true);
}
@@ -106,7 +106,7 @@ public class IrisComplexSelfSelectionTest {
}
@Override
public CNG getChildrenGenerator(RNG rng, int signature, double scale) {
public CNG getChildrenGenerator(long seed, double scale) {
return noise;
}
}
@@ -97,11 +97,11 @@ public class IrisCarveModifierInferenceIsolationTest {
doReturn(0D).when(region).getShoreHeight(anyDouble(), anyDouble());
Method fixBiomeType = IrisComplex.class.getDeclaredMethod(
"fixBiomeType",
Double.class,
double.class,
IrisBiome.class,
IrisRegion.class,
Double.class,
Double.class,
double.class,
double.class,
double.class
);
fixBiomeType.setAccessible(true);