River world hooks

- Skip nib, pothole, wall and slab post passes on river footprints and their collar
- Keep automatic object scatter off the channel and shore band
- Expose deeper biome layers on eroded banks and use the bank biome on the blend band
- Give dry cave headroom the flooded cave biome
This commit is contained in:
Brian Neumann-Fopiano
2026-09-01 19:08:20 -04:00
parent 8a256e3226
commit 676372efab
8 changed files with 76 additions and 14 deletions
@@ -632,7 +632,7 @@ public class IrisComplex implements DataProvider {
if (layer.channel()) {
return 1D;
}
return layer.shore() ? 0.5D : 0.25D;
return layer.shore() ? 0.75D : 0.5D;
}
private double resolveHydrologyFluidSurface(double x, double z) {
@@ -658,6 +658,11 @@ public class IrisComplex implements DataProvider {
return surfaceFluidLayer(x, z) != null;
}
public boolean hasHydrologyChannelOrShore(int x, int z) {
HydrologyColumnLayer layer = surfaceLayer(x, z);
return layer != null && (layer.channel() || layer.shore());
}
public ProceduralStream<IrisBiome> getBiomeStream(InferredType type) {
switch (type) {
case CAVE:
@@ -732,7 +737,7 @@ public class IrisComplex implements DataProvider {
throw new IllegalStateException("Hydrology biome does not exist: " + biomeKey);
}
if (layer.biomeKey() == null) {
return biome;
return implode(biome.withInferredType(InferredType.LAND), x, z);
}
InferredType inferredType = layer.shore()
? InferredType.SHORE
@@ -103,6 +103,8 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
KList<IrisOreGenerator> dimensionUndergroundOres = hideOres ? null : dimension.getUndergroundOreGenerators();
IrisOreGeneratorBounds dimensionSurfaceOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : dimension.getSurfaceOreGeneratorBounds();
IrisOreGeneratorBounds dimensionUndergroundOreBounds = hideOres ? IrisOreGeneratorBounds.EMPTY : dimension.getUndergroundOreGeneratorBounds();
boolean exposeCutStrata = dimension.getHydrology() != null
&& dimension.getHydrology().getRivers().getSurface().getBanks().isExposeCutStrata();
for (int zf = 0; zf < chunkDepth; zf++) {
int realZ = zf + z;
@@ -125,6 +127,15 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
HydrologyColumnLayer hydrologyFluid = hydrology == null
? null
: hydrology.primarySurfaceFluidLayer().orElse(null);
HydrologyColumnLayer hydrologyTerrain = hydrology == null
? null
: hydrology.primarySurfaceLayer().orElse(null);
int cut = exposeCutStrata
&& hydrologyTerrain != null
&& hydrologyTerrain.terrainOwned()
&& !hydrologyTerrain.channel()
? Math.max(0, hydrology.naturalHeight() - he)
: 0;
PlatformBlockState fluid = hydrologyFluid == null
? complex.resolveSurfaceFluid(realX, realZ)
: complex.resolveHydrologyFluid(hydrologyFluid.profileKey(), realX, realZ);
@@ -188,11 +199,11 @@ public class IrisTerrainNormalActuator extends EngineAssignedActuator<PlatformBl
continue;
}
if (blocks == null) {
blocks = biome.generateLayers(dimension, realX, realZ, localRng, he, he, data, complex);
blocks = biome.generateLayers(dimension, realX, realZ, localRng, he + cut, he + cut, data, complex);
}
if (blocks.hasIndex(depth)) {
h.setRaw(xf, i, zf, blocks.get(depth));
if (blocks.hasIndex(depth + cut)) {
h.setRaw(xf, i, zf, blocks.get(depth + cut));
continue;
}
@@ -56,7 +56,7 @@ public record HydrologyColumnLayer(
public String biomeKey() {
if (grading && !channel && !shore) {
return null;
return bankBiomeKey;
}
if (shore) {
return shoreBiomeKey;
@@ -326,7 +326,7 @@ public final class MantleHydrologyComponent extends IrisMantleComponent {
+ position + ".");
}
String biomeKey = action == HydrologyCaveAction.DRY_AIR
? layer.bankBiomeKey()
? layer.floodedCaveBiomeKey()
: layer.floodedCaveBiomeKey();
PlannedCell candidate = new PlannedCell(
new HydrologyCaveCell(action, layer.profileKey(), biomeKey),
@@ -28,6 +28,7 @@ import art.arcane.iris.engine.object.IrisSlopeClip;
import art.arcane.iris.util.project.context.ChunkContext;
import art.arcane.iris.util.common.data.B;
import art.arcane.iris.util.project.hunk.Hunk;
import art.arcane.iris.util.project.stream.ProceduralStream;
import art.arcane.volmlib.util.math.RNG;
import art.arcane.volmlib.util.scheduling.PrecisionStopwatch;
import art.arcane.iris.spi.PlatformBlockState;
@@ -71,6 +72,26 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
* 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();
@@ -100,6 +121,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
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);
// Floating Nibs
int g = 0;
@@ -113,7 +135,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
g += hc < h - 1 ? 1 : 0;
g += hd < h - 1 ? 1 : 0;
if (g == 4 && isAir(x, h - 1, z, originX, originZ, currentData)) {
if (!river && g == 4 && isAir(x, h - 1, z, originX, originZ, currentData)) {
setPostBlock(x, h, z, States.AIR, originX, originZ, currentData);
for (int i = h - 1; i > 0; i--) {
@@ -131,6 +153,10 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
g += hc == h - 1 ? 1 : 0;
g += hd == h - 1 ? 1 : 0;
if (river) {
g = -1;
}
if (g >= 4) {
PlatformBlockState bcState = getPostBlock(x, h, z, originX, originZ, currentData);
PlatformBlockState bState = getPostBlock(x, h + 1, z, originX, originZ, currentData);
@@ -141,7 +167,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
h--;
}
}
} else {
} else if (!river) {
// Potholes
g = 0;
g += ha == h + 1 ? 1 : 0;
@@ -170,7 +196,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
// Wall Patcher
IrisBiome biome = context.getBiome().get(currentPostX, currentPostZ);
if (walls) {
if (walls && !river) {
if (!biome.getWall().getPalette().isEmpty()) {
if (ha < h - 2 || hb < h - 2 || hc < h - 2 || hd < h - 2) {
boolean brokeGround = false;
@@ -197,7 +223,7 @@ public class IrisPostModifier extends EngineAssignedModifier<PlatformBlockState>
}
// Slab
if (slabs) {
if (slabs && !river) {
//@builder
if ((ha == h + 1 && isSolidNonSlab(x + 1, ha, z, originX, originZ, currentData))
|| (hb == h + 1 && isSolidNonSlab(x, hb, z + 1, originX, originZ, currentData))
@@ -205,7 +205,7 @@ public final class IrisSurfaceSupport {
}
int columnX = x + minX + stencilX;
int columnZ = z + minZ + stencilZ;
if (complex.hasHydrologySurfaceFluid(columnX, columnZ)) {
if (complex.hasHydrologyChannelOrShore(columnX, columnZ)) {
return true;
}
}
@@ -21,7 +21,7 @@ import static org.mockito.Mockito.mock;
public class IrisComplexSurfaceBiomeTest {
@Test
public void gradingFootprintKeepsItsExactNaturalParentBiome() {
public void gradingFootprintUsesTheBankBiome() {
HydrologyFeatureRef feature = new HydrologyFeatureRef(
1L,
HydrologyFeatureType.SURFACE_POOL,
@@ -65,7 +65,7 @@ public class IrisComplexSurfaceBiomeTest {
List.of(grading)
);
assertEquals("exact_parent", IrisComplex.hydrologySurfaceBiomeKey(sample));
assertEquals("dry", IrisComplex.hydrologySurfaceBiomeKey(sample));
}
@Test
@@ -0,0 +1,20 @@
package art.arcane.iris.engine.modifier;
import org.junit.Test;
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}));
}
@Test
public void columnsAwayFromRiversKeepThePostPasses() {
assertFalse(IrisPostModifier.skipsHydrologyColumn(new double[] {0D, 0D, 0D, 0D, 0D}));
}
}