Share cave density lattice

- Adaptive plane lattice samples cached per carver across neighbour chunks
- Grid cleared with carvers on hotload
This commit is contained in:
Brian Neumann-Fopiano
2026-09-28 11:06:34 -04:00
parent 631c486a5a
commit 2ce36eb6f1
4 changed files with 162 additions and 12 deletions
@@ -0,0 +1,84 @@
package art.arcane.iris.generation.mantle;
import art.arcane.iris.generation.cache.ConcurrentClockCache;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.Arrays;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReferenceArray;
/**
* Shares the adaptive cave planes' 8-block density lattice between neighbouring chunks. Every lattice column on a
* chunk edge is sampled by two chunks and every corner by four, with identical coordinates and carver noise, so a
* value computed once is reused bit for bit. Racing writers store the same value.
*/
final class CaveDensityGrid {
static final int LATTICE_SHIFT = 3;
private static final int SEGMENT_SHIFT = 5;
private static final int SEGMENT_SIZE = 1 << SEGMENT_SHIFT;
private static final long COORDINATE_MASK = 0xFFFFFFL;
private static final VarHandle VALUES = MethodHandles.arrayElementVarHandle(double[].class);
private static final AtomicInteger OWNERS = new AtomicInteger();
private final ConcurrentClockCache<Column> columns;
CaveDensityGrid(int capacity) {
columns = new ConcurrentClockCache<>(capacity);
}
static int nextOwner() {
return OWNERS.getAndIncrement();
}
Column column(Object owner, int ownerId, int height, int latticeX, int latticeZ) {
long key = (long) (ownerId & 0xFFFF) << 48 | (latticeX & COORDINATE_MASK) << 24 | (latticeZ & COORDINATE_MASK);
Column cached = columns.get(key);
if (cached == null) {
cached = columns.putIfAbsent(key, new Column(owner, latticeX, latticeZ, height));
}
return cached.owner == owner && cached.latticeX == latticeX && cached.latticeZ == latticeZ ? cached : null;
}
void clear() {
columns.clear();
}
static final class Column {
private final Object owner;
private final int latticeX;
private final int latticeZ;
private final AtomicReferenceArray<double[]> segments;
private Column(Object owner, int latticeX, int latticeZ, int height) {
this.owner = owner;
this.latticeX = latticeX;
this.latticeZ = latticeZ;
segments = new AtomicReferenceArray<>(Math.ceilDiv(Math.max(1, height), SEGMENT_SIZE));
}
double get(int y) {
int segment = y >> SEGMENT_SHIFT;
if (segment >= segments.length()) {
return Double.NaN;
}
double[] values = segments.getAcquire(segment);
return values == null ? Double.NaN : (double) VALUES.getOpaque(values, y & (SEGMENT_SIZE - 1));
}
void set(int y, double value) {
int segment = y >> SEGMENT_SHIFT;
if (segment >= segments.length()) {
return;
}
double[] values = segments.getAcquire(segment);
if (values == null) {
double[] created = new double[SEGMENT_SIZE];
Arrays.fill(created, Double.NaN);
double[] published = segments.compareAndExchange(segment, null, created);
values = published == null ? created : published;
}
VALUES.setOpaque(values, y & (SEGMENT_SIZE - 1), value);
}
}
}
@@ -61,6 +61,7 @@ public class IrisCaveCarver3D {
private static final int MINIMUM_PARALLEL_DENSITY_CELLS = 16_384;
private static final int MAXIMUM_DENSITY_WORKERS = 4;
private static final int[] LATTICE_TILE_OFFSETS = {0, 1, 16, 17};
private static final int STANDALONE_DENSITY_GRID_COLUMNS = 1024;
private final Engine engine;
private final IrisData data;
@@ -96,9 +97,16 @@ public class IrisCaveCarver3D {
private final int densityMaximumY;
private final ThreadLocal<Boolean> carving = ThreadLocal.withInitial(() -> false);
private final ThreadLocal<CaveCarveScratch> scratchCache = ThreadLocal.withInitial(CaveCarveScratch::new);
private final CaveDensityGrid densityGrid;
private final int densityGridOwner = CaveDensityGrid.nextOwner();
public IrisCaveCarver3D(Engine engine, IrisCaveProfile profile) {
this(engine, profile, new CaveDensityGrid(STANDALONE_DENSITY_GRID_COLUMNS));
}
IrisCaveCarver3D(Engine engine, IrisCaveProfile profile, CaveDensityGrid densityGrid) {
this.engine = engine;
this.densityGrid = densityGrid;
this.data = engine.getData();
this.profile = profile;
this.carveAir = new MatterCavern(true, "", LIQUID_AIR);
@@ -1542,7 +1550,7 @@ public class IrisCaveCarver3D {
int rowOffset = sampleXIndex * axisSamples;
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityNoWarpNoModules(x, y, z0 + sampleLocalZ);
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
}
}
@@ -1595,7 +1603,7 @@ public class IrisCaveCarver3D {
int rowOffset = sampleXIndex * axisSamples;
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityNoWarpNoModules(x, y, z0 + sampleLocalZ);
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
}
}
@@ -1643,7 +1651,7 @@ public class IrisCaveCarver3D {
int rowOffset = sampleXIndex * axisSamples;
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityWarpOnly(scratch, x, y, z0 + sampleLocalZ);
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
}
}
@@ -1696,14 +1704,7 @@ public class IrisCaveCarver3D {
int rowOffset = sampleXIndex * axisSamples;
for (int sampleZIndex = adaptivePlaneSampleBounds[2]; sampleZIndex <= adaptivePlaneSampleBounds[3]; sampleZIndex++) {
int sampleLocalZ = Math.min(sampleZIndex * adaptiveSampleStep, 16);
adaptivePlaneDensity[rowOffset + sampleZIndex] = sampleDensityWarpModules(
scratch,
x,
y,
z0 + sampleLocalZ,
localModules,
activeModuleCount
);
adaptivePlaneDensity[rowOffset + sampleZIndex] = latticeDensity(scratch, x, y, z0 + sampleLocalZ);
}
}
@@ -2463,6 +2464,33 @@ public class IrisCaveCarver3D {
scratch.adaptiveGeometryAxisCells = axisCells;
}
private double latticeDensity(CaveCarveScratch scratch, int x, int y, int z) {
int latticeMask = (1 << CaveDensityGrid.LATTICE_SHIFT) - 1;
CaveDensityGrid.Column column = ((x | z) & latticeMask) != 0 ? null : densityGrid.column(this, densityGridOwner,
densityMaximumY + 1, x >> CaveDensityGrid.LATTICE_SHIFT, z >> CaveDensityGrid.LATTICE_SHIFT);
if (column == null) {
return samplePlaneDensity(scratch, x, y, z);
}
double cached = column.get(y);
if (!Double.isNaN(cached)) {
return cached;
}
double density = samplePlaneDensity(scratch, x, y, z);
column.set(y, density);
return density;
}
private double samplePlaneDensity(CaveCarveScratch scratch, int x, int y, int z) {
if (!hasWarp) {
return sampleDensityNoWarpNoModules(x, y, z);
}
int activeModuleCount = hasModules ? prepareActiveModules(scratch, y) : 0;
if (activeModuleCount == 0) {
return sampleDensityWarpOnly(scratch, x, y, z);
}
return sampleDensityWarpModules(scratch, x, y, z, scratch.activeModules, activeModuleCount);
}
private double sampleDensityNoWarpNoModules(int x, int y, int z) {
double density = baseDensity.noiseFastSigned3D(x, y, z) * baseWeight;
density += detailDensity.noiseFastSigned3D(x, y, z) * detailWeight;
@@ -54,6 +54,8 @@ public class MantleCarvingComponent extends IrisMantleComponent {
private static final double THRESHOLD_PENALTY = 0.24D;
private static final int MAX_BLENDED_PROFILE_PASSES = 2;
private static final int MAX_POOLED_WEIGHT_BUFFERS = 64;
/** Lattice columns kept for neighbouring chunks: about 3 KB each on land, roughly 50 MB when full. */
private static final int DENSITY_GRID_COLUMNS = 16_384;
private static final int KERNEL_WIDTH = (BLEND_RADIUS * 2) + 1;
private static final int KERNEL_SIZE = KERNEL_WIDTH * KERNEL_WIDTH;
private static final int[] KERNEL_DX = new int[KERNEL_SIZE];
@@ -62,6 +64,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
private static final ThreadLocal<BlendScratch> BLEND_SCRATCH = ThreadLocal.withInitial(BlendScratch::new);
private final Object profileCarverLock = new Object();
private final CaveDensityGrid densityGrid = new CaveDensityGrid(DENSITY_GRID_COLUMNS);
private volatile Map<IrisCaveProfile, IrisCaveCarver3D> profileCarvers = new IdentityHashMap<>();
static {
@@ -94,6 +97,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
// Carvers rebuild deterministically from the carve seed, so output is unchanged.
synchronized (profileCarverLock) {
profileCarvers = new IdentityHashMap<>();
densityGrid.clear();
}
}
@@ -524,7 +528,7 @@ public class MantleCarvingComponent extends IrisMantleComponent {
return carver;
}
IrisCaveCarver3D createdCarver = new IrisCaveCarver3D(getEngineMantle().getEngine(), profile);
IrisCaveCarver3D createdCarver = new IrisCaveCarver3D(getEngineMantle().getEngine(), profile, densityGrid);
synchronized (profileCarverLock) {
IrisCaveCarver3D published = profileCarvers.get(profile);
if (published != null) {
@@ -1038,6 +1038,40 @@ public class IrisCaveCarver3DNearParityTest {
largestPlane < 192);
}
@Test
public void sharedDensityLatticeReproducesIsolatedCarvesAcrossNeighbourChunks() throws Exception {
Engine engine = createEngine(96, 90);
for (boolean warp : new boolean[]{false, true}) {
for (boolean modules : new boolean[]{false, true}) {
AtomicLong sharedSamples = new AtomicLong();
AtomicLong isolatedSamples = new AtomicLong();
IrisCaveCarver3D shared = new IrisCaveCarver3D(engine,
createProfile(warp, modules).setAdaptiveSampling(true), new CaveDensityGrid(4096));
baseDensityField.set(shared, new CountingCNG((CNG) baseDensityField.get(shared), sharedSamples));
for (int chunkX = -1; chunkX <= 1; chunkX++) {
for (int chunkZ = -1; chunkZ <= 1; chunkZ++) {
WriterCapture sharedCapture = createWriterCapture(96);
int sharedCarved = shared.carve(sharedCapture.writer, chunkX, chunkZ, fullWeights(), 0D, 0D,
null, filledHeights(90));
IrisCaveCarver3D isolated = new IrisCaveCarver3D(engine,
createProfile(warp, modules).setAdaptiveSampling(true));
baseDensityField.set(isolated, new CountingCNG((CNG) baseDensityField.get(isolated), isolatedSamples));
WriterCapture isolatedCapture = createWriterCapture(96);
int isolatedCarved = isolated.carve(isolatedCapture.writer, chunkX, chunkZ, fullWeights(), 0D, 0D,
null, filledHeights(90));
assertTrue(isolatedCarved > 0);
assertEquals(isolatedCarved, sharedCarved);
assertEquals(isolatedCapture.carvedCells, sharedCapture.carvedCells);
assertEquals(isolatedCapture.carvedLiquids, sharedCapture.carvedLiquids);
}
}
assertTrue("warp=" + warp + " modules=" + modules + " shared=" + sharedSamples.get()
+ " isolated=" + isolatedSamples.get(),
sharedSamples.get() < isolatedSamples.get() * 3 / 4);
}
}
}
private void assertExactParity(boolean warp, boolean modules, boolean adaptiveSampling) throws Exception {
Engine engine = createEngine(96, 90);
double[] columnWeights = fullWeights();