Merged carve kernel

- Cell-bound cave carve classification with exact mixed-cell evaluation

# Conflicts:
#	probe/src/main/java/art/arcane/iris/probe/GenerationProbe.java
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-21 23:04:20 -04:00
commit 78de23e461
6 files changed
+1048 -4

No files matched your search

@@ -66,6 +66,25 @@ final class CaveCarveScratch {
final int[] tileLocalX = new int[4];
final int[] tileLocalZ = new int[4];
final int[] tileTopY = new int[4];
final long[] rowCarveBits = new long[4];
final long[] cellPassBits = new long[4];
final double[] rowLatticeDensity = new double[9];
final CaveDensityGrid.Column[] cellCornerColumns = new CaveDensityGrid.Column[9];
final double[] cellThresholdMin = new double[4];
final double[] cellThresholdMax = new double[4];
final double[] cellThresholdMaxBoost = new double[4];
final int[] cellBreakFloorMin = new int[4];
final int[] cellCeilingFadeStart = new int[4];
final int[] cellTopY = new int[4];
final int[] cellMinTopY = new int[4];
final boolean[] cellProtected = new boolean[4];
final int[] columnProtectStartY = new int[256];
final int[] columnProtectEndY = new int[256];
int[] rowActiveCount = new int[0];
long solidCells;
long openCells;
long mixedCells;
long exactRows;
CaveFieldModuleState[] activeModules = new CaveFieldModuleState[0];
double[] activeModuleRemainingMin = new double[0];
double[] activeModuleRemainingMax = new double[0];
@@ -84,5 +103,6 @@ final class CaveCarveScratch {
Arrays.fill(sectionMatter, null);
Arrays.fill(sectionSlices, null);
Arrays.fill(densityColumns, null);
Arrays.fill(cellCornerColumns, null);
}
}
@@ -0,0 +1,33 @@
package art.arcane.iris.generation.mantle;
final class CaveCellKernel {
static final int NONE = 0;
static final int ALL = 1;
static final int MIXED = 2;
private static final double GUARD_ULPS = 32D;
private static final double MAXIMUM_MAGNITUDE = 0x1p500;
private CaveCellKernel() {
}
static int decide(double d00, double d01, double d10, double d11, double thresholdLow, double thresholdHigh) {
double low = Math.min(Math.min(d00, d01), Math.min(d10, d11));
double high = Math.max(Math.max(d00, d01), Math.max(d10, d11));
double magnitude = Math.max(Math.abs(low), Math.abs(high));
if (!(magnitude < MAXIMUM_MAGNITUDE)) {
return MIXED;
}
double guard = Math.ulp(magnitude) * GUARD_ULPS;
if (high + guard <= thresholdLow) {
return ALL;
}
if (low - guard > thresholdHigh) {
return NONE;
}
return MIXED;
}
static double threshold(double localThreshold, double normalizationFactor, double inverseNormalization) {
return (localThreshold * normalizationFactor) * inverseNormalization;
}
}
@@ -43,6 +43,7 @@ import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinWorkerThread;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.LongAdder;
public class IrisCaveCarver3D {
private static final byte LIQUID_AIR = 0;
@@ -62,6 +63,12 @@ public class IrisCaveCarver3D {
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 static final int[] CELL_CORNER_MASKS = {0b000011011, 0b000110110, 0b011011000, 0b110110000};
private static final long[] CELL_Z_MASKS = {0x00FF00FF00FF00FFL, 0xFF00FF00FF00FF00L};
private static final LongAdder SOLID_CELLS = new LongAdder();
private static final LongAdder OPEN_CELLS = new LongAdder();
private static final LongAdder MIXED_CELLS = new LongAdder();
private static final LongAdder EXACT_ROWS = new LongAdder();
private final Engine engine;
private final IrisData data;
@@ -155,6 +162,10 @@ public class IrisCaveCarver3D {
densityMaximumY = engine.getWorld().getHeight() - 1;
}
public static CellCounters cellCounters() {
return new CellCounters(SOLID_CELLS.sum(), OPEN_CELLS.sum(), MIXED_CELLS.sum(), EXACT_ROWS.sum());
}
public int carve(MantleWriter writer, int chunkX, int chunkZ) {
CaveCarveScratch scratch = scratchCache.get();
if (!scratch.fullWeightsInitialized) {
@@ -504,7 +515,9 @@ public class IrisCaveCarver3D {
int minimumY = pass.minimumY() + index * rows;
int maximumY = Math.min(pass.maximumY(), minimumY + rows - 1);
if (minimumY <= maximumY) {
tasks.add(() -> classifyRows(pass, minimumY, maximumY, classified));
tasks.add(pass.cellKernel()
? () -> classifyCellRows(pass, minimumY, maximumY, classified)
: () -> classifyRows(pass, minimumY, maximumY, classified));
}
}
runDensityTasks(tasks, pool);
@@ -697,10 +710,25 @@ public class IrisCaveCarver3D {
int[] columns, int[] columnTops, int columnCount, double[] thresholds,
long[] fluidBoundaries, boolean[] surfaceBreakColumns, int[] surfaceBreakFloors,
double surfaceBreakBoost, double[] verticalFade, CaveCarveScratch sourceScratch,
int adaptiveStep, double adaptiveMargin, int[] fluidTops, MatterCavern[] matterByY
int adaptiveStep, double adaptiveMargin, int[] fluidTops, MatterCavern[] matterByY, boolean cellKernel
) {
}
public record CellCounters(long solidCells, long openCells, long mixedCells, long exactRows) {
public long decided() {
return solidCells + openCells;
}
public long evaluated() {
return mixedCells;
}
public String machineLine() {
return "IRIS_GENPROBE_CARVE_CELLS solid=" + solidCells + " open=" + openCells + " mixed=" + mixedCells
+ " exact_rows=" + exactRows;
}
}
private static final class DensityTask implements Runnable, ForkJoinPool.ManagedBlocker {
private final Runnable work;
private final AtomicBoolean claimed = new AtomicBoolean();
@@ -795,7 +823,7 @@ public class IrisCaveCarver3D {
byte[] classified = skipExistingCarved ? null : classifyPassParallel(new DensityPass(
x0, z0, minY, passMaxY, activeColumnIndices, activeColumnTopY, activeColumnCount,
passThreshold, surfaceFluidBoundaries, surfaceBreakColumn, surfaceBreakFloorY,
surfaceBreakThresholdBoost, verticalEdgeFade, scratch, 0, 0D, fluidMaxY, matterByY));
surfaceBreakThresholdBoost, verticalEdgeFade, scratch, 0, 0D, fluidMaxY, matterByY, false));
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
@@ -944,12 +972,23 @@ public class IrisCaveCarver3D {
adaptiveSampleStep,
effectiveAdaptiveSampleStep
);
boolean cellKernel = !skipExistingCarved && usesCellKernel(effectiveAdaptiveSampleStep);
if (cellKernel) {
prepareCellPlan(scratch, x0, z0, minY, passMaxY, activeColumnIndices, activeColumnCount,
surfaceFluidBoundaries, surfaceBreakThresholdBoost);
}
byte[] classified = skipExistingCarved ? null : classifyPassParallel(new DensityPass(
x0, z0, minY, passMaxY, activeColumnIndices, activeColumnTopY, activeColumnCount,
passThreshold, surfaceFluidBoundaries, surfaceBreakColumn, surfaceBreakFloorY,
surfaceBreakThresholdBoost, verticalEdgeFade, scratch, effectiveAdaptiveSampleStep, effectiveAdaptiveThresholdMargin,
fluidMaxY, matterByY));
fluidMaxY, matterByY, cellKernel));
if (cellKernel) {
return emitCellPass(chunk, scratch, classified, x0, z0, minY, passMaxY, minSection, maxSection,
activeColumnIndices, activeColumnTopY, activeColumnCount, surfaceFluidBoundaries,
surfaceBreakThresholdBoost, verticalEdgeFade, fluidMaxY, matterByY, fluidSupportPlan);
}
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
@@ -1059,6 +1098,480 @@ public class IrisCaveCarver3D {
return adaptiveThresholdMargin + ((effectiveAdaptiveSampleStep - adaptiveSampleStep) * ADAPTIVE_DEEP_MARGIN_BOOST);
}
private boolean usesCellKernel(int effectiveAdaptiveSampleStep) {
return effectiveAdaptiveSampleStep == ADAPTIVE_DEEP_SAMPLE_STEP
&& verticalDensityStep == 1
&& parallelDensity
&& (hasWarp || !hasModules);
}
private void prepareCellPlan(
CaveCarveScratch plan,
int x0,
int z0,
int minY,
int passMaxY,
int[] activeColumnIndices,
int activeColumnCount,
long[] surfaceFluidBoundaries,
double surfaceBreakThresholdBoost
) {
int rows = passMaxY - minY + 1;
if (plan.rowActiveCount.length < rows + 1) {
plan.rowActiveCount = new int[rows + 1];
}
int[] rowActiveCount = plan.rowActiveCount;
Arrays.fill(rowActiveCount, 0, rows + 1, 0);
Arrays.fill(plan.cellPassBits, 0L);
Arrays.fill(plan.cellThresholdMin, Double.POSITIVE_INFINITY);
Arrays.fill(plan.cellThresholdMax, Double.NEGATIVE_INFINITY);
Arrays.fill(plan.cellThresholdMaxBoost, Double.NEGATIVE_INFINITY);
Arrays.fill(plan.cellBreakFloorMin, Integer.MAX_VALUE);
Arrays.fill(plan.cellCeilingFadeStart, Integer.MAX_VALUE);
Arrays.fill(plan.cellTopY, Integer.MIN_VALUE);
Arrays.fill(plan.cellMinTopY, Integer.MAX_VALUE);
Arrays.fill(plan.cellProtected, false);
prepareAdaptiveGeometry(plan, ADAPTIVE_DEEP_SAMPLE_STEP, 2, 3);
double[] passThreshold = plan.passThreshold;
for (int activeIndex = 0; activeIndex < activeColumnCount; activeIndex++) {
int column = activeColumnIndices[activeIndex];
int cell = ((column >> 7) << 1) | ((column >> 3) & 1);
int top = plan.columnMaxY[column];
double threshold = passThreshold[column];
plan.cellPassBits[column >> 6] |= 1L << column;
plan.cellThresholdMin[cell] = Math.min(plan.cellThresholdMin[cell], threshold);
plan.cellThresholdMax[cell] = Math.max(plan.cellThresholdMax[cell], threshold);
if (plan.surfaceBreakColumn[column]) {
plan.cellThresholdMaxBoost[cell] = Math.max(plan.cellThresholdMaxBoost[cell], threshold + surfaceBreakThresholdBoost);
plan.cellBreakFloorMin[cell] = Math.min(plan.cellBreakFloorMin[cell], plan.surfaceBreakFloorY[column]);
} else {
plan.cellThresholdMaxBoost[cell] = Math.max(plan.cellThresholdMaxBoost[cell], threshold);
}
if (plan.surfaceCeilingColumn[column]) {
plan.cellCeilingFadeStart[cell] = Math.min(plan.cellCeilingFadeStart[cell], top - SURFACE_CEILING_FADE_DEPTH + 1);
}
plan.cellTopY[cell] = Math.max(plan.cellTopY[cell], top);
plan.cellMinTopY[cell] = Math.min(plan.cellMinTopY[cell], top);
int protectStart = SurfaceFluidBoundaryPlan.NO_BOUNDARY;
int protectEnd = Integer.MIN_VALUE;
if (surfaceFluidBoundaries != null) {
long boundary = surfaceFluidBoundaries[column];
protectStart = SurfaceFluidBoundaryPlan.startY(boundary);
protectEnd = SurfaceFluidBoundaryPlan.endY(boundary);
}
plan.columnProtectStartY[column] = protectStart;
plan.columnProtectEndY[column] = protectEnd;
rowActiveCount[0]++;
rowActiveCount[top - minY + 1]--;
int protectedFrom = Math.max(protectStart, minY);
int protectedTo = Math.min(protectEnd, top);
if (protectedFrom <= protectedTo) {
rowActiveCount[protectedFrom - minY]--;
rowActiveCount[protectedTo - minY + 1]++;
plan.cellProtected[cell] = true;
}
}
for (int row = 1; row < rows; row++) {
rowActiveCount[row] += rowActiveCount[row - 1];
}
int latticeX = x0 >> CaveDensityGrid.LATTICE_SHIFT;
int latticeZ = z0 >> CaveDensityGrid.LATTICE_SHIFT;
int usedCorners = 0;
for (int cell = 0; cell < 4; cell++) {
if (plan.cellTopY[cell] >= minY) {
usedCorners |= CELL_CORNER_MASKS[cell];
}
}
for (int corner = 0; corner < 9; corner++) {
plan.cellCornerColumns[corner] = (usedCorners & (1 << corner)) == 0 ? null
: densityGrid.column(this, densityGridOwner, densityMaximumY + 1,
latticeX + (corner / 3), latticeZ + (corner % 3));
}
}
private int emitCellPass(
MantleChunk<Matter> chunk,
CaveCarveScratch scratch,
byte[] classified,
int x0,
int z0,
int minY,
int passMaxY,
int minSection,
int maxSection,
int[] activeColumnIndices,
int[] activeColumnTopY,
int activeColumnCount,
long[] surfaceFluidBoundaries,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
int[] fluidMaxY,
MatterCavern[] matterByY,
CaveFluidSupportPlan fluidSupportPlan
) {
int carved = 0;
long[] rowBits = scratch.rowCarveBits;
for (int sectionIndex = minSection; sectionIndex <= maxSection; sectionIndex++) {
int sectionMinY = Math.max(minY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex));
int sectionMaxY = Math.min(passMaxY, PowerOfTwoCoordinates.chunkToBlock(sectionIndex) + 15);
MatterSlice<MatterCavern> cavernSlice = resolveCavernSlice(scratch, chunk, sectionIndex);
for (int y = sectionMinY; y <= sectionMaxY; y++) {
int fadeIndex = y - minY;
if (scratch.rowActiveCount[fadeIndex] == 0) {
continue;
}
MatterCavern verticalMatter = matterByY[fadeIndex];
if (classified != null) {
carved += emitClassifiedRow(cavernSlice, classified, fadeIndex, y, verticalMatter, fluidSupportPlan);
continue;
}
classifyAdaptiveRow(scratch, scratch, x0, z0, y, minY, activeColumnIndices, activeColumnTopY,
activeColumnCount, surfaceFluidBoundaries, surfaceBreakThresholdBoost, verticalEdgeFade, rowBits);
for (int word = 0; word < 4; word++) {
long bits = rowBits[word];
while (bits != 0L) {
int column = (word << 6) | Long.numberOfTrailingZeros(bits);
bits &= bits - 1L;
int localX = PowerOfTwoCoordinates.unpackLocal16X(column);
int localZ = column & 15;
MatterCavern matter = verticalMatter;
if (verticalMatter != carveLava && y <= fluidMaxY[column]) {
matter = resolveMatter(scratch, verticalMatter, x0 + localX, y, z0 + localZ, column, fluidMaxY,
cellThreshold(scratch, column, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade));
}
writeCavern(cavernSlice, localX, y, localZ, matter, fluidSupportPlan);
carved++;
}
}
}
}
flushCellCounters(scratch);
return carved;
}
private int emitClassifiedRow(MatterSlice<MatterCavern> cavernSlice, byte[] classified, int fadeIndex, int y,
MatterCavern verticalMatter, CaveFluidSupportPlan fluidSupportPlan) {
int carved = 0;
int offset = fadeIndex * 256;
for (int column = 0; column < 256; column++) {
byte flags = classified[offset + column];
if ((flags & DENSITY_CARVE) == 0) {
continue;
}
MatterCavern matter = (flags & DENSITY_FLUID) != 0 ? carveFluid : verticalMatter;
writeCavern(cavernSlice, PowerOfTwoCoordinates.unpackLocal16X(column), y, column & 15, matter, fluidSupportPlan);
carved++;
}
return carved;
}
private void classifyCellRows(DensityPass pass, int minimumY, int maximumY, byte[] classified) {
CaveCarveScratch scratch = new CaveCarveScratch();
CaveCarveScratch plan = pass.sourceScratch();
long[] rowBits = scratch.rowCarveBits;
for (int y = minimumY; y <= maximumY; y++) {
int fadeIndex = y - pass.minimumY();
if (plan.rowActiveCount[fadeIndex] == 0) {
continue;
}
classifyAdaptiveRow(scratch, plan, pass.x(), pass.z(), y, pass.minimumY(), pass.columns(), pass.columnTops(),
pass.columnCount(), pass.fluidBoundaries(), pass.surfaceBreakBoost(), pass.verticalFade(), rowBits);
MatterCavern verticalMatter = pass.matterByY()[fadeIndex];
int offset = fadeIndex * 256;
for (int word = 0; word < 4; word++) {
long bits = rowBits[word];
while (bits != 0L) {
int column = (word << 6) | Long.numberOfTrailingZeros(bits);
bits &= bits - 1L;
MatterCavern matter = verticalMatter;
if (verticalMatter != carveLava && y <= pass.fluidTops()[column]) {
matter = resolveMatter(scratch, verticalMatter, pass.x() + PowerOfTwoCoordinates.unpackLocal16X(column),
y, pass.z() + (column & 15), column, pass.fluidTops(),
cellThreshold(plan, column, y, pass.minimumY(), pass.surfaceBreakBoost(), pass.verticalFade()));
}
classified[offset + column] = matter == carveFluid ? DENSITY_CARVE | DENSITY_FLUID : DENSITY_CARVE;
}
}
}
flushCellCounters(scratch);
}
private void classifyAdaptiveRow(
CaveCarveScratch scratch,
CaveCarveScratch plan,
int x0,
int z0,
int y,
int minY,
int[] activeColumnIndices,
int[] activeColumnTopY,
int activeColumnCount,
long[] surfaceFluidBoundaries,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
long[] rowBits
) {
Arrays.fill(rowBits, 0L);
if (plan.rowActiveCount[y - minY] >= ADAPTIVE_MIN_PLANE_COLUMNS) {
classifyCellRow(scratch, plan, x0, z0, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade, rowBits);
return;
}
int[] planeColumnIndices = scratch.planeColumnIndices;
double[] planeThresholdLimit = scratch.planeThresholdLimit;
boolean[] planeCarve = scratch.planeCarve;
int planeCount = 0;
for (int activeIndex = 0; activeIndex < activeColumnCount; activeIndex++) {
if (activeColumnTopY[activeIndex] < y) {
continue;
}
int columnIndex = activeColumnIndices[activeIndex];
if (SurfaceFluidBoundaryPlan.protects(surfaceFluidBoundaries, columnIndex, y)) {
continue;
}
planeColumnIndices[planeCount] = columnIndex;
planeThresholdLimit[planeCount] = cellLocalThreshold(plan, columnIndex, y, minY, surfaceBreakThresholdBoost,
verticalEdgeFade) * normalizationFactor;
planeCount++;
}
classifyDensityPlane(scratch, x0, z0, y, planeColumnIndices, planeThresholdLimit, planeCount, planeCarve);
for (int planeIndex = 0; planeIndex < planeCount; planeIndex++) {
if (planeCarve[planeIndex]) {
int columnIndex = planeColumnIndices[planeIndex];
rowBits[columnIndex >> 6] |= 1L << columnIndex;
}
}
scratch.exactRows++;
}
private void classifyCellRow(
CaveCarveScratch scratch,
CaveCarveScratch plan,
int x0,
int z0,
int y,
int minY,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
long[] rowBits
) {
int activeCells = 0;
int corners = 0;
for (int cell = 0; cell < 4; cell++) {
if (isCellActive(plan, cell, y)) {
activeCells |= 1 << cell;
corners |= CELL_CORNER_MASKS[cell];
}
}
if (activeCells == 0) {
return;
}
double[] density = scratch.rowLatticeDensity;
for (int corner = 0; corner < 9; corner++) {
if ((corners & (1 << corner)) != 0) {
density[corner] = cornerDensity(scratch, plan.cellCornerColumns[corner],
x0 + (corner / 3) * ADAPTIVE_DEEP_SAMPLE_STEP, y, z0 + (corner % 3) * ADAPTIVE_DEEP_SAMPLE_STEP);
}
}
int fadeIndex = y - minY;
double fade = verticalEdgeFade[fadeIndex];
double closure = plan.surfaceClosureThreshold[fadeIndex];
for (int cell = 0; cell < 4; cell++) {
if ((activeCells & (1 << cell)) == 0) {
continue;
}
int corner = ((cell >> 1) * 3) + (cell & 1);
double d00 = density[corner];
double d01 = density[corner + 1];
double d10 = density[corner + 3];
double d11 = density[corner + 4];
double low = plan.cellThresholdMin[cell] - fade;
if (y >= plan.cellCeilingFadeStart[cell]) {
low = Math.min(low, closure);
}
double high = (y >= plan.cellBreakFloorMin[cell] ? plan.cellThresholdMaxBoost[cell] : plan.cellThresholdMax[cell]) - fade;
int decision = CaveCellKernel.decide(d00, d01, d10, d11,
CaveCellKernel.threshold(low, normalizationFactor, inverseNormalization),
CaveCellKernel.threshold(high, normalizationFactor, inverseNormalization));
boolean uniform = !plan.cellProtected[cell] && y <= plan.cellMinTopY[cell];
if (decision == CaveCellKernel.NONE) {
scratch.solidCells++;
} else if (decision == CaveCellKernel.ALL) {
scratch.openCells++;
if (uniform) {
int word = (cell >> 1) << 1;
long mask = CELL_Z_MASKS[cell & 1];
rowBits[word] |= plan.cellPassBits[word] & mask;
rowBits[word + 1] |= plan.cellPassBits[word + 1] & mask;
} else {
markActiveCellColumns(plan, cell, y, rowBits);
}
} else {
scratch.mixedCells++;
if (uniform && y < plan.cellCeilingFadeStart[cell] && y < plan.cellBreakFloorMin[cell]) {
classifyPlainMixedCell(plan, cell, fade, d00, d01, d10, d11, rowBits);
} else {
classifyMixedCell(plan, cell, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade, d00, d01, d10, d11, rowBits);
}
}
}
}
private void classifyMixedCell(
CaveCarveScratch plan,
int cell,
int y,
int minY,
double surfaceBreakThresholdBoost,
double[] verticalEdgeFade,
double d00,
double d01,
double d10,
double d11,
long[] rowBits
) {
double[] adaptiveTx = plan.adaptiveTx;
double[] adaptiveTz = plan.adaptiveTz;
int firstLocalX = (cell >> 1) << 3;
int firstLocalZ = (cell & 1) << 3;
for (int localX = firstLocalX; localX < firstLocalX + ADAPTIVE_DEEP_SAMPLE_STEP; localX++) {
int rowColumn = PowerOfTwoCoordinates.packLocal16(localX, firstLocalZ);
double tx = adaptiveTx[rowColumn];
double dx0 = d00 + ((d10 - d00) * tx);
double dx1 = d01 + ((d11 - d01) * tx);
double slope = dx1 - dx0;
for (int column = rowColumn; column < rowColumn + ADAPTIVE_DEEP_SAMPLE_STEP; column++) {
if (!isCellColumnActive(plan, column, y)) {
continue;
}
double predictedDensity = dx0 + (slope * adaptiveTz[column]);
if (predictedDensity <= cellThreshold(plan, column, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade)) {
rowBits[column >> 6] |= 1L << column;
}
}
}
}
private void classifyPlainMixedCell(
CaveCarveScratch plan,
int cell,
double fade,
double d00,
double d01,
double d10,
double d11,
long[] rowBits
) {
double[] passThreshold = plan.passThreshold;
double[] adaptiveTx = plan.adaptiveTx;
double[] adaptiveTz = plan.adaptiveTz;
int firstLocalX = (cell >> 1) << 3;
int firstLocalZ = (cell & 1) << 3;
for (int localX = firstLocalX; localX < firstLocalX + ADAPTIVE_DEEP_SAMPLE_STEP; localX++) {
int rowColumn = PowerOfTwoCoordinates.packLocal16(localX, firstLocalZ);
double tx = adaptiveTx[rowColumn];
double dx0 = d00 + ((d10 - d00) * tx);
double dx1 = d01 + ((d11 - d01) * tx);
double slope = dx1 - dx0;
long bits = 0L;
for (int offset = 0; offset < ADAPTIVE_DEEP_SAMPLE_STEP; offset++) {
int column = rowColumn + offset;
double predictedDensity = dx0 + (slope * adaptiveTz[column]);
if (predictedDensity <= CaveCellKernel.threshold(passThreshold[column] - fade, normalizationFactor,
inverseNormalization)) {
bits |= 1L << offset;
}
}
rowBits[rowColumn >> 6] |= bits << (rowColumn & 63);
}
}
private static void markActiveCellColumns(CaveCarveScratch plan, int cell, int y, long[] rowBits) {
int firstLocalX = (cell >> 1) << 3;
int firstLocalZ = (cell & 1) << 3;
for (int localX = firstLocalX; localX < firstLocalX + ADAPTIVE_DEEP_SAMPLE_STEP; localX++) {
int rowColumn = PowerOfTwoCoordinates.packLocal16(localX, firstLocalZ);
for (int column = rowColumn; column < rowColumn + ADAPTIVE_DEEP_SAMPLE_STEP; column++) {
if (isCellColumnActive(plan, column, y)) {
rowBits[column >> 6] |= 1L << column;
}
}
}
}
private static boolean isCellActive(CaveCarveScratch plan, int cell, int y) {
if (y > plan.cellTopY[cell]) {
return false;
}
if (!plan.cellProtected[cell]) {
return true;
}
int firstLocalX = (cell >> 1) << 3;
int firstLocalZ = (cell & 1) << 3;
for (int localX = firstLocalX; localX < firstLocalX + ADAPTIVE_DEEP_SAMPLE_STEP; localX++) {
int rowColumn = PowerOfTwoCoordinates.packLocal16(localX, firstLocalZ);
for (int column = rowColumn; column < rowColumn + ADAPTIVE_DEEP_SAMPLE_STEP; column++) {
if (isCellColumnActive(plan, column, y)) {
return true;
}
}
}
return false;
}
private static boolean isCellColumnActive(CaveCarveScratch plan, int column, int y) {
return (plan.cellPassBits[column >> 6] & (1L << column)) != 0L
&& plan.columnMaxY[column] >= y
&& (y < plan.columnProtectStartY[column] || y > plan.columnProtectEndY[column]);
}
private double cornerDensity(CaveCarveScratch scratch, CaveDensityGrid.Column column, int x, int y, int z) {
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 cellThreshold(CaveCarveScratch plan, int column, int y, int minY, double surfaceBreakThresholdBoost,
double[] verticalEdgeFade) {
return CaveCellKernel.threshold(cellLocalThreshold(plan, column, y, minY, surfaceBreakThresholdBoost, verticalEdgeFade),
normalizationFactor, inverseNormalization);
}
private double cellLocalThreshold(CaveCarveScratch plan, int column, int y, int minY, double surfaceBreakThresholdBoost,
double[] verticalEdgeFade) {
double localThreshold = plan.passThreshold[column];
if (plan.surfaceBreakColumn[column] && y >= plan.surfaceBreakFloorY[column]) {
localThreshold += surfaceBreakThresholdBoost;
}
localThreshold -= verticalEdgeFade[y - minY];
return applySurfaceCeilingFade(plan, localThreshold, column, y, minY);
}
private static void flushCellCounters(CaveCarveScratch scratch) {
if (scratch.solidCells != 0L) {
SOLID_CELLS.add(scratch.solidCells);
scratch.solidCells = 0L;
}
if (scratch.openCells != 0L) {
OPEN_CELLS.add(scratch.openCells);
scratch.openCells = 0L;
}
if (scratch.mixedCells != 0L) {
MIXED_CELLS.add(scratch.mixedCells);
scratch.mixedCells = 0L;
}
if (scratch.exactRows != 0L) {
EXACT_ROWS.add(scratch.exactRows);
scratch.exactRows = 0L;
}
}
private int carvePassLattice(
MantleChunk<Matter> chunk,
int x0,
@@ -0,0 +1,106 @@
package art.arcane.iris.generation.mantle;
import org.junit.Test;
import java.util.SplittableRandom;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
public class CaveCellKernelTest {
private static final int TRIALS = 200_000;
@Test
public void decidedCellsAgreeWithEveryInterpolatedColumn() {
SplittableRandom random = new SplittableRandom(91_337L);
long decided = 0L;
for (int trial = 0; trial < TRIALS; trial++) {
double scale = Math.scalb(1D, random.nextInt(-30, 4));
double base = (random.nextDouble() * 2D - 1D) * scale;
double spread = random.nextInt(4) == 0 ? 0D : random.nextDouble() * scale * Math.scalb(1D, -random.nextInt(0, 40));
double d00 = base + spread * (random.nextDouble() * 2D - 1D);
double d01 = random.nextInt(8) == 0 ? d00 : base + spread * (random.nextDouble() * 2D - 1D);
double d10 = base + spread * (random.nextDouble() * 2D - 1D);
double d11 = random.nextInt(8) == 0 ? d10 : base + spread * (random.nextDouble() * 2D - 1D);
double low = Math.min(Math.min(d00, d01), Math.min(d10, d11));
double high = Math.max(Math.max(d00, d01), Math.max(d10, d11));
int mode = random.nextInt(6);
double[] thresholds = new double[64];
for (int column = 0; column < 64; column++) {
thresholds[column] = pickThreshold(random, low, high, mode);
}
double thresholdLow = Double.POSITIVE_INFINITY;
double thresholdHigh = Double.NEGATIVE_INFINITY;
for (double threshold : thresholds) {
thresholdLow = Math.min(thresholdLow, threshold);
thresholdHigh = Math.max(thresholdHigh, threshold);
}
int decision = CaveCellKernel.decide(d00, d01, d10, d11, thresholdLow, thresholdHigh);
if (decision == CaveCellKernel.MIXED) {
continue;
}
decided++;
for (int column = 0; column < 64; column++) {
double tx = (column >> 3) / 8D;
double tz = (column & 7) / 8D;
boolean carved = bilinear(d00, d01, d10, d11, tx, tz) <= thresholds[column];
assertEquals("trial " + trial + " column " + column, decision == CaveCellKernel.ALL, carved);
}
}
assertTrue("decided " + decided, decided > TRIALS / 4);
}
@Test
public void thresholdsAtTheCornerExtremesStayMixed() {
double d00 = 0.1D;
double d01 = -0.2D;
double d10 = 0.3D;
double d11 = 0.05D;
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(d00, d01, d10, d11, 0.3D, 0.3D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(d00, d01, d10, d11, Math.nextUp(0.3D), 0.4D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(d00, d01, d10, d11, -0.3D, Math.nextDown(-0.2D)));
assertEquals(CaveCellKernel.ALL, CaveCellKernel.decide(d00, d01, d10, d11, 0.3D + 1.0E-12D, 0.5D));
assertEquals(CaveCellKernel.NONE, CaveCellKernel.decide(d00, d01, d10, d11, -0.9D, -0.2D - 1.0E-12D));
}
@Test
public void nonFiniteCornersAreNeverDecided() {
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(Double.NaN, 0D, 0D, 0D, 5D, 5D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(0D, Double.POSITIVE_INFINITY, 0D, 0D, -5D, -5D));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(0D, 0D, Double.NEGATIVE_INFINITY, 0D,
Double.POSITIVE_INFINITY, Double.POSITIVE_INFINITY));
assertEquals(CaveCellKernel.MIXED, CaveCellKernel.decide(0D, 0D, 0D, 0D, Double.NaN, 1D));
}
@Test
public void monotoneThresholdMappingPreservesBounds() {
SplittableRandom random = new SplittableRandom(7_771L);
for (int trial = 0; trial < TRIALS; trial++) {
double normalization = 0.25D + random.nextDouble() * 3D;
double inverse = 1D / normalization;
double a = (random.nextDouble() * 2D - 1D) * Math.scalb(1D, random.nextInt(-20, 3));
double b = random.nextInt(4) == 0 ? Math.nextUp(a) : a + random.nextDouble() * Math.scalb(1D, random.nextInt(-50, 0));
assertTrue(CaveCellKernel.threshold(a, normalization, inverse) <= CaveCellKernel.threshold(b, normalization, inverse));
assertEquals(Double.doubleToRawLongBits((a * normalization) * inverse),
Double.doubleToRawLongBits(CaveCellKernel.threshold(a, normalization, inverse)));
}
}
private static double pickThreshold(SplittableRandom random, double low, double high, int mode) {
double width = Math.max(high - low, Math.ulp(Math.max(Math.abs(low), Math.abs(high))));
return switch (mode) {
case 0 -> high + Math.ulp(high) * random.nextInt(0, 64);
case 1 -> low - Math.ulp(low) * random.nextInt(0, 64);
case 2 -> high + width * random.nextDouble() * 0.5D;
case 3 -> low - width * random.nextDouble() * 0.5D;
case 4 -> high + width * (1D + random.nextDouble() * 4D);
default -> low - width * (1D + random.nextDouble() * 4D);
};
}
private static double bilinear(double d00, double d01, double d10, double d11, double tx, double tz) {
double dx0 = d00 + ((d10 - d00) * tx);
double dx1 = d01 + ((d11 - d01) * tx);
return dx0 + ((dx1 - dx0) * tz);
}
}
@@ -0,0 +1,370 @@
package art.arcane.iris.generation.mantle;
import art.arcane.iris.generation.cave.IrisCaveFieldModule;
import art.arcane.iris.generation.cave.IrisCaveProfile;
import art.arcane.iris.generation.noise.IrisGeneratorStyle;
import art.arcane.iris.generation.noise.IrisStyledRange;
import art.arcane.iris.generation.noise.NoiseStyle;
import art.arcane.iris.generation.runtime.Engine;
import art.arcane.iris.generation.runtime.EngineMetrics;
import art.arcane.iris.generation.runtime.SeedManager;
import art.arcane.iris.generation.terrain.IrisDimension;
import art.arcane.iris.pack.loading.IrisData;
import art.arcane.iris.pack.value.IrisRange;
import art.arcane.iris.world.IrisWorld;
import art.arcane.volmlib.util.collection.KList;
import art.arcane.volmlib.util.mantle.runtime.Mantle;
import art.arcane.volmlib.util.mantle.runtime.MantleChunk;
import art.arcane.volmlib.util.matter.Matter;
import art.arcane.volmlib.util.matter.MatterCavern;
import art.arcane.volmlib.util.matter.MatterSlice;
import art.arcane.volmlib.util.matter.slices.CavernMatter;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.TimeUnit;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.withSettings;
public class IrisCaveCarver3DCellKernelTest {
private static final long CARVE_SEED = -54_777_863_784_492_918L;
private static final int WORLD_HEIGHT = 160;
private static final int CHUNK_SPAN = 4;
@Test
public void warpModulesProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.WARP_MODULES, 0xdf617c77d3c3cc5eL, 67798);
}
@Test
public void warpOnlyTerrainProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.WARP_TERRAIN, 0x7e0b63cab5bb3904L, 67036);
}
@Test
public void styledThresholdBlendProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.STYLED_BLEND, 0x25792d49143774faL, 30387);
}
@Test
public void fluidBoundaryProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.FLUID_BOUNDARY, 0xb102145856119afeL, 17584);
}
@Test
public void noWarpModulesProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.NO_WARP_MODULES, 0x5a83dc20c89c6398L, 47534);
}
@Test
public void verticalStepProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.VERTICAL_STEP, 0xac0413031ed65b61L, 48011);
}
@Test
public void latticeProfileMatchesGoldenWriteSequence() throws Exception {
assertScenario(Scenario.LATTICE, 0xc7ffab155e6bdef9L, 72714);
}
@Test
public void floorFluidProfilesSharingChunksMatchGoldenWriteSequence() throws Exception {
assertScenario(Scenario.FLOOR_FLUID_LAYERED, 0xb4c0fb23e071617dL, 68747);
}
@Test
public void cellKernelDecidesCellsWithoutColumnEvaluation() throws Exception {
IrisCaveCarver3D.CellCounters before = IrisCaveCarver3D.cellCounters();
run(Scenario.WARP_MODULES, false);
IrisCaveCarver3D.CellCounters after = IrisCaveCarver3D.cellCounters();
long decided = after.decided() - before.decided();
long evaluated = after.evaluated() - before.evaluated();
assertTrue("decided=" + decided + " evaluated=" + evaluated, decided > 0L && evaluated > 0L);
}
private void assertScenario(Scenario scenario, long expectedHash, int expectedWrites) throws Exception {
Result serial = run(scenario, false);
Result parallel = run(scenario, true);
assertTrue(serial.carved > 0);
assertEquals(serial.hash, parallel.hash);
assertEquals(serial.writes, parallel.writes);
assertEquals(serial.carved, parallel.carved);
assertEquals(expectedHash, serial.hash);
assertEquals(expectedWrites, serial.writes);
}
private Result run(Scenario scenario, boolean parallel) throws Exception {
if (!parallel) {
return carveArea(scenario);
}
ForkJoinPool pool = new ForkJoinPool(4);
try {
return pool.submit(() -> carveArea(scenario)).get(120, TimeUnit.SECONDS);
} finally {
pool.shutdownNow();
}
}
private Result carveArea(Scenario scenario) {
Engine engine = createEngine(scenario);
CaveDensityGrid grid = new CaveDensityGrid(4096);
IrisCaveProfile[] profiles = scenario.profiles();
IrisCaveCarver3D[] carvers = new IrisCaveCarver3D[profiles.length];
for (int index = 0; index < profiles.length; index++) {
carvers[index] = new IrisCaveCarver3D(engine, profiles[index], grid);
}
Capture capture = new Capture();
int carved = 0;
for (int chunkX = -2; chunkX < CHUNK_SPAN - 2; chunkX++) {
for (int chunkZ = -1; chunkZ < CHUNK_SPAN - 1; chunkZ++) {
MantleWriter writer = capture.writer(chunkX, chunkZ);
CaveFluidSupportPlan supportPlan = new CaveFluidSupportPlan();
for (int index = 0; index < carvers.length; index++) {
carved += carvers[index].carve(writer, chunkX, chunkZ, scenario.weights(index, chunkX, chunkZ),
0.08D, 0.24D, scenario.worldYRange(), scenario.surfaceHeights(chunkX, chunkZ),
scenario.boundaries(chunkX, chunkZ), null, supportPlan);
}
capture.resolving = true;
supportPlan.resolve(writer.acquireChunk(chunkX, chunkZ));
capture.finishResolve();
}
}
return new Result(capture.hash, capture.writes, carved);
}
private Engine createEngine(Scenario scenario) {
Engine engine = mock(Engine.class, withSettings().stubOnly());
IrisData data = mock(IrisData.class, withSettings().stubOnly());
IrisDimension dimension = mock(IrisDimension.class, withSettings().stubOnly());
SeedManager seedManager = mock(SeedManager.class, withSettings().stubOnly());
doReturn(data).when(engine).getData();
doReturn(dimension).when(engine).getDimension();
doReturn(seedManager).when(engine).getSeedManager();
doReturn(CARVE_SEED).when(seedManager).getCarve();
doReturn(new EngineMetrics(16)).when(engine).getMetrics();
doReturn(IrisWorld.builder().minHeight(-32).maxHeight(WORLD_HEIGHT - 32).build()).when(engine).getWorld();
doReturn(WORLD_HEIGHT).when(engine).getHeight();
doReturn(110).when(engine).getHeight(anyInt(), anyInt());
doReturn(24).when(dimension).getCaveLavaHeight();
doReturn(70).when(dimension).getFluidHeight();
doReturn(scenario == Scenario.VERTICAL_STEP ? 4 : 1).when(dimension).getCaveDensitySamplingStep();
return engine;
}
private static IrisCaveProfile baseProfile(boolean warp, boolean modules) {
IrisCaveProfile profile = new IrisCaveProfile();
profile.setEnabled(true);
profile.setVerticalRange(new IrisRange(4D, 150D));
profile.setVerticalEdgeFade(14);
profile.setVerticalEdgeFadeStrength(0.21D);
profile.setBaseDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.21D));
profile.setDetailDensityStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.43D));
profile.setWarpStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.37D));
profile.setSurfaceBreakStyle(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.140625D));
profile.setBaseWeight(1D);
profile.setDetailWeight(0.42D);
profile.setWarpStrength(warp ? 3.7D : 0D);
profile.setDensityThreshold(new IrisStyledRange(-0.18D, -0.18D, new IrisGeneratorStyle(NoiseStyle.FLAT)));
profile.setThresholdBias(0D);
profile.setSampleStep(1);
profile.setSurfaceClearance(5);
profile.setAllowSurfaceBreak(true);
profile.setSurfaceBreakNoiseThreshold(0.3D);
profile.setSurfaceBreakDepth(14);
profile.setSurfaceBreakThresholdBoost(0.21D);
profile.setAllowFluid(false);
profile.setAllowLava(true);
KList<IrisCaveFieldModule> caveModules = new KList<>();
if (modules) {
caveModules.add(new IrisCaveFieldModule(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.33D),
0.27D, 0.05D, new IrisRange(0D, 90D), false));
caveModules.add(new IrisCaveFieldModule(new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.51D),
0.19D, -0.07D, new IrisRange(40D, 150D), true));
}
profile.setModules(caveModules);
return profile;
}
private enum Scenario {
WARP_MODULES,
WARP_TERRAIN,
STYLED_BLEND,
FLUID_BOUNDARY,
NO_WARP_MODULES,
VERTICAL_STEP,
LATTICE,
FLOOR_FLUID_LAYERED;
IrisCaveProfile[] profiles() {
if (this == FLOOR_FLUID_LAYERED) {
return new IrisCaveProfile[]{
baseProfile(true, true).setAllowFluid(true).setFluidRequiresFloor(true).setFluidMinDepthBelowSurface(4),
baseProfile(true, false).setAllowFluid(true).setFluidRequiresFloor(true).setFluidMinDepthBelowSurface(2)
.setAllowLava(false).setDensityThreshold(new IrisStyledRange(-0.24D, -0.08D,
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.45D)))
};
}
return new IrisCaveProfile[]{profile()};
}
IrisCaveProfile profile() {
return switch (this) {
case WARP_MODULES, VERTICAL_STEP -> baseProfile(true, true);
case WARP_TERRAIN -> baseProfile(true, false);
case STYLED_BLEND -> baseProfile(false, false).setDensityThreshold(new IrisStyledRange(-0.32D, -0.04D,
new IrisGeneratorStyle(NoiseStyle.SIMPLEX).zoomed(0.6D)));
case FLUID_BOUNDARY -> baseProfile(true, true).setAllowFluid(true).setFluidRequiresFloor(false)
.setFluidMinDepthBelowSurface(6).setAllowLava(false);
case NO_WARP_MODULES -> baseProfile(false, true);
case LATTICE -> baseProfile(true, true).setSampleStep(3);
case FLOOR_FLUID_LAYERED -> baseProfile(true, true);
};
}
double[] weights(int layer, int chunkX, int chunkZ) {
double[] weights = new double[256];
for (int index = 0; index < 256; index++) {
int worldX = (chunkX << 4) + (index >> 4);
int worldZ = (chunkZ << 4) + (index & 15);
double blend = 0.5D + 0.6D * Math.sin(worldX * 0.11D) * Math.cos(worldZ * 0.07D);
weights[index] = switch (this) {
case STYLED_BLEND, FLUID_BOUNDARY -> blend;
case FLOOR_FLUID_LAYERED -> layer == 0 ? blend : 1D - blend;
default -> 1D;
};
}
return weights;
}
int[] surfaceHeights(int chunkX, int chunkZ) {
int[] heights = new int[256];
for (int index = 0; index < 256; index++) {
int worldX = (chunkX << 4) + (index >> 4);
int worldZ = (chunkZ << 4) + (index & 15);
heights[index] = this == WARP_MODULES || this == LATTICE || this == FLOOR_FLUID_LAYERED
? 118
: 96 + (int) Math.round(22D * Math.sin(worldX * 0.09D) + 15D * Math.cos(worldZ * 0.13D));
}
return heights;
}
IrisRange worldYRange() {
return this == FLUID_BOUNDARY || this == FLOOR_FLUID_LAYERED ? new IrisRange(-20D, 100D) : null;
}
long[] boundaries(int chunkX, int chunkZ) {
long[] boundaries = new long[256];
Arrays.fill(boundaries, SurfaceFluidBoundaryPlan.boundary(SurfaceFluidBoundaryPlan.NO_BOUNDARY, Integer.MIN_VALUE));
if (this != FLUID_BOUNDARY && this != WARP_TERRAIN) {
return boundaries;
}
for (int index = 0; index < 256; index++) {
int worldX = (chunkX << 4) + (index >> 4);
if (Math.floorMod(worldX + chunkZ, 5) == 0) {
boundaries[index] = SurfaceFluidBoundaryPlan.boundary(60 + Math.floorMod(index, 7), 82);
}
}
return boundaries;
}
}
private record Result(long hash, int writes, int carved) {
}
private static final class Capture {
private final Map<Long, Map<Integer, Matter>> chunks = new HashMap<>();
private final IdentityHashMap<MatterCavern, Integer> identities = new IdentityHashMap<>();
private final List<Long> resolved = new ArrayList<>();
private long hash = 0xcbf29ce484222325L;
private int writes;
private boolean resolving;
private MantleWriter writer(int chunkX, int chunkZ) {
MantleWriter writer = mock(MantleWriter.class, withSettings().stubOnly());
@SuppressWarnings("unchecked")
Mantle<Matter> mantle = mock(Mantle.class, withSettings().stubOnly());
@SuppressWarnings("unchecked")
MantleChunk<Matter> chunk = mock(MantleChunk.class, withSettings().stubOnly());
long key = ((long) chunkX << 32) ^ (chunkZ & 0xffffffffL);
Map<Integer, Matter> sections = chunks.computeIfAbsent(key, ignored -> new HashMap<>());
doReturn(mantle).when(writer).getMantle();
doReturn(WORLD_HEIGHT).when(mantle).getWorldHeight();
doReturn(chunk).when(writer).acquireChunk(anyInt(), anyInt());
doAnswer(invocation -> sections.get(invocation.getArgument(0, Integer.class))).when(chunk).get(anyInt());
doAnswer(invocation -> sections.computeIfAbsent(invocation.getArgument(0, Integer.class),
sectionIndex -> createSection(chunkX, chunkZ, sectionIndex))).when(chunk).getOrCreate(anyInt());
return writer;
}
private Matter createSection(int chunkX, int chunkZ, int sectionIndex) {
mix(0x5EC7L);
mix(chunkX);
mix(chunkZ);
mix(sectionIndex);
Matter matter = mock(Matter.class, withSettings().stubOnly());
MatterSlice<MatterCavern> slice = new RecordingCavernMatter(this, sectionIndex);
doReturn(slice).when(matter).slice(MatterCavern.class);
doReturn(slice).when(matter).getSlice(MatterCavern.class);
return matter;
}
private void record(int sectionIndex, int x, int y, int z, MatterCavern value) {
writes++;
if (resolving) {
resolved.add(((long) sectionIndex << 20) | ((long) x << 16) | ((long) y << 12) | ((long) z << 8) | value.getLiquid());
return;
}
mix(sectionIndex);
mix((x << 8) | (y << 4) | z);
mix(value == null ? -1 : value.getLiquid());
Integer identity = identities.get(value);
if (identity == null) {
identity = identities.size();
identities.put(value, identity);
}
mix(identity);
}
private void finishResolve() {
resolving = false;
resolved.sort(Long::compare);
for (long entry : resolved) {
mix(entry);
}
resolved.clear();
}
private void mix(long value) {
hash ^= value;
hash *= 0x100000001b3L;
}
}
private static final class RecordingCavernMatter extends CavernMatter {
private final Capture capture;
private final int sectionIndex;
private RecordingCavernMatter(Capture capture, int sectionIndex) {
super(16, 16, 16);
this.capture = capture;
this.sectionIndex = sectionIndex;
}
@Override
public void set(int x, int y, int z, MatterCavern value) {
super.set(x, y, z, value);
capture.record(sectionIndex, x, y, z, value);
}
}
}