Merged river lookup batching

- Post processing reuses river-weight samples across neighboring columns
This commit is contained in:
Brian Neumann-Fopiano
2026-10-13 11:07:22 -04:00
4 changed files with 100 additions and 31 deletions
@@ -54,43 +54,44 @@ public class IrisPostModifier extends EngineAssignedModifier<NativeBlockState> {
int depth = output.getDepth();
int planeWidth = width + 2;
int[] heights = heightPlane(x, z, width, depth, planeWidth);
boolean[] rivers = riverPlane(getComplex().getRiverCarveWeightStream(), x, z, width, depth);
IrisDimension dimension = getDimension();
boolean walls = dimension.isPostProcessingWalls();
boolean slabs = dimension.isPostProcessingSlabs();
for (int i = 0; i < width; i++) {
for (int j = 0; j < depth; j++) {
post(i, j, sync, i + x, j + z, context, heights, planeWidth, walls, slabs);
post(i, j, sync, i + x, j + z, context, heights, rivers, planeWidth, walls, slabs);
}
}
getEngine().getMetrics().getPost().put(p.getMilliseconds());
}
static boolean[] riverPlane(ProceduralStream<Double> carveWeights, int x, int z, int width, int depth) {
int planeWidth = width + 2;
boolean[] rivers = new boolean[planeWidth * (depth + 2)];
for (int j = -1; j <= depth; j++) {
boolean edge = j == -1 || j == depth;
int from = edge ? 0 : -1;
int to = edge ? width - 1 : width;
int row = (j + 1) * planeWidth;
for (int i = from; i <= to; i++) {
rivers[row + i + 1] = carveWeights.get(x + i, z + j) > 0D;
}
}
return rivers;
}
static boolean skipsHydrologyColumn(boolean[] rivers, int center, int planeWidth) {
return rivers[center] || rivers[center + 1] || rivers[center - 1]
|| rivers[center + planeWidth] || rivers[center - planeWidth];
}
/**
* Every column reads its own height plus its four neighbours, so adjacent columns would otherwise
* resolve the same height stream entry up to five times. Resolve the padded plane once instead. The
* four diagonal corners are never read, so they are left unresolved.
*/
private boolean riverColumn(int x, int z) {
ProceduralStream<Double> carveWeights = getComplex().getRiverCarveWeightStream();
return skipsHydrologyColumn(new double[] {
carveWeights.get(x, z),
carveWeights.get(x + 1, z),
carveWeights.get(x - 1, z),
carveWeights.get(x, z + 1),
carveWeights.get(x, z - 1)
});
}
static boolean skipsHydrologyColumn(double[] carveWeights) {
for (double weight : carveWeights) {
if (weight > 0D) {
return true;
}
}
return false;
}
private int[] heightPlane(int x, int z, int width, int depth, int planeWidth) {
int[] heights = new int[planeWidth * (depth + 2)];
EngineMantle mantle = getEngine().getMantle();
@@ -109,7 +110,7 @@ public class IrisPostModifier extends EngineAssignedModifier<NativeBlockState> {
return heights;
}
private void post(int currentPostX, int currentPostZ, Hunk<NativeBlockState> currentData, int x, int z, ChunkContext context, int[] heights, int planeWidth, boolean walls, boolean slabs) {
private void post(int currentPostX, int currentPostZ, Hunk<NativeBlockState> currentData, int x, int z, ChunkContext context, int[] heights, boolean[] rivers, int planeWidth, boolean walls, boolean slabs) {
// x/z are world coordinates, the hunk is indexed relative to this chunk origin.
int originX = x - currentPostX;
int originZ = z - currentPostZ;
@@ -120,7 +121,7 @@ public class IrisPostModifier extends EngineAssignedModifier<NativeBlockState> {
int hc = heights[center - 1];
int hd = heights[center - planeWidth];
int fluidHeight = (int) Math.round(getComplex().getRiverWaterSurfaceStream().get(x, z));
boolean river = riverColumn(x, z);
boolean river = skipsHydrologyColumn(rivers, center, planeWidth);
// Floating Nibs
int g = 0;
@@ -1,20 +1,87 @@
package art.arcane.iris.generation.stage;
import art.arcane.volmlib.util.stream.ProceduralStream;
import org.junit.Test;
import java.util.HashMap;
import java.util.Map;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
public class IrisPostModifierHydrologyTest {
@Test
public void columnsTouchingARiverFootprintSkipThePostPasses() {
assertTrue(IrisPostModifier.skipsHydrologyColumn(new double[] {1D, 0D, 0D, 0D, 0D}));
assertTrue(IrisPostModifier.skipsHydrologyColumn(new double[] {0D, 0D, 0.5D, 0D, 0D}));
assertTrue(IrisPostModifier.skipsHydrologyColumn(new double[] {0D, 0D, 0D, 0D, 0.75D}));
for (int index : new int[] {4, 3, 5, 1, 7}) {
boolean[] rivers = new boolean[9];
rivers[index] = true;
assertTrue(IrisPostModifier.skipsHydrologyColumn(rivers, 4, 3));
}
}
@Test
public void columnsAwayFromRiversKeepThePostPasses() {
assertFalse(IrisPostModifier.skipsHydrologyColumn(new double[] {0D, 0D, 0D, 0D, 0D}));
boolean[] rivers = new boolean[9];
rivers[0] = true;
rivers[2] = true;
rivers[6] = true;
rivers[8] = true;
assertFalse(IrisPostModifier.skipsHydrologyColumn(rivers, 4, 3));
}
@Test
public void paddedPlaneSamplesEachRequiredCoordinateOnce() {
Map<Long, Integer> samples = new HashMap<>();
ProceduralStream<Double> stream = ProceduralStream.ofDouble((x, z) -> {
long key = ((long) (int) x << 32) | ((int) z & 0xffffffffL);
samples.merge(key, 1, Integer::sum);
return weight((int) x, (int) z);
});
boolean[] rivers = IrisPostModifier.riverPlane(stream, -32, 48, 16, 16);
assertEquals(320, samples.size());
for (int count : samples.values()) {
assertEquals(1, count);
}
assertPlaneMatchesPointSampling(rivers, -32, 48, 16, 16);
}
@Test
public void rectangularPlanesPreservePositiveWeightRules() {
ProceduralStream<Double> stream = ProceduralStream.ofDouble((x, z) -> weight((int) x, (int) z));
for (int width : new int[] {1, 3, 16}) {
for (int depth : new int[] {1, 5, 16}) {
boolean[] rivers = IrisPostModifier.riverPlane(stream, -19, -37, width, depth);
assertPlaneMatchesPointSampling(rivers, -19, -37, width, depth);
}
}
}
private static void assertPlaneMatchesPointSampling(boolean[] rivers, int x, int z, int width, int depth) {
int planeWidth = width + 2;
for (int localX = 0; localX < width; localX++) {
for (int localZ = 0; localZ < depth; localZ++) {
int worldX = x + localX;
int worldZ = z + localZ;
boolean expected = weight(worldX, worldZ) > 0D || weight(worldX + 1, worldZ) > 0D
|| weight(worldX - 1, worldZ) > 0D || weight(worldX, worldZ + 1) > 0D
|| weight(worldX, worldZ - 1) > 0D;
assertEquals(expected, IrisPostModifier.skipsHydrologyColumn(rivers,
(localZ + 1) * planeWidth + localX + 1, planeWidth));
}
}
}
private static double weight(int x, int z) {
return switch (Math.floorMod(x * 31 + z * 17, 8)) {
case 0 -> Double.NaN;
case 1 -> -1D;
case 2 -> 0D;
case 3 -> -0D;
case 4 -> Double.MIN_VALUE;
case 5 -> 1D;
case 6 -> Double.POSITIVE_INFINITY;
default -> Double.NEGATIVE_INFINITY;
};
}
}
@@ -91,9 +91,9 @@ public class IrisPostModifierSlabSupportTest {
heights[2 * PLANE_WIDTH + 3] = SURFACE + 1;
Method post = IrisPostModifier.class.getDeclaredMethod("post", int.class, int.class, Hunk.class,
int.class, int.class, ChunkContext.class, int[].class, int.class, boolean.class, boolean.class);
int.class, int.class, ChunkContext.class, int[].class, boolean[].class, int.class, boolean.class, boolean.class);
post.setAccessible(true);
post.invoke(modifier, 1, 1, output, 1, 1, context, heights, PLANE_WIDTH, false, true);
post.invoke(modifier, 1, 1, output, 1, 1, context, heights, new boolean[heights.length], PLANE_WIDTH, false, true);
return output.get(1, SURFACE + 1, 1);
}
@@ -123,9 +123,10 @@ public class IrisPostModifierWallShoreTest {
heights[5] = SURFACE - 4;
Method post = IrisPostModifier.class.getDeclaredMethod("post", int.class, int.class, Hunk.class,
int.class, int.class, ChunkContext.class, int[].class, int.class, boolean.class, boolean.class);
int.class, int.class, ChunkContext.class, int[].class, boolean[].class, int.class, boolean.class, boolean.class);
post.setAccessible(true);
post.invoke(modifier, 0, 0, data, 0, 0, context, heights, PLANE_WIDTH, walls, false);
boolean[] rivers = IrisPostModifier.riverPlane(carveWeights, 0, 0, PLANE_WIDTH - 2, PLANE_WIDTH - 2);
post.invoke(modifier, 0, 0, data, 0, 0, context, heights, rivers, PLANE_WIDTH, walls, false);
return data.get(0, SURFACE, 0);
}