Deferred cave pool terrain

- Opened the cave pool terrain view only for chunks that draw a pool site
- Added a test that chunks without a pool site never open their caves
This commit is contained in:
Brian Neumann-Fopiano committed 2026-10-30 20:09:02 -04:00
1 parent 3c39092cc7
commit 1899de7f3d
4 files changed
+36 -10

No files matched your search

@@ -10,6 +10,7 @@ import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.Arrays; import java.util.Arrays;
import java.util.Set; import java.util.Set;
import java.util.function.Supplier;
public final class CavePoolFiller { public final class CavePoolFiller {
public static final int REACH = HydrologySettings.MAX_CAVE_POOL_RADIUS + 1; public static final int REACH = HydrologySettings.MAX_CAVE_POOL_RADIUS + 1;
@@ -40,15 +41,19 @@ public final class CavePoolFiller {
this.density = settings.density(); this.density = settings.density();
} }
public void fill(int chunkX, int chunkZ, CarvedTerrain carved, MantleWriter writer, long seed) { public void fill(int chunkX, int chunkZ, Supplier<? extends CarvedTerrain> terrain, MantleWriter writer, long seed) {
if (!pools.enabled() || density == 0f) { if (!pools.enabled() || density == 0f) {
return; return;
} }
int attempts = attempts(seed, chunkX, chunkZ); int attempts = attempts(seed, chunkX, chunkZ);
CarvedTerrain carved = null;
for (int attempt = 0; attempt < attempts; attempt++) { for (int attempt = 0; attempt < attempts; attempt++) {
if (density < 1f && CellHash.unit(CellHash.mix(seed, SALT_DENSITY + attempt, chunkX, chunkZ)) >= density) { if (density < 1f && CellHash.unit(CellHash.mix(seed, SALT_DENSITY + attempt, chunkX, chunkZ)) >= density) {
continue; continue;
} }
if (carved == null) {
carved = terrain.get();
}
long column = CellHash.mix(seed, SALT_COLUMN + attempt, chunkX, chunkZ); long column = CellHash.mix(seed, SALT_COLUMN + attempt, chunkX, chunkZ);
int x = (chunkX << 4) + (int) (column & 15); int x = (chunkX << 4) + (int) (column & 15);
int z = (chunkZ << 4) + (int) ((column >>> 4) & 15); int z = (chunkZ << 4) + (int) ((column >>> 4) & 15);
@@ -85,8 +85,9 @@ public final class MantleHydrologyComponent extends IrisMantleComponent {
generateFalls(writer, complex, runtime, own, chunkX, chunkZ); generateFalls(writer, complex, runtime, own, chunkX, chunkZ);
if (poolsActive()) { if (poolsActive()) {
HydrologySettings settings = runtime.settings(); HydrologySettings settings = runtime.settings();
new CavePoolFiller(settings).fill(chunkX, chunkZ, Engine engine = getEngineMantle().getEngine();
new MantleCarvedTerrain(writer, complex, getEngineMantle().getEngine(), chunkX, chunkZ), writer, settings.seed()); new CavePoolFiller(settings).fill(chunkX, chunkZ, () -> new MantleCarvedTerrain(writer, complex, engine, chunkX, chunkZ), writer,
settings.seed());
} }
} }
@@ -15,6 +15,7 @@ import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer; import java.util.function.Consumer;
import java.util.function.IntFunction; import java.util.function.IntFunction;
@@ -50,18 +51,37 @@ public class CavePoolFillerTest {
return 0; return 0;
}); });
for (int chunkX = 0; chunkX < 1024; chunkX++) { for (int chunkX = 0; chunkX < 1024; chunkX++) {
filler.fill(chunkX, 0, carved, writer, SEED); filler.fill(chunkX, 0, () -> carved, writer, SEED);
} }
assertTrue("half density samples approximately half the candidate columns", columns.size() > 3600 && columns.size() < 4500); assertTrue("half density samples approximately half the candidate columns", columns.size() > 3600 && columns.size() < 4500);
Set<Long> repeated = new HashSet<>(columns); Set<Long> repeated = new HashSet<>(columns);
columns.clear(); columns.clear();
for (int chunkX = 1023; chunkX >= 0; chunkX--) { for (int chunkX = 1023; chunkX >= 0; chunkX--) {
filler.fill(chunkX, 0, carved, writer, SEED); filler.fill(chunkX, 0, () -> carved, writer, SEED);
} }
assertEquals(repeated, columns); assertEquals(repeated, columns);
verifyNoInteractions(writer); verifyNoInteractions(writer);
} }
@Test
public void chunksWithoutAPoolSiteNeverOpenTheirCaves() {
IrisHydrology config = new IrisHydrology().setDensity(0.5D);
config.getUnderground().getCavePools().setDensity(0.05D);
CavePoolFiller filler = new CavePoolFiller(HydrologySettings.compile(config, SEED, 63, 0, 384));
CarvedTerrain carved = mock(CarvedTerrain.class);
MantleWriter writer = mock(MantleWriter.class);
AtomicInteger opened = new AtomicInteger();
int chunks = 1024;
for (int chunkX = 0; chunkX < chunks; chunkX++) {
filler.fill(chunkX, 0, () -> {
opened.incrementAndGet();
return carved;
}, writer, SEED);
}
assertTrue("only chunks drawing a pool site open their caves, opened " + opened.get(), opened.get() > 0 && opened.get() < chunks / 8);
verifyNoInteractions(writer);
}
@Test @Test
public void zeroGlobalDensityDoesNotReadCaves() { public void zeroGlobalDensityDoesNotReadCaves() {
IrisHydrology config = new IrisHydrology().setDensity(0D); IrisHydrology config = new IrisHydrology().setDensity(0D);
@@ -69,7 +89,7 @@ public class CavePoolFillerTest {
CavePoolFiller filler = new CavePoolFiller(HydrologySettings.compile(config, SEED, 63, 0, 384)); CavePoolFiller filler = new CavePoolFiller(HydrologySettings.compile(config, SEED, 63, 0, 384));
CarvedTerrain carved = mock(CarvedTerrain.class); CarvedTerrain carved = mock(CarvedTerrain.class);
MantleWriter writer = mock(MantleWriter.class); MantleWriter writer = mock(MantleWriter.class);
filler.fill(0, 0, carved, writer, SEED); filler.fill(0, 0, () -> carved, writer, SEED);
verifyNoInteractions(carved, writer); verifyNoInteractions(carved, writer);
} }
@@ -143,10 +163,10 @@ public class CavePoolFillerTest {
MantleWriter forwardWriter = writer(forward); MantleWriter forwardWriter = writer(forward);
MantleWriter reverseWriter = writer(reverse); MantleWriter reverseWriter = writer(reverse);
for (int index = 0; index < 9; index++) { for (int index = 0; index < 9; index++) {
new CavePoolFiller(settings).fill(index % 3 - 1, index / 3 - 1, world, forwardWriter, SEED); new CavePoolFiller(settings).fill(index % 3 - 1, index / 3 - 1, () -> world, forwardWriter, SEED);
} }
for (int index = 8; index >= 0; index--) { for (int index = 8; index >= 0; index--) {
new CavePoolFiller(settings).fill(index % 3 - 1, index / 3 - 1, world, reverseWriter, SEED); new CavePoolFiller(settings).fill(index % 3 - 1, index / 3 - 1, () -> world, reverseWriter, SEED);
} }
assertFalse(forward.isEmpty()); assertFalse(forward.isEmpty());
@@ -284,7 +304,7 @@ public class CavePoolFillerTest {
private static Map<Long, HydrologyCaveCell> fill(HydrologySettings settings, VoxelWorld world, int chunkX, int chunkZ) { private static Map<Long, HydrologyCaveCell> fill(HydrologySettings settings, VoxelWorld world, int chunkX, int chunkZ) {
Map<Long, HydrologyCaveCell> written = new HashMap<>(); Map<Long, HydrologyCaveCell> written = new HashMap<>();
new CavePoolFiller(settings).fill(chunkX, chunkZ, world, writer(written), SEED); new CavePoolFiller(settings).fill(chunkX, chunkZ, () -> world, writer(written), SEED);
return written; return written;
} }
@@ -324,7 +324,7 @@ public class MantleCarvedTerrainTest {
for (SeaInlet inlet : complex.getHydrologyRuntime().inlets(0, chunkZ)) { for (SeaInlet inlet : complex.getHydrologyRuntime().inlets(0, chunkZ)) {
rasterizer.write(inlet, 0, chunkZ, writer, HydrologyCaveCell.SEA_FLUID, "", SeaInletRasterizer.SubterrainFluid.NOWHERE); rasterizer.write(inlet, 0, chunkZ, writer, HydrologyCaveCell.SEA_FLUID, "", SeaInletRasterizer.SubterrainFluid.NOWHERE);
} }
new CavePoolFiller(settings).fill(0, chunkZ, new MantleCarvedTerrain(writer, complex, engine, 0, chunkZ), writer, SEED); new CavePoolFiller(settings).fill(0, chunkZ, () -> new MantleCarvedTerrain(writer, complex, engine, 0, chunkZ), writer, SEED);
} }
return cells; return cells;
} }