This commit is contained in:
Brian Neumann-Fopiano
2026-02-18 23:44:23 -05:00
parent 7b9280083f
commit 1f41e195cf
6 changed files with 450 additions and 34 deletions

View File

@@ -27,7 +27,6 @@ import art.arcane.iris.engine.object.*;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.iris.util.project.context.IrisContext;
import art.arcane.iris.util.common.data.DataProvider;
import art.arcane.iris.util.project.interpolation.IrisInterpolation.NoiseKey;
import art.arcane.volmlib.util.math.M;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.iris.util.project.noise.CNG;
@@ -83,6 +82,8 @@ public class IrisComplex implements DataProvider {
private ProceduralStream<BlockData> fluidStream;
private IrisBiome focusBiome;
private IrisRegion focusRegion;
private Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> generatorBounds;
private Set<IrisBiome> generatorBiomes;
public IrisComplex(Engine engine) {
this(engine, false);
@@ -97,6 +98,7 @@ public class IrisComplex implements DataProvider {
double height = engine.getMaxHeight();
fluidHeight = engine.getDimension().getFluidHeight();
generators = new HashMap<>();
generatorBiomes = Collections.newSetFromMap(new IdentityHashMap<>());
focusBiome = engine.getFocus();
focusRegion = engine.getFocusRegion();
Map<InferredType, ProceduralStream<IrisBiome>> inferredStreams = new HashMap<>();
@@ -116,9 +118,20 @@ public class IrisComplex implements DataProvider {
.getAllBiomes(this)
.forEach(this::registerGenerators));
}
generatorBounds = buildGeneratorBounds(engine);
boolean legacy = engine.getDimension().isLegacyRarity();
overlayStream = ProceduralStream.ofDouble((x, z) -> 0.0D).waste("Overlay Stream");
engine.getDimension().getOverlayNoise().forEach(i -> overlayStream = overlayStream.add((x, z) -> i.get(rng, getData(), x, z)));
KList<IrisShapedGeneratorStyle> overlayNoise = engine.getDimension().getOverlayNoise();
overlayStream = overlayNoise.isEmpty()
? ProceduralStream.ofDouble((x, z) -> 0.0D).waste("Overlay Stream")
: ProceduralStream.ofDouble((x, z) -> {
double value = 0D;
for (IrisShapedGeneratorStyle style : overlayNoise) {
value += style.get(rng, getData(), x, z);
}
return value;
}).waste("Overlay Stream");
rockStream = engine.getDimension().getRockPalette().getLayerGenerator(rng.nextParallelRNG(45), data).stream()
.select(engine.getDimension().getRockPalette().getBlockData(data)).waste("Rock Stream");
fluidStream = engine.getDimension().getFluidPalette().getLayerGenerator(rng.nextParallelRNG(78), data).stream()
@@ -306,18 +319,27 @@ public class IrisComplex implements DataProvider {
return 0;
}
HashMap<NoiseKey, IrisBiome> cache = new HashMap<>(64);
CoordinateBiomeCache sampleCache = new CoordinateBiomeCache(64);
IdentityHashMap<IrisBiome, GeneratorBounds> cachedBounds = generatorBounds.get(interpolator);
IdentityHashMap<IrisBiome, GeneratorBounds> localBounds = new IdentityHashMap<>(8);
double hi = interpolator.interpolate(x, z, (xx, zz) -> {
try {
IrisBiome bx = baseBiomeStream.get(xx, zz);
cache.put(new NoiseKey(xx, zz), bx);
double b = 0;
for (IrisGenerator gen : generators) {
b += bx.getGenLinkMax(gen.getLoadKey(), engine);
IrisBiome bx = sampleCache.get(xx, zz);
if (bx == null) {
bx = baseBiomeStream.get(xx, zz);
sampleCache.put(xx, zz, bx);
}
return b;
GeneratorBounds bounds = cachedBounds == null ? null : cachedBounds.get(bx);
if (bounds == null) {
bounds = localBounds.get(bx);
if (bounds == null) {
bounds = computeGeneratorBounds(engine, generators, bx);
localBounds.put(bx, bounds);
}
}
return bounds.max;
} catch (Throwable e) {
Iris.reportError(e);
e.printStackTrace();
@@ -329,18 +351,22 @@ public class IrisComplex implements DataProvider {
double lo = interpolator.interpolate(x, z, (xx, zz) -> {
try {
IrisBiome bx = cache.get(new NoiseKey(xx, zz));
IrisBiome bx = sampleCache.get(xx, zz);
if (bx == null) {
bx = baseBiomeStream.get(xx, zz);
cache.put(new NoiseKey(xx, zz), bx);
}
double b = 0;
for (IrisGenerator gen : generators) {
b += bx.getGenLinkMin(gen.getLoadKey(), engine);
sampleCache.put(xx, zz, bx);
}
return b;
GeneratorBounds bounds = cachedBounds == null ? null : cachedBounds.get(bx);
if (bounds == null) {
bounds = localBounds.get(bx);
if (bounds == null) {
bounds = computeGeneratorBounds(engine, generators, bx);
localBounds.put(bx, bounds);
}
}
return bounds.min;
} catch (Throwable e) {
Iris.reportError(e);
e.printStackTrace();
@@ -362,8 +388,8 @@ public class IrisComplex implements DataProvider {
private double getInterpolatedHeight(Engine engine, double x, double z, long seed) {
double h = 0;
for (IrisInterpolator i : generators.keySet()) {
h += interpolateGenerators(engine, i, generators.get(i), x, z, seed);
for (Map.Entry<IrisInterpolator, Set<IrisGenerator>> entry : generators.entrySet()) {
h += interpolateGenerators(engine, entry.getKey(), entry.getValue(), x, z, seed);
}
return h;
@@ -374,6 +400,7 @@ public class IrisComplex implements DataProvider {
}
private void registerGenerators(IrisBiome biome) {
generatorBiomes.add(biome);
biome.getGenerators().forEach(c -> registerGenerator(c.getCachedGenerator(this)));
}
@@ -381,6 +408,38 @@ public class IrisComplex implements DataProvider {
generators.computeIfAbsent(cachedGenerator.getInterpolator(), (k) -> new HashSet<>()).add(cachedGenerator);
}
private Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> buildGeneratorBounds(Engine engine) {
Map<IrisInterpolator, IdentityHashMap<IrisBiome, GeneratorBounds>> bounds = new HashMap<>();
KList<IrisBiome> allBiomes = new KList<>(generatorBiomes);
if (focusBiome != null && !allBiomes.contains(focusBiome)) {
allBiomes.add(focusBiome);
}
for (Map.Entry<IrisInterpolator, Set<IrisGenerator>> entry : generators.entrySet()) {
IdentityHashMap<IrisBiome, GeneratorBounds> interpolatorBounds = new IdentityHashMap<>(Math.max(allBiomes.size(), 16));
for (IrisBiome biome : allBiomes) {
interpolatorBounds.put(biome, computeGeneratorBounds(engine, entry.getValue(), biome));
}
bounds.put(entry.getKey(), interpolatorBounds);
}
return bounds;
}
private GeneratorBounds computeGeneratorBounds(Engine engine, Set<IrisGenerator> generators, IrisBiome biome) {
double min = 0D;
double max = 0D;
for (IrisGenerator gen : generators) {
String key = gen.getLoadKey();
max += biome.getGenLinkMax(key, engine);
min += biome.getGenLinkMin(key, engine);
}
return new GeneratorBounds(min, max);
}
private IrisBiome implode(IrisBiome b, Double x, Double z) {
if (b.getChildren().isEmpty()) {
return b;
@@ -406,6 +465,66 @@ public class IrisComplex implements DataProvider {
return implode(biome, x, z, max - 1);
}
private static class GeneratorBounds {
private final double min;
private final double max;
private GeneratorBounds(double min, double max) {
this.min = min;
this.max = max;
}
}
private static class CoordinateBiomeCache {
private long[] xBits;
private long[] zBits;
private IrisBiome[] values;
private int size;
private CoordinateBiomeCache(int initialSize) {
xBits = new long[initialSize];
zBits = new long[initialSize];
values = new IrisBiome[initialSize];
size = 0;
}
private IrisBiome get(double x, double z) {
long xb = Double.doubleToLongBits(x);
long zb = Double.doubleToLongBits(z);
for (int i = 0; i < size; i++) {
if (xBits[i] == xb && zBits[i] == zb) {
return values[i];
}
}
return null;
}
private void put(double x, double z, IrisBiome biome) {
if (size >= xBits.length) {
grow();
}
xBits[size] = Double.doubleToLongBits(x);
zBits[size] = Double.doubleToLongBits(z);
values[size] = biome;
size++;
}
private void grow() {
int nextSize = xBits.length << 1;
long[] nx = new long[nextSize];
long[] nz = new long[nextSize];
IrisBiome[] nv = new IrisBiome[nextSize];
System.arraycopy(xBits, 0, nx, 0, size);
System.arraycopy(zBits, 0, nz, 0, size);
System.arraycopy(values, 0, nv, 0, size);
xBits = nx;
zBits = nz;
values = nv;
}
}
public void close() {
}

View File

@@ -32,6 +32,7 @@ import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import java.io.File;
import java.util.Objects;
@Snippet("style")
@@ -91,26 +92,85 @@ public class IrisGeneratorStyle {
}
private int imageMapHash() {
if (imageMap == null) {
return 0;
}
return Objects.hash(imageMap.getImage(),
imageMap.getCoordinateScale(),
imageMap.getInterpolationMethod(),
imageMap.getChannel(),
imageMap.isInverted(),
imageMap.isTiled(),
imageMap.isCentered());
}
private int hash() {
return Objects.hash(expression, imageMap, multiplier, axialFracturing, fracture != null ? fracture.hash() : 0, exponent, cacheSize, zoom, cellularZoom, cellularFrequency, style);
return Objects.hash(expression, imageMapHash(), script, multiplier, axialFracturing, fracture != null ? fracture.hash() : 0, exponent, cacheSize, zoom, cellularZoom, cellularFrequency, style);
}
private String cachePrefix(RNG rng) {
return "style-" + Integer.toUnsignedString(hash())
+ "-seed-" + Long.toUnsignedString(rng.getSeed())
+ "-src-";
}
private String cacheKey(RNG rng, long sourceStamp) {
return cachePrefix(rng) + Long.toUnsignedString(sourceStamp);
}
private long scriptStamp(IrisData data) {
if (getScript() == null) {
return 0L;
}
File scriptFile = data.getScriptLoader().findFile(getScript());
if (scriptFile == null) {
return Integer.toUnsignedLong(getScript().hashCode());
}
return Integer.toUnsignedLong(Objects.hash(getScript(), scriptFile.lastModified(), scriptFile.length()));
}
private void clearStaleCacheEntries(IrisData data, String prefix, String key) {
File cacheFolder = new File(data.getDataFolder(), ".cache");
File[] files = cacheFolder.listFiles((dir, name) -> name.endsWith(".cnm") && name.startsWith(prefix));
if (files == null) {
return;
}
String keepFile = key + ".cnm";
for (File file : files) {
if (keepFile.equals(file.getName())) {
continue;
}
if (!file.delete()) {
Iris.debug("Unable to delete stale noise cache " + file.getName());
}
}
}
public CNG createNoCache(RNG rng, IrisData data, boolean actuallyCached) {
String cacheKey = hash() + "";
CNG cng = null;
long sourceStamp = 0L;
if (getExpression() != null) {
IrisExpression e = data.getExpressionLoader().load(getExpression());
if (e != null) {
cng = new CNG(rng, new ExpressionNoise(rng, e), 1D, 1).bake();
sourceStamp = Integer.toUnsignedLong(Objects.hash(getExpression(),
e.getLoadFile() == null ? 0L : e.getLoadFile().lastModified()));
}
} else if (getImageMap() != null) {
cng = new CNG(rng, new ImageNoise(data, getImageMap()), 1D, 1).bake();
sourceStamp = Integer.toUnsignedLong(imageMapHash());
} else if (getScript() != null) {
Object result = data.getEnvironment().createNoise(getScript(), rng);
if (result == null) Iris.warn("Failed to create noise from script: " + getScript());
if (result instanceof NoiseGenerator generator) {
cng = new CNG(rng, generator, 1D, 1).bake();
sourceStamp = scriptStamp(data);
}
}
@@ -126,7 +186,13 @@ public class IrisGeneratorStyle {
}
if (cellularFrequency > 0) {
return cng.cellularize(rng.nextParallelRNG(884466), cellularFrequency).scale(1D / cellularZoom).bake();
cng = cng.cellularize(rng.nextParallelRNG(884466), cellularFrequency).scale(1D / cellularZoom).bake();
}
if (cacheSize > 0) {
String key = cacheKey(rng, sourceStamp);
clearStaleCacheEntries(data, cachePrefix(rng), key);
cng = cng.cached(cacheSize, key, data.getDataFolder());
}
return cng;

View File

@@ -63,6 +63,14 @@ public class IrisShapedGeneratorStyle {
return generator.create(rng, data).fitDouble(min, max, dim);
}
public double get(RNG rng, IrisData data, double x, double z) {
return generator.create(rng, data).fitDouble(min, max, x, z);
}
public double get(RNG rng, IrisData data, double x, double y, double z) {
return generator.create(rng, data).fitDouble(min, max, x, y, z);
}
public boolean isFlat() {
return min == max || getGenerator().isFlat();
}

View File

@@ -999,8 +999,8 @@ public class IrisInterpolation {
}
public static double getNoise(InterpolationMethod method, int x, int z, double h, NoiseProvider noise) {
HashMap<NoiseKey, Double> cache = new HashMap<>(64);
NoiseProvider n = (x1, z1) -> cache.computeIfAbsent(new NoiseKey(x1 - x, z1 - z), k -> noise.noise(x1, z1));
NoiseSampleCache2D cache = new NoiseSampleCache2D(64);
NoiseProvider n = (x1, z1) -> cache.getOrSample(x1 - x, z1 - z, x1, z1, noise);
if (method.equals(InterpolationMethod.BILINEAR)) {
return getBilinearNoise(x, z, h, n);
@@ -1059,6 +1059,56 @@ public class IrisInterpolation {
return n.noise(x, z);
}
private static class NoiseSampleCache2D {
private long[] xBits;
private long[] zBits;
private double[] values;
private int size;
public NoiseSampleCache2D(int initialCapacity) {
xBits = new long[initialCapacity];
zBits = new long[initialCapacity];
values = new double[initialCapacity];
size = 0;
}
public double getOrSample(double relativeX, double relativeZ, double sampleX, double sampleZ, NoiseProvider provider) {
long rx = Double.doubleToLongBits(relativeX);
long rz = Double.doubleToLongBits(relativeZ);
for (int i = 0; i < size; i++) {
if (xBits[i] == rx && zBits[i] == rz) {
return values[i];
}
}
double value = provider.noise(sampleX, sampleZ);
if (size >= xBits.length) {
grow();
}
xBits[size] = rx;
zBits[size] = rz;
values[size] = value;
size++;
return value;
}
private void grow() {
int nextLength = xBits.length << 1;
long[] nextXBits = new long[nextLength];
long[] nextZBits = new long[nextLength];
double[] nextValues = new double[nextLength];
System.arraycopy(xBits, 0, nextXBits, 0, size);
System.arraycopy(zBits, 0, nextZBits, 0, size);
System.arraycopy(values, 0, nextValues, 0, size);
xBits = nextXBits;
zBits = nextZBits;
values = nextValues;
}
}
public static double rangeScale(double amin, double amax, double bmin, double bmax, double b) {
return amin + ((amax - amin) * ((b - bmin) / (bmax - bmin)));
}

View File

@@ -425,6 +425,26 @@ public class CNG {
return (int) Math.round(IrisInterpolation.lerp(min, max, noise));
}
public int fit(int min, int max, double x, double z) {
if (min == max) {
return min;
}
double noise = noise(x, z);
return (int) Math.round(IrisInterpolation.lerp(min, max, noise));
}
public int fit(int min, int max, double x, double y, double z) {
if (min == max) {
return min;
}
double noise = noise(x, y, z);
return (int) Math.round(IrisInterpolation.lerp(min, max, noise));
}
public int fit(double min, double max, double... dim) {
if (min == max) {
return (int) Math.round(min);
@@ -435,6 +455,26 @@ public class CNG {
return (int) Math.round(IrisInterpolation.lerp(min, max, noise));
}
public int fit(double min, double max, double x, double z) {
if (min == max) {
return (int) Math.round(min);
}
double noise = noise(x, z);
return (int) Math.round(IrisInterpolation.lerp(min, max, noise));
}
public int fit(double min, double max, double x, double y, double z) {
if (min == max) {
return (int) Math.round(min);
}
double noise = noise(x, y, z);
return (int) Math.round(IrisInterpolation.lerp(min, max, noise));
}
public double fitDouble(double min, double max, double... dim) {
if (min == max) {
return min;
@@ -445,6 +485,26 @@ public class CNG {
return IrisInterpolation.lerp(min, max, noise);
}
public double fitDouble(double min, double max, double x, double z) {
if (min == max) {
return min;
}
double noise = noise(x, z);
return IrisInterpolation.lerp(min, max, noise);
}
public double fitDouble(double min, double max, double x, double y, double z) {
if (min == max) {
return min;
}
double noise = noise(x, y, z);
return IrisInterpolation.lerp(min, max, noise);
}
private double getNoise(double... dim) {
double scale = noscale ? 1 : this.bakedScale * this.scale;
@@ -481,6 +541,42 @@ public class CNG {
return generator.noise(x * scale, y * scale, z * scale) * opacity;
}
private double getNoise(double x) {
double scl = noscale ? 1 : this.bakedScale * this.scale;
if (fracture == null || noscale) {
return generator.noise(x * scl, 0D, 0D) * opacity;
}
double fx = x + ((fracture.noise(x) - 0.5D) * fscale);
return generator.noise(fx * scl, 0D, 0D) * opacity;
}
private double getNoise(double x, double z) {
double scl = noscale ? 1 : this.bakedScale * this.scale;
if (fracture == null || noscale) {
return generator.noise(x * scl, z * scl, 0D) * opacity;
}
double fx = x + ((fracture.noise(x, z) - 0.5D) * fscale);
double fz = z + ((fracture.noise(z, x) - 0.5D) * fscale);
return generator.noise(fx * scl, fz * scl, 0D) * opacity;
}
private double getNoise(double x, double y, double z) {
double scl = noscale ? 1 : this.bakedScale * this.scale;
if (fracture == null || noscale) {
return generator.noise(x * scl, y * scl, z * scl) * opacity;
}
double fx = x + ((fracture.noise(x, y, z) - 0.5D) * fscale);
double fy = y + ((fracture.noise(y, x) - 0.5D) * fscale);
double fz = z + ((fracture.noise(z, x, y) - 0.5D) * fscale);
return generator.noise(fx * scl, fy * scl, fz * scl) * opacity;
}
public double invertNoise(double... dim) {
if (dim.length == 1) {
return noise(-dim[0]);
@@ -497,9 +593,69 @@ public class CNG {
return (noise(dim) * 2) - 1;
}
private static boolean isWholeCoordinate(double value) {
return value == Math.rint(value);
}
private double applyPost(double n, double x) {
n = power != 1D ? (n < 0 ? -Math.pow(Math.abs(n), power) : Math.pow(n, power)) : n;
double m = 1;
hits += oct;
if (children != null) {
for (CNG i : children) {
double[] r = injector.combine(n, i.noise(x));
n = r[0];
m += r[1];
}
}
return ((n / m) - down + up) * patch;
}
private double applyPost(double n, double x, double z) {
n = power != 1D ? (n < 0 ? -Math.pow(Math.abs(n), power) : Math.pow(n, power)) : n;
double m = 1;
hits += oct;
if (children != null) {
for (CNG i : children) {
double[] r = injector.combine(n, i.noise(x, z));
n = r[0];
m += r[1];
}
}
return ((n / m) - down + up) * patch;
}
private double applyPost(double n, double x, double y, double z) {
n = power != 1D ? (n < 0 ? -Math.pow(Math.abs(n), power) : Math.pow(n, power)) : n;
double m = 1;
hits += oct;
if (children != null) {
for (CNG i : children) {
double[] r = injector.combine(n, i.noise(x, y, z));
n = r[0];
m += r[1];
}
}
return ((n / m) - down + up) * patch;
}
public double noise(double... dim) {
if (cache != null && dim.length == 2) {
return cache.get((int) dim[0], (int) dim[1]);
if (dim.length == 1) {
return noise(dim[0]);
}
if (dim.length == 2) {
return noise(dim[0], dim[1]);
}
if (dim.length == 3) {
return noise(dim[0], dim[1], dim[2]);
}
double n = getNoise(dim);
@@ -519,6 +675,22 @@ public class CNG {
return ((n / m) - down + up) * patch;
}
public double noise(double x) {
return applyPost(getNoise(x), x);
}
public double noise(double x, double z) {
if (cache != null && isWholeCoordinate(x) && isWholeCoordinate(z)) {
return cache.get((int) x, (int) z);
}
return applyPost(getNoise(x, z), x, z);
}
public double noise(double x, double y, double z) {
return applyPost(getNoise(x, y, z), x, y, z);
}
public CNG pow(double power) {
this.power = power;
return this;

View File

@@ -23,11 +23,12 @@ class ChunkedDataCache<T> private constructor(
if (!cache) return
supervisorScope {
for (i in 0 until 16) {
for (j in 0 until 16) {
launch(context) {
val t = stream.get((x + i).toDouble(), (z + j).toDouble())
data[(j * 16) + i] = t
for (j in 0 until 16) {
launch(context) {
val rowOffset = j * 16
val zz = (z + j).toDouble()
for (i in 0 until 16) {
data[rowOffset + i] = stream.get((x + i).toDouble(), zz)
}
}
}