diff --git a/core/src/main/java/art/arcane/iris/core/pack/PackHydrologyValidator.java b/core/src/main/java/art/arcane/iris/core/pack/PackHydrologyValidator.java index c6992b227..2024e65b4 100644 --- a/core/src/main/java/art/arcane/iris/core/pack/PackHydrologyValidator.java +++ b/core/src/main/java/art/arcane/iris/core/pack/PackHydrologyValidator.java @@ -48,7 +48,7 @@ final class PackHydrologyValidator { "surfaceBiomes", "mouthBiomes", "shoreBiomes", - "dryBiomes", + "bankBiomes", "floodedCaveBiomes" ); private static final List REGION_BIOME_FIELDS = List.of( diff --git a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyColumnLayer.java b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyColumnLayer.java index bb7400634..ca8d064f2 100644 --- a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyColumnLayer.java +++ b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyColumnLayer.java @@ -20,7 +20,7 @@ public record HydrologyColumnLayer( String surfaceBiomeKey, String mouthBiomeKey, String shoreBiomeKey, - String dryBiomeKey, + String bankBiomeKey, String floodedCaveBiomeKey ) { public HydrologyColumnLayer { @@ -29,7 +29,7 @@ public record HydrologyColumnLayer( surfaceBiomeKey = requireKey(surfaceBiomeKey, "surfaceBiomeKey"); mouthBiomeKey = requireKey(mouthBiomeKey, "mouthBiomeKey"); shoreBiomeKey = requireKey(shoreBiomeKey, "shoreBiomeKey"); - dryBiomeKey = requireKey(dryBiomeKey, "dryBiomeKey"); + bankBiomeKey = requireKey(bankBiomeKey, "bankBiomeKey"); floodedCaveBiomeKey = requireKey(floodedCaveBiomeKey, "floodedCaveBiomeKey"); if (channel && bedY > fluidHeadY) { throw new IllegalArgumentException("A channel bed cannot be above its fluid head."); diff --git a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyFootprintCompiler.java b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyFootprintCompiler.java index ad588b73a..6e376eaf6 100644 --- a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyFootprintCompiler.java +++ b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyFootprintCompiler.java @@ -788,7 +788,7 @@ final class HydrologyFootprintCompiler { layer.surfaceBiomeKey(), layer.mouthBiomeKey(), layer.shoreBiomeKey(), - layer.dryBiomeKey(), + layer.bankBiomeKey(), layer.floodedCaveBiomeKey() ); } @@ -2121,7 +2121,7 @@ final class HydrologyFootprintCompiler { layer.surfaceBiomeKey(), layer.mouthBiomeKey(), layer.shoreBiomeKey(), - layer.dryBiomeKey(), + layer.bankBiomeKey(), layer.floodedCaveBiomeKey() ); } @@ -2232,7 +2232,7 @@ final class HydrologyFootprintCompiler { terrain.surfaceBiomeKey(), terrain.mouthBiomeKey(), terrain.shoreBiomeKey(), - terrain.dryBiomeKey(), + terrain.bankBiomeKey(), terrain.floodedCaveBiomeKey() ); } @@ -2443,7 +2443,7 @@ final class HydrologyFootprintCompiler { terrain.surfaceBiomeKey(), terrain.mouthBiomeKey(), terrain.shoreBiomeKey(), - terrain.dryBiomeKey(), + terrain.bankBiomeKey(), terrain.floodedCaveBiomeKey() ); addLayer(columns, x, z, terrain, layer); @@ -3655,7 +3655,7 @@ final class HydrologyFootprintCompiler { first.surfaceBiomeKey(), first.mouthBiomeKey(), first.shoreBiomeKey(), - first.dryBiomeKey(), + first.bankBiomeKey(), first.floodedCaveBiomeKey() ); } diff --git a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettings.java b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettings.java index f53099695..ef2e6778c 100644 --- a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettings.java +++ b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettings.java @@ -12,7 +12,7 @@ public record HydrologyPlannerSettings( Geometry geometry, List deepFluids ) { - private static final long PLAN_FORMAT_REVISION = 5L; + private static final long PLAN_FORMAT_REVISION = 6L; private static final int MAXIMUM_CROSS_TILE_COLOR_PERIOD = 4; public HydrologyPlannerSettings { @@ -36,7 +36,7 @@ public record HydrologyPlannerSettings( return new HydrologyPlannerSettings( 63, new Routing(2048, 64, 8, 8192, 8192, new Branching(384, 192), 1.5D, 24D, 2D, 0.2D), - new Surface(true, surfaceSources, 4, 8, 1, 4, 3, 7, 6, 2, 10, 24, true, 192, 10), + new Surface(true, surfaceSources, 4, 8, 2, 4, 1, 1, 10, 1.5D, 4, 32, false, 1, 1, Banks.defaults()), new Hydraulics(80, 180, 1, 7, 24, 8), new Underground(true, undergroundSources, -48, 72, 3, 8, 1, 3, 6, 14, true), new Outlets( @@ -97,8 +97,9 @@ public record HydrologyPlannerSettings( int radius = 0; int routeDisplacement = Math.multiplyExact(routing.sampleSpacing(), 3); if (surface.enabled() && surface.sources().enabled()) { + int blendWidth = Math.max(surface.maximumTerrainBlendWidth(), surface.banks().maximumBlendWidth()); int surfaceRadius = (int) StrictMath.ceil( - surface.maximumWidth() / 2D + surface.shoreWidth() + surface.maximumTerrainBlendWidth() + surface.maximumWidth() / 2D + surface.shoreWidth() + blendWidth ); surfaceRadius = Math.max( surfaceRadius, @@ -228,11 +229,12 @@ public record HydrologyPlannerSettings( int maximumTerrainBlendWidth, boolean ridgeTunnelsEnabled, int maximumRidgeTunnelLength, - int ridgeTunnelHeadroom + int ridgeTunnelHeadroom, + Banks banks ) { public Surface { - if (sources == null) { - throw new IllegalArgumentException("Surface source settings are required."); + if (sources == null || banks == null) { + throw new IllegalArgumentException("Surface source and bank settings are required."); } requireRange(minimumWidth, maximumWidth, 1, 256, "surface width"); requireRange(minimumDepth, maximumDepth, 1, 64, "surface depth"); @@ -247,6 +249,37 @@ public record HydrologyPlannerSettings( } } + public record Banks( + int inset, + int freeboard, + double blendSlope, + int minimumBlendWidth, + int maximumBlendWidth, + double roughness, + int roughnessWavelength, + int cascadeRun, + int waterfallMinimumDrop, + double mouthFlareRatio, + boolean exposeCutStrata + ) { + public Banks { + if (inset < 1 || inset > 16 || freeboard < 1 || freeboard > 8 + || !Double.isFinite(blendSlope) || blendSlope < 1D || blendSlope > 16D + || minimumBlendWidth < 1 || maximumBlendWidth < minimumBlendWidth || maximumBlendWidth > 128 + || !Double.isFinite(roughness) || roughness < 0D || roughness > 1D + || roughnessWavelength < 3 || roughnessWavelength > 128 + || cascadeRun < 1 || cascadeRun > 16 + || waterfallMinimumDrop < 2 || waterfallMinimumDrop > 24 + || !Double.isFinite(mouthFlareRatio) || mouthFlareRatio < 1D || mouthFlareRatio > 4D) { + throw new IllegalArgumentException("Surface bank settings are invalid."); + } + } + + public static Banks defaults() { + return new Banks(1, 1, 3D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true); + } + } + public record Hydraulics( int minimumTargetPoolLength, int maximumTargetPoolLength, diff --git a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSample.java b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSample.java index 756254746..4f282689b 100644 --- a/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSample.java +++ b/core/src/main/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSample.java @@ -22,11 +22,12 @@ public record HydrologyTerrainSample( double depthMultiplier, double incisionMultiplier, double routingMultiplier, + double bankMultiplier, String parentBiomeKey, String surfaceBiomeKey, String mouthBiomeKey, String shoreBiomeKey, - String dryBiomeKey, + String bankBiomeKey, String floodedCaveBiomeKey, List preferredProfileKeys ) { @@ -39,6 +40,7 @@ public record HydrologyTerrainSample( requireFinitePositive(depthMultiplier, "depthMultiplier"); requireFiniteNonNegative(incisionMultiplier, "incisionMultiplier"); requireFiniteNonNegative(routingMultiplier, "routingMultiplier"); + requireFinitePositive(bankMultiplier, "bankMultiplier"); if (caveAvailable && (caveFluidY <= caveFloorY || caveFluidY >= naturalHeight)) { throw new IllegalArgumentException("Available cave fluid must be above its floor and below natural terrain."); } @@ -46,7 +48,7 @@ public record HydrologyTerrainSample( surfaceBiomeKey = normalizeKey(surfaceBiomeKey, parentBiomeKey); mouthBiomeKey = normalizeKey(mouthBiomeKey, surfaceBiomeKey); shoreBiomeKey = normalizeKey(shoreBiomeKey, parentBiomeKey); - dryBiomeKey = normalizeKey(dryBiomeKey, parentBiomeKey); + bankBiomeKey = normalizeKey(bankBiomeKey, parentBiomeKey); floodedCaveBiomeKey = normalizeKey(floodedCaveBiomeKey, surfaceBiomeKey); preferredProfileKeys = normalizeProfiles(preferredProfileKeys); } @@ -72,6 +74,7 @@ public record HydrologyTerrainSample( 1D, 1D, 1D, + 1D, parentBiomeKey, parentBiomeKey, parentBiomeKey, @@ -103,6 +106,7 @@ public record HydrologyTerrainSample( 1D, 1D, 1D, + 1D, parentBiomeKey, parentBiomeKey, parentBiomeKey, @@ -134,11 +138,12 @@ public record HydrologyTerrainSample( depthMultiplier, incisionMultiplier, routingMultiplier, + bankMultiplier, parentBiomeKey, surfaceBiomeKey, mouthBiomeKey, shoreBiomeKey, - dryBiomeKey, + bankBiomeKey, floodedCaveBiomeKey, preferredProfileKeys ); diff --git a/core/src/main/java/art/arcane/iris/engine/hydrology/policy/EffectiveRiverPolicy.java b/core/src/main/java/art/arcane/iris/engine/hydrology/policy/EffectiveRiverPolicy.java index c5c1a3bf7..48a4454e7 100644 --- a/core/src/main/java/art/arcane/iris/engine/hydrology/policy/EffectiveRiverPolicy.java +++ b/core/src/main/java/art/arcane/iris/engine/hydrology/policy/EffectiveRiverPolicy.java @@ -14,12 +14,13 @@ public record EffectiveRiverPolicy( List surfaceBiomes, List mouthBiomes, List shoreBiomes, - List dryBiomes, + List bankBiomes, List floodedCaveBiomes, double widthMultiplier, double depthMultiplier, double incisionMultiplier, - double routingMultiplier + double routingMultiplier, + double bankMultiplier ) { public EffectiveRiverPolicy { placement = Objects.requireNonNull(placement); @@ -28,7 +29,7 @@ public record EffectiveRiverPolicy( surfaceBiomes = List.copyOf(surfaceBiomes); mouthBiomes = List.copyOf(mouthBiomes); shoreBiomes = List.copyOf(shoreBiomes); - dryBiomes = List.copyOf(dryBiomes); + bankBiomes = List.copyOf(bankBiomes); floodedCaveBiomes = List.copyOf(floodedCaveBiomes); } diff --git a/core/src/main/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolver.java b/core/src/main/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolver.java index a55eba56c..a4eb89541 100644 --- a/core/src/main/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolver.java +++ b/core/src/main/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolver.java @@ -41,12 +41,13 @@ public final class RiverPolicyResolver { private List surfaceBiomes = List.of(); private List mouthBiomes = List.of(); private List shoreBiomes = List.of(); - private List dryBiomes = List.of(); + private List bankBiomes = List.of(); private List floodedCaveBiomes = List.of(); private double widthMultiplier = 1D; private double depthMultiplier = 1D; private double incisionMultiplier = 1D; private double routingMultiplier = 1D; + private double bankMultiplier = 1D; private void apply(IrisRiverPolicy policy) { if (policy == null) { @@ -73,8 +74,8 @@ public final class RiverPolicyResolver { if (policy.getShoreBiomes() != null) { shoreBiomes = List.copyOf(policy.getShoreBiomes()); } - if (policy.getDryBiomes() != null) { - dryBiomes = List.copyOf(policy.getDryBiomes()); + if (policy.getBankBiomes() != null) { + bankBiomes = List.copyOf(policy.getBankBiomes()); } if (policy.getFloodedCaveBiomes() != null) { floodedCaveBiomes = List.copyOf(policy.getFloodedCaveBiomes()); @@ -91,6 +92,9 @@ public final class RiverPolicyResolver { if (policy.getRoutingMultiplier() != null) { routingMultiplier = policy.getRoutingMultiplier(); } + if (policy.getBankMultiplier() != null) { + bankMultiplier = policy.getBankMultiplier(); + } } private EffectiveRiverPolicy build() { @@ -102,12 +106,13 @@ public final class RiverPolicyResolver { surfaceBiomes, mouthBiomes, shoreBiomes, - dryBiomes, + bankBiomes, floodedCaveBiomes, widthMultiplier, depthMultiplier, incisionMultiplier, - routingMultiplier + routingMultiplier, + bankMultiplier ); } } diff --git a/core/src/main/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntime.java b/core/src/main/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntime.java index f8f421cb7..0e53a6359 100644 --- a/core/src/main/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntime.java +++ b/core/src/main/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntime.java @@ -30,7 +30,6 @@ import art.arcane.iris.engine.object.IrisRiverGrottoConfig; import art.arcane.iris.engine.object.IrisRiverChannelShapeConfig; import art.arcane.iris.engine.object.IrisRiverDropShapeConfig; import art.arcane.iris.engine.object.IrisRiverGeometryConfig; -import art.arcane.iris.engine.object.IrisRiverHydraulicsConfig; import art.arcane.iris.engine.object.IrisRiverHydrology; import art.arcane.iris.engine.object.IrisRiverInlandOutlet; import art.arcane.iris.engine.object.IrisRiverMeanderConfig; @@ -38,7 +37,9 @@ import art.arcane.iris.engine.object.IrisRiverProfile; import art.arcane.iris.engine.object.IrisRiverRoutingConfig; import art.arcane.iris.engine.object.IrisRiverRoutingMode; import art.arcane.iris.engine.object.IrisStyledRange; +import art.arcane.iris.engine.object.IrisSurfaceRiverBankConfig; import art.arcane.iris.engine.object.IrisSurfaceRiverChannelConfig; +import art.arcane.iris.engine.object.IrisSurfaceRiverFlowConfig; import art.arcane.iris.engine.object.IrisSurfaceRiverConfig; import art.arcane.iris.engine.object.IrisSurfaceRiverSourceConfig; import art.arcane.iris.engine.object.IrisUndergroundRiverConfig; @@ -463,11 +464,12 @@ public final class IrisHydrologyRuntime implements AutoCloseable { policy.depthMultiplier(), policy.incisionMultiplier(), policy.routingMultiplier() * routingPreference, + policy.bankMultiplier(), parentBiomeKey, selectKey(policy.surfaceBiomes(), parentBiomeKey, biomePatchNoise, 1), selectKey(policy.mouthBiomes(), parentBiomeKey, biomePatchNoise, 2), selectKey(policy.shoreBiomes(), parentBiomeKey, biomePatchNoise, 3), - selectKey(policy.dryBiomes(), parentBiomeKey, biomePatchNoise, 4), + selectKey(policy.bankBiomes(), parentBiomeKey, biomePatchNoise, 4), selectKey(policy.floodedCaveBiomes(), parentBiomeKey, biomePatchNoise, 5), profiles ); @@ -556,21 +558,13 @@ public final class IrisHydrologyRuntime implements AutoCloseable { IrisRiverHydrology rivers = hydrology.getRivers(); IrisSurfaceRiverConfig surface = rivers.getSurface(); IrisSurfaceRiverChannelConfig channel = surface.getChannel(); - IrisRiverHydraulicsConfig hydraulics = surface.getHydraulics(); + IrisSurfaceRiverBankConfig banks = surface.getBanks(); IrisUndergroundRiverConfig underground = rivers.getUnderground(); ProceduralStream surfaceWidth = styledStream(context, channel.getWidth(), SURFACE_WIDTH_SALT); ProceduralStream surfaceDepth = styledStream(context, channel.getDepth(), SURFACE_DEPTH_SALT); - ProceduralStream surfaceInset = styledStream(context, channel.getSurfaceInset(), SURFACE_INSET_SALT); - ProceduralStream surfaceBlend = styledStream( - context, - channel.getTerrainBlendWidth(), - SURFACE_BLEND_SALT - ); - ProceduralStream targetPool = styledStream( - context, - hydraulics.getTargetPoolLength(), - TARGET_POOL_SALT - ); + ProceduralStream surfaceInset = ProceduralStream.ofDouble((x, z) -> (double) channel.getInset()); + ProceduralStream surfaceBlend = ProceduralStream.ofDouble((x, z) -> (double) banks.getMaximumBlendWidth()); + ProceduralStream targetPool = ProceduralStream.ofDouble((x, z) -> 128D); ProceduralStream undergroundLevel = styledStream( context, underground.getFluidLevel(), @@ -646,10 +640,12 @@ public final class IrisHydrologyRuntime implements AutoCloseable { IrisSurfaceRiverConfig surface = rivers.getSurface(); IrisSurfaceRiverSourceConfig surfaceSources = surface.getSources(); IrisSurfaceRiverChannelConfig channel = surface.getChannel(); - IrisRiverHydraulicsConfig hydraulics = surface.getHydraulics(); + IrisSurfaceRiverBankConfig banks = surface.getBanks(); + IrisSurfaceRiverFlowConfig flow = surface.getFlow(); IrisUndergroundRiverConfig underground = rivers.getUnderground(); IrisUndergroundRiverSourceConfig undergroundSources = underground.getSources(); int routeNodes = maximumRouteNodes(routing); + int refinementSpacing = refinementSpacing(routing.getSampleSpacing()); int minimumWorldY = dimension.getMinHeight(); boolean riversEnabled = rivers.isEnabled(); boolean surfaceEnabled = riversEnabled && surface.isEnabled(); @@ -657,17 +653,17 @@ public final class IrisHydrologyRuntime implements AutoCloseable { HydrologyPlannerSettings.Routing plannerRouting = new HydrologyPlannerSettings.Routing( routing.getTileSize(), routing.getSampleSpacing(), - routing.getRefinementSpacing(), + refinementSpacing, routeNodes, routing.getMaximumRouteLength(), new HydrologyPlannerSettings.Branching( - routing.getBranching().getMinimumSurfaceCourseLength(), - routing.getBranching().getMinimumUndergroundCourseLength() + routing.getMinimumSurfaceCourseLength(), + routing.getMinimumUndergroundCourseLength() ), - 1.5D, - 24D, - 2D, - 0.2D + routing.getValleyPreference(), + routing.getUphillPenalty(), + routing.getSlopePenalty(), + routing.getConfluenceAttraction() ); HydrologyPlannerSettings.Source plannerSurfaceSources = new HydrologyPlannerSettings.Source( surfaceEnabled, @@ -684,23 +680,36 @@ public final class IrisHydrologyRuntime implements AutoCloseable { maximumInt(channel.getWidth()), minimumInt(channel.getDepth()), maximumInt(channel.getDepth()), - minimumInt(channel.getSurfaceInset()), - maximumInt(channel.getSurfaceInset()), + channel.getInset(), + channel.getInset(), channel.getMaximumIncision(), - channel.getShoreWidth(), - minimumInt(channel.getTerrainBlendWidth()), - maximumInt(channel.getTerrainBlendWidth()), - surface.getRidgeTunnels().isEnabled(), - surface.getRidgeTunnels().getMaximumLength(), - surface.getRidgeTunnels().getHeadroom() + banks.getShoreWidth(), + banks.getMinimumBlendWidth(), + banks.getMaximumBlendWidth(), + false, + 1, + 1, + new HydrologyPlannerSettings.Banks( + channel.getInset(), + banks.getFreeboard(), + banks.getBlendSlope(), + banks.getMinimumBlendWidth(), + banks.getMaximumBlendWidth(), + channel.getRoughness(), + channel.getRoughnessWavelength(), + flow.getCascadeRun(), + flow.getWaterfallMinimumDrop(), + surface.getMouths().getFlareRatio(), + banks.isExposeCutStrata() + ) ); HydrologyPlannerSettings.Hydraulics plannerHydraulics = new HydrologyPlannerSettings.Hydraulics( - minimumInt(hydraulics.getTargetPoolLength()), - maximumInt(hydraulics.getTargetPoolLength()), - hydraulics.getRiffleDrop(), - hydraulics.getMaximumGradualDrop(), - hydraulics.getMaximumGradualLength(), - hydraulics.getWaterfallMinimumDrop() + 80, + 180, + 1, + flow.getWaterfallMinimumDrop() - 1, + 24, + flow.getWaterfallMinimumDrop() ); HydrologyPlannerSettings.Source plannerUndergroundSources = new HydrologyPlannerSettings.Source( undergroundEnabled, @@ -736,11 +745,19 @@ public final class IrisHydrologyRuntime implements AutoCloseable { inland.getHeadroom(), inland.getMaximumVolume()), inlandEnabled && inland.isConnectSurfaceRivers(), Math.max(4, coastal.getVerticalRadius()), - surface.getMouths().getLevelingDistance(), + underground.getMouthLevelingDistance(), surface.getMouths().getMaximumOceanApron(), routing.getMaximumOutletsPerTile() ); - HydrologyPlannerSettings.Geometry plannerGeometry = geometry(rivers.getGeometry()); + HydrologyPlannerSettings.Geometry plannerGeometry = geometry( + rivers.getGeometry(), + new HydrologyPlannerSettings.ChannelShape( + 2D, + channel.getRoughness(), + channel.getRoughness(), + channel.getRoughnessWavelength() + ) + ); return new HydrologyPlannerSettings( dimension.getFluidHeight(), plannerRouting, @@ -749,11 +766,21 @@ public final class IrisHydrologyRuntime implements AutoCloseable { plannerUnderground, plannerOutlets, plannerGeometry, - deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing) + deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, refinementSpacing) ); } - private static HydrologyPlannerSettings.Geometry geometry(IrisRiverGeometryConfig geometry) { + static int refinementSpacing(int sampleSpacing) { + if (sampleSpacing % 4 == 0) { + return 4; + } + return sampleSpacing % 2 == 0 ? 2 : 1; + } + + private static HydrologyPlannerSettings.Geometry geometry( + IrisRiverGeometryConfig geometry, + HydrologyPlannerSettings.ChannelShape surfaceShape + ) { IrisRiverMeanderConfig meanders = geometry.getMeanders(); IrisRiverDropShapeConfig drops = geometry.getDrops(); return new HydrologyPlannerSettings.Geometry( @@ -766,7 +793,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable { meanders.getSmoothingPasses(), meanders.getMaximumTurnDegrees() ), - channelShape(geometry.getSurface()), + surfaceShape, channelShape(geometry.getUnderground()), channelShape(geometry.getGrottos()), new HydrologyPlannerSettings.Drops( @@ -809,7 +836,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable { private static List deepFluids( List configurations, int minimumWorldY, - IrisRiverRoutingConfig routing + IrisRiverRoutingConfig routing, + int refinementSpacing ) { ArrayList deepFluids = new ArrayList<>(); for (IrisDeepFluidConfig configuration : configurations) { @@ -820,7 +848,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable { int maximumChannelLength = maximumDeepChannelLength(configuration, routing); int minimumChannelLength = maximumChannelLength == 0 ? 0 - : Math.min(maximumChannelLength, Math.max(routing.getRefinementSpacing(), radius)); + : Math.min(maximumChannelLength, Math.max(refinementSpacing, radius)); deepFluids.add(new HydrologyPlannerSettings.DeepFluid( configuration.getId(), configuration.getDensity() > 0D @@ -855,7 +883,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable { return 0; } int configuredLength = Math.max( - routing.getRefinementSpacing(), configuration.getSpacing() / 3); + refinementSpacing(routing.getSampleSpacing()), configuration.getSpacing() / 3); return Math.min(configuredLength, routing.getTileSize() / 2); } diff --git a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponent.java b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponent.java index 1d7a810ce..9b187fd1f 100644 --- a/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponent.java +++ b/core/src/main/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponent.java @@ -326,7 +326,7 @@ public final class MantleHydrologyComponent extends IrisMantleComponent { + position + "."); } String biomeKey = action == HydrologyCaveAction.DRY_AIR - ? layer.dryBiomeKey() + ? layer.bankBiomeKey() : layer.floodedCaveBiomeKey(); PlannedCell candidate = new PlannedCell( new HydrologyCaveCell(action, layer.profileKey(), biomeKey), diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverBranchingConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverBranchingConfig.java deleted file mode 100644 index 54e8d9dc4..000000000 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverBranchingConfig.java +++ /dev/null @@ -1,25 +0,0 @@ -package art.arcane.iris.engine.object; - -import art.arcane.iris.engine.object.annotations.Desc; -import art.arcane.iris.engine.object.annotations.MaxNumber; -import art.arcane.iris.engine.object.annotations.MinNumber; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -@Accessors(chain = true) -@NoArgsConstructor -@Desc("Controls minimum complete river lengths before a route is published.") -@Data -public class IrisRiverBranchingConfig { - @MinNumber(0) - @MaxNumber(32768) - @Desc("Minimum complete surface source-to-outlet route length in blocks.") - private int minimumSurfaceCourseLength = 384; - - @MinNumber(0) - @MaxNumber(32768) - @Desc("Minimum complete underground source-to-outlet route length in blocks.") - private int minimumUndergroundCourseLength = 192; - -} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverGeometryConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverGeometryConfig.java index 1b7d9840d..64d13b11a 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverGeometryConfig.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverGeometryConfig.java @@ -7,21 +7,18 @@ import lombok.experimental.Accessors; @Accessors(chain = true) @NoArgsConstructor -@Desc("Controls organic river routing, beds, carved walls, and descending water geometry.") +@Desc("Controls organic river routing and contained cave geometry.") @Data public class IrisRiverGeometryConfig { @Desc("Surface and underground centerline meanders.") private IrisRiverMeanderConfig meanders = new IrisRiverMeanderConfig(); - @Desc("Surface channel bed and bank shape.") - private IrisRiverChannelShapeConfig surface = new IrisRiverChannelShapeConfig(); - @Desc("Underground channel bed and wall shape.") private IrisRiverChannelShapeConfig underground = new IrisRiverChannelShapeConfig(); @Desc("Grotto pool bed and wall shape.") private IrisRiverChannelShapeConfig grottos = new IrisRiverChannelShapeConfig(); - @Desc("Cascade, cataract, waterfall, and sinkhole descent shape.") + @Desc("Underground drop, sinkhole and receiving basin shape.") private IrisRiverDropShapeConfig drops = new IrisRiverDropShapeConfig(); } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverHydraulicsConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverHydraulicsConfig.java deleted file mode 100644 index 212c66588..000000000 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverHydraulicsConfig.java +++ /dev/null @@ -1,41 +0,0 @@ -package art.arcane.iris.engine.object; - -import art.arcane.iris.engine.object.annotations.Desc; -import art.arcane.iris.engine.object.annotations.MaxNumber; -import art.arcane.iris.engine.object.annotations.MinNumber; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -@Accessors(chain = true) -@NoArgsConstructor -@Desc("Controls deterministic river head transitions and hydraulic segment classification.") -@Data -public class IrisRiverHydraulicsConfig { - @Desc("Target longitudinal length of level pools between head transitions.") - private IrisStyledRange targetPoolLength = range(80D, 180D, 1024D); - - @MinNumber(0) - @MaxNumber(16) - @Desc("Normal one-step head loss represented by a riffle.") - private int riffleDrop = 1; - - @MinNumber(0) - @MaxNumber(64) - @Desc("Largest head loss represented as a gradual cascade.") - private int maximumGradualDrop = 7; - - @MinNumber(1) - @MaxNumber(1024) - @Desc("Largest longitudinal distance used for one gradual cascade transition.") - private int maximumGradualLength = 24; - - @MinNumber(1) - @MaxNumber(128) - @Desc("Minimum required head loss represented by a waterfall.") - private int waterfallMinimumDrop = 8; - - private static IrisStyledRange range(double min, double max, double zoom) { - return new IrisStyledRange(min, max, new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(zoom)); - } -} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverMouthConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverMouthConfig.java index d47e7bfea..0a9126550 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverMouthConfig.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverMouthConfig.java @@ -9,16 +9,16 @@ import lombok.experimental.Accessors; @Accessors(chain = true) @NoArgsConstructor -@Desc("Controls river leveling before a coastal reservoir and the final ocean footprint.") +@Desc("Controls how a surface river meets the sea.") @Data public class IrisRiverMouthConfig { - @MinNumber(0) - @MaxNumber(2048) - @Desc("Distance in blocks over which an ocean-bound river reaches sea level before the coastline.") - private int levelingDistance = 64; + @MinNumber(1) + @MaxNumber(4) + @Desc("Width multiplier reached at the coastline over the final stretch of the river.") + private double flareRatio = 1.6D; @MinNumber(0) @MaxNumber(64) - @Desc("Maximum number of blocks the river footprint may enter an ocean reservoir before terminating.") + @Desc("Maximum number of blocks the recorded mouth footprint may enter the ocean before terminating.") private int maximumOceanApron = 8; } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverPolicy.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverPolicy.java index 2d6f51733..7700931e3 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverPolicy.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverPolicy.java @@ -47,7 +47,7 @@ public class IrisRiverPolicy { @RegistryListResource(IrisBiome.class) @ArrayType(type = String.class) @Desc("Dry river footprint biome selection. An empty list explicitly clears the inherited selection.") - private KList dryBiomes = null; + private KList bankBiomes = null; @RegistryListResource(IrisBiome.class) @ArrayType(type = String.class) @@ -74,12 +74,17 @@ public class IrisRiverPolicy { @Desc("Multiplier applied to terrain-guided routing cost.") private Double routingMultiplier = null; + @MinNumber(0.0001) + @MaxNumber(16) + @Desc("Multiplier applied to the eroded bank width outside the shore band.") + private Double bankMultiplier = null; + public KSet getAllBiomeIds() { KSet biomeIds = new KSet<>(); addAll(biomeIds, surfaceBiomes); addAll(biomeIds, mouthBiomes); addAll(biomeIds, shoreBiomes); - addAll(biomeIds, dryBiomes); + addAll(biomeIds, bankBiomes); addAll(biomeIds, floodedCaveBiomes); return biomeIds; } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverRidgeTunnelConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverRidgeTunnelConfig.java deleted file mode 100644 index 4e211b498..000000000 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverRidgeTunnelConfig.java +++ /dev/null @@ -1,27 +0,0 @@ -package art.arcane.iris.engine.object; - -import art.arcane.iris.engine.object.annotations.Desc; -import art.arcane.iris.engine.object.annotations.MaxNumber; -import art.arcane.iris.engine.object.annotations.MinNumber; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.experimental.Accessors; - -@Accessors(chain = true) -@NoArgsConstructor -@Desc("Controls short contained ridge bores on otherwise viable surface courses.") -@Data -public class IrisRiverRidgeTunnelConfig { - @Desc("Allow short ridge bores when they preserve the accepted drainage course.") - private boolean enabled = true; - - @MinNumber(1) - @MaxNumber(4096) - @Desc("Maximum accepted ridge-bore length in blocks.") - private int maximumLength = 192; - - @MinNumber(1) - @MaxNumber(128) - @Desc("Dry vertical clearance above the river head inside a ridge bore.") - private int headroom = 10; -} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverRoutingConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverRoutingConfig.java index e0f4064d3..44d71f825 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisRiverRoutingConfig.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisRiverRoutingConfig.java @@ -24,22 +24,24 @@ public class IrisRiverRoutingConfig { @Desc("Spacing in blocks between terrain samples used to build the drainage graph.") private int sampleSpacing = 64; - @MinNumber(1) - @MaxNumber(64) - @Desc("Spacing in blocks used to refine accepted terrain-guided centerlines.") - private int refinementSpacing = 8; - - @Desc("Minimum route and tributary lengths used to suppress chopped, clustered branches.") - private IrisRiverBranchingConfig branching = new IrisRiverBranchingConfig(); - @MinNumber(256) @MaxNumber(32768) @Desc("Maximum bounded length in blocks of one accepted river route.") private int maximumRouteLength = 16384; + @MinNumber(0) + @MaxNumber(32768) + @Desc("Minimum exposed source-to-outlet length in blocks of a published surface river.") + private int minimumSurfaceCourseLength = 384; + + @MinNumber(0) + @MaxNumber(32768) + @Desc("Minimum source-to-outlet length in blocks of a published underground river.") + private int minimumUndergroundCourseLength = 192; + @MinNumber(1) @MaxNumber(256) - @Desc("Maximum drainage roots selected per planning tile. Fewer roots produce longer, more strongly branching river trees.") + @Desc("Maximum drainage roots selected per planning tile.") private int maximumOutletsPerTile = 4; @Desc("Allow rivers to terminate at an ocean reservoir.") @@ -48,4 +50,24 @@ public class IrisRiverRoutingConfig { @ArrayType(type = IrisRiverInlandOutlet.class) @Desc("Contained inland outlet kinds available when no legal ocean outlet is selected.") private KList inlandOutlets = new KList<>(); + + @MinNumber(0) + @MaxNumber(64) + @Desc("How strongly routes prefer lower ground.") + private double valleyPreference = 1.5D; + + @MinNumber(0) + @MaxNumber(1024) + @Desc("Cost per block of rise along a route.") + private double uphillPenalty = 24D; + + @MinNumber(0) + @MaxNumber(64) + @Desc("Cost per unit of terrain slope along a route.") + private double slopePenalty = 2D; + + @MinNumber(0) + @MaxNumber(16) + @Desc("How strongly routes are drawn toward existing drainage.") + private double confluenceAttraction = 0.2D; } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverBankConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverBankConfig.java new file mode 100644 index 000000000..5b82c799a --- /dev/null +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverBankConfig.java @@ -0,0 +1,42 @@ +package art.arcane.iris.engine.object; + +import art.arcane.iris.engine.object.annotations.Desc; +import art.arcane.iris.engine.object.annotations.MaxNumber; +import art.arcane.iris.engine.object.annotations.MinNumber; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Accessors(chain = true) +@NoArgsConstructor +@Desc("Controls the lip, shore band and eroded valley around a surface river.") +@Data +public class IrisSurfaceRiverBankConfig { + @MinNumber(1) + @MaxNumber(8) + @Desc("Blocks the bank top sits above the water surface at the channel edge.") + private int freeboard = 1; + + @MinNumber(0) + @MaxNumber(8) + @Desc("Width in blocks of the shore band that receives shore biome content.") + private double shoreWidth = 1.5D; + + @MinNumber(1) + @MaxNumber(16) + @Desc("Horizontal run in blocks of eroded bank per block of cut depth.") + private double blendSlope = 3D; + + @MinNumber(1) + @MaxNumber(64) + @Desc("Narrowest eroded band outside the shore, in blocks.") + private int minimumBlendWidth = 4; + + @MinNumber(1) + @MaxNumber(128) + @Desc("Widest eroded band outside the shore, in blocks.") + private int maximumBlendWidth = 32; + + @Desc("Show the biome's deeper layers on eroded banks instead of the surface layer.") + private boolean exposeCutStrata = true; +} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverChannelConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverChannelConfig.java index 20948c9d4..7536c4e38 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverChannelConfig.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverChannelConfig.java @@ -9,39 +9,37 @@ import lombok.experimental.Accessors; @Accessors(chain = true) @NoArgsConstructor -@Desc("Controls the accepted surface-river terrain and biome footprint.") +@Desc("Controls the wet channel of a surface river.") @Data public class IrisSurfaceRiverChannelConfig { @Desc("Wet channel width in blocks.") private IrisStyledRange width = range(4D, 8D, 1024D); - @Desc("Wet bed depth in blocks.") - private IrisStyledRange depth = range(1D, 4D, 768D, 2D); + @Desc("Wet bed depth in blocks at the channel center.") + private IrisStyledRange depth = range(2D, 4D, 768D); - @Desc("Vertical distance from the natural terrain corridor to the solved water surface.") - private IrisStyledRange surfaceInset = range(3D, 7D, 768D, 2D); + @MinNumber(1) + @MaxNumber(16) + @Desc("Blocks the water surface sits below the lowest natural bank beside the channel.") + private int inset = 1; @MinNumber(0) @MaxNumber(64) - @Desc("Maximum permitted channel incision below natural terrain.") - private int maximumIncision = 6; + @Desc("Maximum cut below natural terrain at the channel center before a course is rejected.") + private int maximumIncision = 10; - @MinNumber(1) - @MaxNumber(2) - @Desc("Narrow biome-selection band immediately outside the wet channel.") - private double shoreWidth = 2D; + @MinNumber(0) + @MaxNumber(1) + @Desc("Coherent variation of the wet outline and bed as a fraction of the channel size.") + private double roughness = 0.25D; - @Desc("Wider terrain-grading distance outside the shore band.") - private IrisStyledRange terrainBlendWidth = range(10D, 24D, 1024D); + @MinNumber(3) + @MaxNumber(128) + @Desc("Wavelength in blocks of the outline and bed variation.") + private int roughnessWavelength = 16; private static IrisStyledRange range(double min, double max, double zoom) { - return range(min, max, zoom, 1D); - } - - private static IrisStyledRange range(double min, double max, double zoom, double exponent) { - IrisGeneratorStyle style = new IrisGeneratorStyle(NoiseStyle.IRIS) - .zoomed(zoom) - .setExponent(exponent); + IrisGeneratorStyle style = new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(zoom); return new IrisStyledRange(min, max, style); } } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverConfig.java index f59cd73a4..6c667000b 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverConfig.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverConfig.java @@ -16,15 +16,15 @@ public class IrisSurfaceRiverConfig { @Desc("Surface-river source allocation.") private IrisSurfaceRiverSourceConfig sources = new IrisSurfaceRiverSourceConfig(); - @Desc("Surface channel and surrounding terrain footprint.") + @Desc("Wet channel width, depth, inset and roughness.") private IrisSurfaceRiverChannelConfig channel = new IrisSurfaceRiverChannelConfig(); - @Desc("Pool, riffle, cascade, and waterfall classification thresholds.") - private IrisRiverHydraulicsConfig hydraulics = new IrisRiverHydraulicsConfig(); + @Desc("Lip, shore band and eroded valley around the channel.") + private IrisSurfaceRiverBankConfig banks = new IrisSurfaceRiverBankConfig(); - @Desc("Short ridge-bore configuration for otherwise viable surface routes.") - private IrisRiverRidgeTunnelConfig ridgeTunnels = new IrisRiverRidgeTunnelConfig(); + @Desc("Cascade and waterfall thresholds.") + private IrisSurfaceRiverFlowConfig flow = new IrisSurfaceRiverFlowConfig(); - @Desc("Coastal mouth leveling and ocean-apron limits.") + @Desc("Coastal mouth flare and ocean-apron limits.") private IrisRiverMouthConfig mouths = new IrisRiverMouthConfig(); } diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverFlowConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverFlowConfig.java new file mode 100644 index 000000000..9afd67a88 --- /dev/null +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisSurfaceRiverFlowConfig.java @@ -0,0 +1,24 @@ +package art.arcane.iris.engine.object; + +import art.arcane.iris.engine.object.annotations.Desc; +import art.arcane.iris.engine.object.annotations.MaxNumber; +import art.arcane.iris.engine.object.annotations.MinNumber; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +@Accessors(chain = true) +@NoArgsConstructor +@Desc("Controls how a surface river descends.") +@Data +public class IrisSurfaceRiverFlowConfig { + @MinNumber(1) + @MaxNumber(16) + @Desc("Shortest level run in blocks between one-block steps before consecutive steps are labelled a cascade.") + private int cascadeRun = 2; + + @MinNumber(2) + @MaxNumber(24) + @Desc("Smallest natural cliff in blocks that becomes a waterfall instead of a cascade.") + private int waterfallMinimumDrop = 6; +} diff --git a/core/src/main/java/art/arcane/iris/engine/object/IrisUndergroundRiverConfig.java b/core/src/main/java/art/arcane/iris/engine/object/IrisUndergroundRiverConfig.java index a1a439bcf..576fa52a3 100644 --- a/core/src/main/java/art/arcane/iris/engine/object/IrisUndergroundRiverConfig.java +++ b/core/src/main/java/art/arcane/iris/engine/object/IrisUndergroundRiverConfig.java @@ -1,6 +1,8 @@ package art.arcane.iris.engine.object; import art.arcane.iris.engine.object.annotations.Desc; +import art.arcane.iris.engine.object.annotations.MaxNumber; +import art.arcane.iris.engine.object.annotations.MinNumber; import lombok.Data; import lombok.NoArgsConstructor; import lombok.experimental.Accessors; @@ -31,6 +33,11 @@ public class IrisUndergroundRiverConfig { @Desc("Allow accepted underground courses to connect transactionally to existing caves.") private boolean connectToExistingCaves = true; + @MinNumber(0) + @MaxNumber(2048) + @Desc("Distance in blocks over which an ocean-bound underground river levels to sea level before its mouth.") + private int mouthLevelingDistance = 64; + private static IrisStyledRange range(double min, double max, double zoom) { return new IrisStyledRange(min, max, new IrisGeneratorStyle(NoiseStyle.IRIS).zoomed(zoom)); } diff --git a/core/src/test/java/art/arcane/iris/core/pack/PackHydrologyValidatorTest.java b/core/src/test/java/art/arcane/iris/core/pack/PackHydrologyValidatorTest.java index d8a161af4..0a5932ec2 100644 --- a/core/src/test/java/art/arcane/iris/core/pack/PackHydrologyValidatorTest.java +++ b/core/src/test/java/art/arcane/iris/core/pack/PackHydrologyValidatorTest.java @@ -50,7 +50,7 @@ public class PackHydrologyValidatorTest { "derivative": "minecraft:river", "riverPolicy": { "profiles": ["default"], - "dryBiomes": ["river_dry"] + "bankBiomes": ["river_dry"] } } """); @@ -267,7 +267,7 @@ public class PackHydrologyValidatorTest { "surfaceBiomes": ["missing"], "mouthBiomes": "river", "shoreBiomes": ["biome", "biome"], - "dryBiomes": [17], + "bankBiomes": [17], "floodedCaveBiomes": ["../escape"], "widthMultiplier": 0, "depthMultiplier": 17, @@ -286,7 +286,7 @@ public class PackHydrologyValidatorTest { assertContains(result.errors(), "surfaceBiomes[0] references missing biome 'missing'"); assertContains(result.errors(), ".mouthBiomes must be an array or null"); assertContains(result.errors(), "shoreBiomes[1] duplicates biome 'biome'"); - assertContains(result.errors(), "dryBiomes[0] must name a biome resource"); + assertContains(result.errors(), "bankBiomes[0] must name a biome resource"); assertContains(result.errors(), "floodedCaveBiomes[0] must name a biome resource"); assertContains(result.errors(), "widthMultiplier must be at least"); assertContains(result.errors(), "depthMultiplier must be at most"); @@ -859,7 +859,7 @@ public class PackHydrologyValidatorTest { "surfaceBiomes": ["river_surface"], "mouthBiomes": ["river_surface"], "shoreBiomes": ["river_shore"], - "dryBiomes": ["river_dry"], + "bankBiomes": ["river_dry"], "floodedCaveBiomes": ["river_dry"], "widthMultiplier": 1, "depthMultiplier": 1, diff --git a/core/src/test/java/art/arcane/iris/core/project/IrisRiverSchemaTest.java b/core/src/test/java/art/arcane/iris/core/project/IrisRiverSchemaTest.java index dd91d5e84..b50e69953 100644 --- a/core/src/test/java/art/arcane/iris/core/project/IrisRiverSchemaTest.java +++ b/core/src/test/java/art/arcane/iris/core/project/IrisRiverSchemaTest.java @@ -164,7 +164,7 @@ public class IrisRiverSchemaTest { assertEquals("array", properties.getJSONObject("surfaceBiomes").getString("type")); assertEquals("array", properties.getJSONObject("mouthBiomes").getString("type")); assertEquals("array", properties.getJSONObject("shoreBiomes").getString("type")); - assertEquals("array", properties.getJSONObject("dryBiomes").getString("type")); + assertEquals("array", properties.getJSONObject("bankBiomes").getString("type")); assertEquals("array", properties.getJSONObject("floodedCaveBiomes").getString("type")); assertEquals(16D, properties.getJSONObject("widthMultiplier").getDouble("maximum"), 0D); assertEquals(0D, properties.getJSONObject("incisionMultiplier").getDouble("minimum"), 0D); diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerCrossTileCaveAdmissionIntegrationTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerCrossTileCaveAdmissionIntegrationTest.java index 3e82e9714..32fd2f889 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerCrossTileCaveAdmissionIntegrationTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerCrossTileCaveAdmissionIntegrationTest.java @@ -270,7 +270,8 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest { true, 96, 10 - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4), new HydrologyPlannerSettings.Underground( true, @@ -351,7 +352,8 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest { baseSurface.ridgeTunnelsEnabled(), baseSurface.maximumRidgeTunnelLength(), baseSurface.ridgeTunnelHeadroom() - ), + , + HydrologyPlannerSettings.Banks.defaults()), base.hydraulics(), new HydrologyPlannerSettings.Underground( false, @@ -412,6 +414,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -452,11 +455,12 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest { sample.depthMultiplier(), sample.incisionMultiplier(), sample.routingMultiplier(), + 1D, sample.parentBiomeKey(), sample.surfaceBiomeKey(), sample.mouthBiomeKey(), sample.shoreBiomeKey(), - sample.dryBiomeKey(), + sample.bankBiomeKey(), sample.floodedCaveBiomeKey(), sample.preferredProfileKeys() ); @@ -484,6 +488,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest { 1D, 1D, 1D, + 1D, "ocean_parent", "surface", "mouth", diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerGlobalInvariantTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerGlobalInvariantTest.java index fe50c6e1d..827c0f849 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerGlobalInvariantTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerGlobalInvariantTest.java @@ -687,7 +687,8 @@ public class HydrologyPlannerGlobalInvariantTest { false, 0, 0 - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 3, 8, 4), new HydrologyPlannerSettings.Underground( undergroundEnabled, @@ -752,7 +753,8 @@ public class HydrologyPlannerGlobalInvariantTest { surface.ridgeTunnelsEnabled(), surface.maximumRidgeTunnelLength(), surface.ridgeTunnelHeadroom() - ), + , + HydrologyPlannerSettings.Banks.defaults()), base.hydraulics(), new HydrologyPlannerSettings.Underground( underground.enabled(), @@ -935,6 +937,7 @@ public class HydrologyPlannerGlobalInvariantTest { 1D, 1D, 1D, + 1D, ocean ? "ocean" : "land", "surface", "mouth", @@ -985,6 +988,7 @@ public class HydrologyPlannerGlobalInvariantTest { 1D, 1D, 1D, + 1D, "land", "surface", "mouth", @@ -1033,6 +1037,7 @@ public class HydrologyPlannerGlobalInvariantTest { 1D, 1D, 1D, + 1D, "land", "surface", "mouth", @@ -1157,7 +1162,7 @@ public class HydrologyPlannerGlobalInvariantTest { addString(terrain.surfaceBiomeKey()); addString(terrain.mouthBiomeKey()); addString(terrain.shoreBiomeKey()); - addString(terrain.dryBiomeKey()); + addString(terrain.bankBiomeKey()); addString(terrain.floodedCaveBiomeKey()); addInt(terrain.preferredProfileKeys().size()); for (String profile : terrain.preferredProfileKeys()) { @@ -1287,7 +1292,7 @@ public class HydrologyPlannerGlobalInvariantTest { addString(layer.surfaceBiomeKey()); addString(layer.mouthBiomeKey()); addString(layer.shoreBiomeKey()); - addString(layer.dryBiomeKey()); + addString(layer.bankBiomeKey()); addString(layer.floodedCaveBiomeKey()); } diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettingsTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettingsTest.java index 03770ebf8..b2329e2e9 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettingsTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerSettingsTest.java @@ -89,7 +89,8 @@ public class HydrologyPlannerSettingsTest { false, 0, 0 - ); + , + HydrologyPlannerSettings.Banks.defaults()); HydrologyPlannerSettings.Hydraulics hydraulics = new HydrologyPlannerSettings.Hydraulics( 8, 8, @@ -139,6 +140,38 @@ public class HydrologyPlannerSettingsTest { assertEquals(2730, deepFluid.maximumChannelLength()); } + @Test + public void bankSettingsRejectValuesOutsideTheirBounds() { + HydrologyPlannerSettings.Banks banks = HydrologyPlannerSettings.Banks.defaults(); + + assertEquals(1, banks.inset()); + assertEquals(1, banks.freeboard()); + assertEquals(3D, banks.blendSlope(), 0D); + assertEquals(4, banks.minimumBlendWidth()); + assertEquals(32, banks.maximumBlendWidth()); + assertEquals(6, banks.waterfallMinimumDrop()); + assertTrue(banks.exposeCutStrata()); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks( + 0, 1, 3D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true)); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks( + 1, 0, 3D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true)); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks( + 1, 1, 0.5D, 4, 32, 0.25D, 16, 2, 6, 1.6D, true)); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks( + 1, 1, 3D, 40, 32, 0.25D, 16, 2, 6, 1.6D, true)); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks( + 1, 1, 3D, 4, 32, 0.25D, 16, 2, 1, 1.6D, true)); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks( + 1, 1, 3D, 4, 32, 0.25D, 16, 0, 6, 1.6D, true)); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Banks( + 1, 1, 3D, 4, 32, 1.5D, 16, 2, 6, 1.6D, true)); + assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Surface( + true, + new HydrologyPlannerSettings.Source(true, 1D, 0, 0, 1, 0), + 1, 1, 1, 1, 1, 1, 0, 1D, 0, 0, false, 0, 0, + null)); + } + @Test public void inconsistentHydraulicThresholdsAreRejected() { assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Hydraulics( diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerTest.java index af0a20923..06f3656be 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyPlannerTest.java @@ -509,6 +509,7 @@ public class HydrologyPlannerTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -1124,7 +1125,7 @@ public class HydrologyPlannerTest { assertEquals("surface", layer.surfaceBiomeKey()); assertEquals("mouth", layer.mouthBiomeKey()); assertEquals("shore", layer.shoreBiomeKey()); - assertEquals("dry", layer.dryBiomeKey()); + assertEquals("dry", layer.bankBiomeKey()); assertEquals("flooded", layer.floodedCaveBiomeKey()); if (layer.grading() && !layer.shore() && !layer.channel()) { sawGrading = true; @@ -1204,7 +1205,8 @@ public class HydrologyPlannerTest { base.surface().ridgeTunnelsEnabled(), base.surface().maximumRidgeTunnelLength(), base.surface().ridgeTunnelHeadroom() - ); + , + HydrologyPlannerSettings.Banks.defaults()); HydrologyPlannerSettings shaped = new HydrologyPlannerSettings( base.seaLevel(), base.routing(), @@ -1498,7 +1500,8 @@ public class HydrologyPlannerTest { surface.ridgeTunnelsEnabled(), 32, surface.ridgeTunnelHeadroom() - ), + , + HydrologyPlannerSettings.Banks.defaults()), base.hydraulics(), base.underground(), base.outlets(), @@ -1850,7 +1853,8 @@ public class HydrologyPlannerTest { true, 64, 8 - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4), new HydrologyPlannerSettings.Underground( undergroundDensity > 0D, @@ -1926,7 +1930,8 @@ public class HydrologyPlannerTest { surface.ridgeTunnelsEnabled(), 192, surface.ridgeTunnelHeadroom() - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 4, 12, 5), base.underground(), new HydrologyPlannerSettings.Outlets( @@ -2002,6 +2007,7 @@ public class HydrologyPlannerTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -2066,6 +2072,7 @@ public class HydrologyPlannerTest { 1D, 1D, 1D, + 1D, "ocean_parent", "surface", "mouth", @@ -2097,6 +2104,7 @@ public class HydrologyPlannerTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -2137,6 +2145,7 @@ public class HydrologyPlannerTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -2168,6 +2177,7 @@ public class HydrologyPlannerTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -2199,6 +2209,7 @@ public class HydrologyPlannerTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -2233,7 +2244,8 @@ public class HydrologyPlannerTest { surface.ridgeTunnelsEnabled(), surface.maximumRidgeTunnelLength(), surface.ridgeTunnelHeadroom() - ), + , + HydrologyPlannerSettings.Banks.defaults()), settings.hydraulics(), settings.underground(), settings.outlets(), diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyStyledGeometryTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyStyledGeometryTest.java index adeb5844d..767f3bee1 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyStyledGeometryTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyStyledGeometryTest.java @@ -236,7 +236,8 @@ public class HydrologyStyledGeometryTest { true, 96, 8 - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4), new HydrologyPlannerSettings.Underground( undergroundDensity > 0D, @@ -288,6 +289,7 @@ public class HydrologyStyledGeometryTest { 1D, 1D, 1D, + 1D, "ocean", "surface", "mouth", @@ -319,6 +321,7 @@ public class HydrologyStyledGeometryTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSampleTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSampleTest.java index 27a2e4f9b..712d0bb93 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSampleTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTerrainSampleTest.java @@ -70,6 +70,7 @@ public final class HydrologyTerrainSampleTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTileCacheTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTileCacheTest.java index 0ea448dd9..4232f11e2 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTileCacheTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/HydrologyTileCacheTest.java @@ -245,7 +245,7 @@ public class HydrologyTileCacheTest { 63, new HydrologyPlannerSettings.Routing(64, 16, 4, 128, 64, new HydrologyPlannerSettings.Branching(0, 0), 0D, 0D, 0D, 0D), - new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 2, 4, 4, 1D, 2, 4, false, 0, 0), + new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 2, 4, 4, 1D, 2, 4, false, 0, 0, HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 2, 8, 3), new HydrologyPlannerSettings.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false), new HydrologyPlannerSettings.Outlets( @@ -284,7 +284,7 @@ public class HydrologyTileCacheTest { 63, new HydrologyPlannerSettings.Routing(64, 16, 4, 128, 96, new HydrologyPlannerSettings.Branching(16, 8), 0.5D, 12D, 0.5D, 0.1D), - new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 4, 8, 20, 1.5D, 2, 4, true, 96, 8), + new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 4, 8, 20, 1.5D, 2, 4, true, 96, 8, HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 2, 8, 3), new HydrologyPlannerSettings.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false), new HydrologyPlannerSettings.Outlets( @@ -327,6 +327,7 @@ public class HydrologyTileCacheTest { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolverTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolverTest.java index 307f79930..c88d3e546 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolverTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/policy/RiverPolicyResolverTest.java @@ -29,10 +29,12 @@ public class RiverPolicyResolverTest { assertEquals(IrisRiverRoutingMode.ALLOW, policy.routing()); assertTrue(policy.outletAdmission()); assertTrue(policy.profiles().isEmpty()); + assertTrue(policy.bankBiomes().isEmpty()); assertEquals(1D, policy.widthMultiplier(), 0D); assertEquals(1D, policy.depthMultiplier(), 0D); assertEquals(1D, policy.incisionMultiplier(), 0D); assertEquals(1D, policy.routingMultiplier(), 0D); + assertEquals(1D, policy.bankMultiplier(), 0D); assertTrue(policy.allowsSources()); assertTrue(policy.allowsTransit()); assertTrue(policy.allowsRouting()); @@ -49,13 +51,16 @@ public class RiverPolicyResolverTest { .setProfiles(new KList<>("water")) .setSurfaceBiomes(new KList<>("river/dimension")) .setMouthBiomes(new KList<>("river/mouth")) + .setBankBiomes(new KList<>("river/bank-dimension")) .setWidthMultiplier(1.5D) - .setDepthMultiplier(1.25D); + .setDepthMultiplier(1.25D) + .setBankMultiplier(2D); IrisRiverPolicy region = new IrisRiverPolicy() .setRouting(IrisRiverRoutingMode.PREFER) .setProfiles(new KList<>()) .setSurfaceBiomes(new KList<>("river/region")) .setShoreBiomes(new KList<>("river/shore")) + .setBankBiomes(new KList<>()) .setWidthMultiplier(0.8D) .setRoutingMultiplier(2D); IrisRiverPolicy biome = new IrisRiverPolicy() @@ -63,10 +68,10 @@ public class RiverPolicyResolverTest { .setRouting(IrisRiverRoutingMode.BLOCK) .setOutletAdmission(true) .setSurfaceBiomes(new KList<>()) - .setDryBiomes(new KList<>("river/dry")) .setFloodedCaveBiomes(new KList<>("river/flooded")) .setDepthMultiplier(0.75D) - .setIncisionMultiplier(0.5D); + .setIncisionMultiplier(0.5D) + .setBankMultiplier(0.5D); EffectiveRiverPolicy policy = RiverPolicyResolver.resolve(dimension, region, biome); @@ -75,14 +80,15 @@ public class RiverPolicyResolverTest { assertTrue(policy.outletAdmission()); assertTrue(policy.profiles().isEmpty()); assertTrue(policy.surfaceBiomes().isEmpty()); + assertTrue(policy.bankBiomes().isEmpty()); assertEquals(List.of("river/mouth"), policy.mouthBiomes()); assertEquals(List.of("river/shore"), policy.shoreBiomes()); - assertEquals(List.of("river/dry"), policy.dryBiomes()); assertEquals(List.of("river/flooded"), policy.floodedCaveBiomes()); assertEquals(0.8D, policy.widthMultiplier(), 0D); assertEquals(0.75D, policy.depthMultiplier(), 0D); assertEquals(0.5D, policy.incisionMultiplier(), 0D); assertEquals(2D, policy.routingMultiplier(), 0D); + assertEquals(0.5D, policy.bankMultiplier(), 0D); assertTrue(policy.allowsSources()); assertTrue(policy.allowsTransit()); assertFalse(policy.allowsRouting()); @@ -90,6 +96,17 @@ public class RiverPolicyResolverTest { assertTrue(policy.requiresHeadwaters()); } + @Test + public void bankBiomesInheritUntilOverridden() { + IrisRiverPolicy dimension = new IrisRiverPolicy().setBankBiomes(new KList<>("river/bank")); + IrisRiverPolicy region = new IrisRiverPolicy().setRouting(IrisRiverRoutingMode.PREFER); + + EffectiveRiverPolicy policy = RiverPolicyResolver.resolve(dimension, region, null); + + assertEquals(List.of("river/bank"), policy.bankBiomes()); + assertEquals(1D, policy.bankMultiplier(), 0D); + } + @Test public void resolvesOwnerPoliciesAndSnapshotsMutableLists() { KList profiles = new KList<>("water"); diff --git a/core/src/test/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntimeSettingsTest.java b/core/src/test/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntimeSettingsTest.java index 86bb40d98..9e9d5bacd 100644 --- a/core/src/test/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntimeSettingsTest.java +++ b/core/src/test/java/art/arcane/iris/engine/hydrology/runtime/IrisHydrologyRuntimeSettingsTest.java @@ -19,8 +19,7 @@ public class IrisHydrologyRuntimeSettingsTest { .setShortChannels(true) .setSpacing(8192); IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig() - .setTileSize(256) - .setRefinementSpacing(8); + .setTileSize(256); assertEquals(128, IrisHydrologyRuntime.maximumDeepChannelLength(deepFluid, routing)); } @@ -31,8 +30,7 @@ public class IrisHydrologyRuntimeSettingsTest { .setShortChannels(false) .setSpacing(8192); IrisRiverRoutingConfig routing = new IrisRiverRoutingConfig() - .setTileSize(256) - .setRefinementSpacing(8); + .setTileSize(256); assertEquals(0, IrisHydrologyRuntime.maximumDeepChannelLength(deepFluid, routing)); } @@ -75,6 +73,57 @@ public class IrisHydrologyRuntimeSettingsTest { .underground().sources().minimumSpacing()); } + @Test + public void surfaceBankAndFlowConfigurationMapsIntoPlannerSettings() { + IrisDimension dimension = new IrisDimension(); + IrisRiverHydrology rivers = dimension.getHydrology().getRivers(); + rivers.getSurface().getChannel().setInset(2).setMaximumIncision(12); + rivers.getSurface().getBanks() + .setFreeboard(2) + .setShoreWidth(2.5D) + .setBlendSlope(4D) + .setMinimumBlendWidth(6) + .setMaximumBlendWidth(48) + .setExposeCutStrata(false); + rivers.getSurface().getFlow().setCascadeRun(3).setWaterfallMinimumDrop(8); + rivers.getSurface().getMouths().setFlareRatio(2.2D); + rivers.getUnderground().setMouthLevelingDistance(96); + rivers.getRouting() + .setSampleSpacing(32) + .setMinimumSurfaceCourseLength(500) + .setMinimumUndergroundCourseLength(250) + .setValleyPreference(2.5D) + .setUphillPenalty(30D) + .setSlopePenalty(3D) + .setConfluenceAttraction(0.4D); + + HydrologyPlannerSettings settings = IrisHydrologyRuntime.createSettings(dimension, dimension.getHydrology()); + HydrologyPlannerSettings.Banks banks = settings.surface().banks(); + + assertEquals(2, banks.inset()); + assertEquals(2, banks.freeboard()); + assertEquals(4D, banks.blendSlope(), 0D); + assertEquals(6, banks.minimumBlendWidth()); + assertEquals(48, banks.maximumBlendWidth()); + assertEquals(3, banks.cascadeRun()); + assertEquals(8, banks.waterfallMinimumDrop()); + assertEquals(2.2D, banks.mouthFlareRatio(), 0D); + assertFalse(banks.exposeCutStrata()); + assertEquals(12, settings.surface().maximumIncision()); + assertEquals(2.5D, settings.surface().shoreWidth(), 0D); + assertEquals(8, settings.hydraulics().waterfallMinimumDrop()); + assertEquals(96, settings.outlets().mouthLevelingDistance()); + assertEquals(4, settings.routing().refinementSpacing()); + assertEquals(500, settings.routing().branching().minimumSurfaceCourseLength()); + assertEquals(250, settings.routing().branching().minimumUndergroundCourseLength()); + assertEquals(2.5D, settings.routing().valleyPreference(), 0D); + assertEquals(30D, settings.routing().uphillPenalty(), 0D); + assertEquals(3D, settings.routing().slopePenalty(), 0D); + assertEquals(0.4D, settings.routing().confluenceAttraction(), 0D); + assertEquals(2, IrisHydrologyRuntime.refinementSpacing(50)); + assertEquals(1, IrisHydrologyRuntime.refinementSpacing(45)); + } + @Test public void routingOutletLimitControlsDrainageRootCount() { IrisDimension dimension = new IrisDimension(); @@ -96,10 +145,8 @@ public class IrisHydrologyRuntimeSettingsTest { .setMaximumOffsetRatio(0.44D) .setSmoothingPasses(2) .setMaximumTurnDegrees(76D); - rivers.getGeometry().getSurface() - .setBedRoundness(3.1D) - .setBedRoughness(0.37D) - .setWallRoughness(0.29D) + rivers.getSurface().getChannel() + .setRoughness(0.37D) .setRoughnessWavelength(13); rivers.getGeometry().getDrops() .setCascadeRunPerBlock(4) @@ -121,9 +168,8 @@ public class IrisHydrologyRuntimeSettingsTest { assertEquals(0.44D, geometry.meanders().maximumOffsetRatio(), 0D); assertEquals(2, geometry.meanders().smoothingPasses()); assertEquals(76D, geometry.meanders().maximumTurnDegrees(), 0D); - assertEquals(3.1D, geometry.surface().bedRoundness(), 0D); assertEquals(0.37D, geometry.surface().bedRoughness(), 0D); - assertEquals(0.29D, geometry.surface().wallRoughness(), 0D); + assertEquals(0.37D, geometry.surface().wallRoughness(), 0D); assertEquals(13, geometry.surface().roughnessWavelength()); assertEquals(4, geometry.drops().cascadeRunPerBlock()); assertEquals(2.2D, geometry.drops().cascadeExponent(), 0D); diff --git a/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponentTest.java b/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponentTest.java index 09a4a6f9d..71fdd5807 100644 --- a/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponentTest.java +++ b/core/src/test/java/art/arcane/iris/engine/mantle/components/MantleHydrologyComponentTest.java @@ -1541,7 +1541,8 @@ public class MantleHydrologyComponentTest { false, 0, 0 - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 3, 8, 4), new HydrologyPlannerSettings.Underground( false, @@ -1597,6 +1598,7 @@ public class MantleHydrologyComponentTest { 1D, 1D, 1D, + 1D, ocean ? "ocean" : "land", "surface", "mouth", diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionReachableBiomesTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionReachableBiomesTest.java index 70cd34930..b2c573e33 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionReachableBiomesTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisDimensionReachableBiomesTest.java @@ -148,7 +148,7 @@ public class IrisDimensionReachableBiomesTest { .setChildren(new KList<>("dimension-child")); IrisBiome dimensionChild = biome("dimension-child"); IrisBiome regionMouth = biome("region-mouth").setRiverPolicy(new IrisRiverPolicy() - .setDryBiomes(new KList<>("nested-dry"))); + .setBankBiomes(new KList<>("nested-dry"))); IrisBiome biomeShore = biome("biome-shore"); IrisBiome nestedDry = biome("nested-dry"); @@ -222,7 +222,7 @@ public class IrisDimensionReachableBiomesTest { IrisRegion reachable = new IrisRegion() .setLandBiomes(new KList<>("root")) .setRiverPolicy(new IrisRiverPolicy() - .setDryBiomes(new KList<>("region-dry"))); + .setBankBiomes(new KList<>("region-dry"))); IrisBiome root = biome("root").setRiverPolicy(new IrisRiverPolicy() .setFloodedCaveBiomes(new KList<>("biome-flooded"))); diff --git a/core/src/test/java/art/arcane/iris/engine/object/IrisRiverConfigurationTest.java b/core/src/test/java/art/arcane/iris/engine/object/IrisRiverConfigurationTest.java index c3399ed7a..9b3b7c057 100644 --- a/core/src/test/java/art/arcane/iris/engine/object/IrisRiverConfigurationTest.java +++ b/core/src/test/java/art/arcane/iris/engine/object/IrisRiverConfigurationTest.java @@ -32,9 +32,14 @@ public class IrisRiverConfigurationTest { IrisRiverRoutingConfig routing = dimension.getHydrology().getRivers().getRouting(); assertEquals(2048, routing.getTileSize()); + assertEquals(64, routing.getSampleSpacing()); assertEquals(16384, routing.getMaximumRouteLength()); - assertEquals(384, routing.getBranching().getMinimumSurfaceCourseLength()); - assertEquals(192, routing.getBranching().getMinimumUndergroundCourseLength()); + assertEquals(384, routing.getMinimumSurfaceCourseLength()); + assertEquals(192, routing.getMinimumUndergroundCourseLength()); + assertEquals(1.5D, routing.getValleyPreference(), 0D); + assertEquals(24D, routing.getUphillPenalty(), 0D); + assertEquals(2D, routing.getSlopePenalty(), 0D); + assertEquals(0.2D, routing.getConfluenceAttraction(), 0D); IrisSurfaceRiverConfig surface = dimension.getHydrology().getRivers().getSurface(); assertTrue(surface.isEnabled()); @@ -43,19 +48,22 @@ public class IrisRiverConfigurationTest { assertEquals(384, surface.getSources().getMinimumSpacing()); assertEquals(4D, surface.getChannel().getWidth().getMin(), 0D); assertEquals(8D, surface.getChannel().getWidth().getMax(), 0D); - assertEquals(1D, surface.getChannel().getDepth().getMin(), 0D); + assertEquals(2D, surface.getChannel().getDepth().getMin(), 0D); assertEquals(4D, surface.getChannel().getDepth().getMax(), 0D); - assertEquals(2D, surface.getChannel().getDepth().getStyle().getExponent(), 0D); - assertEquals(3D, surface.getChannel().getSurfaceInset().getMin(), 0D); - assertEquals(7D, surface.getChannel().getSurfaceInset().getMax(), 0D); - assertEquals(2D, surface.getChannel().getSurfaceInset().getStyle().getExponent(), 0D); - assertEquals(6, surface.getChannel().getMaximumIncision()); - assertEquals(2D, surface.getChannel().getShoreWidth(), 0D); - assertEquals(64, surface.getMouths().getLevelingDistance()); + assertEquals(1, surface.getChannel().getInset()); + assertEquals(10, surface.getChannel().getMaximumIncision()); + assertEquals(0.25D, surface.getChannel().getRoughness(), 0D); + assertEquals(16, surface.getChannel().getRoughnessWavelength()); + assertEquals(1, surface.getBanks().getFreeboard()); + assertEquals(1.5D, surface.getBanks().getShoreWidth(), 0D); + assertEquals(3D, surface.getBanks().getBlendSlope(), 0D); + assertEquals(4, surface.getBanks().getMinimumBlendWidth()); + assertEquals(32, surface.getBanks().getMaximumBlendWidth()); + assertTrue(surface.getBanks().isExposeCutStrata()); + assertEquals(2, surface.getFlow().getCascadeRun()); + assertEquals(6, surface.getFlow().getWaterfallMinimumDrop()); + assertEquals(1.6D, surface.getMouths().getFlareRatio(), 0D); assertEquals(8, surface.getMouths().getMaximumOceanApron()); - assertEquals(80D, surface.getHydraulics().getTargetPoolLength().getMin(), 0D); - assertEquals(180D, surface.getHydraulics().getTargetPoolLength().getMax(), 0D); - assertEquals(8, surface.getHydraulics().getWaterfallMinimumDrop()); IrisRiverDropShapeConfig drops = dimension.getHydrology().getRivers().getGeometry().getDrops(); assertEquals(2, drops.getCascadeRunPerBlock()); assertEquals(1.4D, drops.getCascadeExponent(), 0D); @@ -75,6 +83,7 @@ public class IrisRiverConfigurationTest { assertEquals(3D, underground.getChannelWidth().getMin(), 0D); assertEquals(8D, underground.getChannelWidth().getMax(), 0D); assertTrue(underground.isConnectToExistingCaves()); + assertEquals(64, underground.getMouthLevelingDistance()); IrisCoastalRiverGrottoConfig coastal = dimension.getHydrology().getRivers().getGrottos().getCoastal(); IrisInlandRiverGrottoConfig inland = dimension.getHydrology().getRivers().getGrottos().getInland(); @@ -101,6 +110,7 @@ public class IrisRiverConfigurationTest { assertNull(dimension.getRiverPolicy().getPlacement()); assertNull(dimension.getRiverPolicy().getRouting()); assertNull(dimension.getRiverPolicy().getOutletAdmission()); + assertNull(dimension.getRiverPolicy().getBankMultiplier()); assertNull(new IrisRegion().getRiverPolicy()); assertNull(new IrisBiome().getRiverPolicy()); } @@ -116,15 +126,16 @@ public class IrisRiverConfigurationTest { "routing": { "tileSize": 4096, "sampleSpacing": 96, - "refinementSpacing": 6, - "branching": { - "minimumSurfaceCourseLength": 640, - "minimumUndergroundCourseLength": 480 - }, "maximumRouteLength": 32768, + "minimumSurfaceCourseLength": 640, + "minimumUndergroundCourseLength": 480, "maximumOutletsPerTile": 3, "oceanOutlets": true, - "inlandOutlets": ["SINKHOLE_GROTTO"] + "inlandOutlets": ["SINKHOLE_GROTTO"], + "valleyPreference": 2.5, + "uphillPenalty": 30, + "slopePenalty": 3, + "confluenceAttraction": 0.4 }, "geometry": { "drops": { @@ -143,20 +154,21 @@ public class IrisRiverConfigurationTest { "channel": { "width": {"min": 5, "max": 24}, "depth": {"min": 2, "max": 6}, - "surfaceInset": {"min": 5, "max": 9}, + "inset": 2, "maximumIncision": 14, - "shoreWidth": 1.5, - "terrainBlendWidth": {"min": 8, "max": 10} + "roughness": 0.4, + "roughnessWavelength": 24 }, - "hydraulics": { - "targetPoolLength": {"min": 64, "max": 192}, - "riffleDrop": 1, - "maximumGradualDrop": 4, - "maximumGradualLength": 12, - "waterfallMinimumDrop": 5 + "banks": { + "freeboard": 2, + "shoreWidth": 2.5, + "blendSlope": 4, + "minimumBlendWidth": 6, + "maximumBlendWidth": 48, + "exposeCutStrata": false }, - "ridgeTunnels": {"enabled": true, "maximumLength": 224, "headroom": 12}, - "mouths": {"levelingDistance": 80, "maximumOceanApron": 1} + "flow": {"cascadeRun": 3, "waterfallMinimumDrop": 8}, + "mouths": {"flareRatio": 2.2, "maximumOceanApron": 1} }, "underground": { "sources": {"density": 0.4, "minimumPerTile": 3, "minimumSpacing": 896}, @@ -164,7 +176,8 @@ public class IrisRiverConfigurationTest { "channelWidth": {"min": 4, "max": 16}, "depth": {"min": 2, "max": 4}, "headroom": {"min": 7, "max": 15}, - "connectToExistingCaves": false + "connectToExistingCaves": false, + "mouthLevelingDistance": 80 }, "grottos": { "coastal": { @@ -211,8 +224,10 @@ public class IrisRiverConfigurationTest { "profiles": ["underworld"], "surfaceBiomes": ["river/surface"], "shoreBiomes": ["river/shore"], + "bankBiomes": ["river/bank"], "widthMultiplier": 1.5, - "incisionMultiplier": 0.75 + "incisionMultiplier": 0.75, + "bankMultiplier": 2 } } """, IrisDimension.class); @@ -236,14 +251,31 @@ public class IrisRiverConfigurationTest { IrisRiverHydrology rivers = dimension.getHydrology().getRivers(); assertTrue(rivers.isEnabled()); assertEquals(4096, rivers.getRouting().getTileSize()); + assertEquals(96, rivers.getRouting().getSampleSpacing()); assertEquals(32768, rivers.getRouting().getMaximumRouteLength()); assertEquals(3, rivers.getRouting().getMaximumOutletsPerTile()); - assertEquals(640, rivers.getRouting().getBranching().getMinimumSurfaceCourseLength()); - assertEquals(480, rivers.getRouting().getBranching().getMinimumUndergroundCourseLength()); + assertEquals(640, rivers.getRouting().getMinimumSurfaceCourseLength()); + assertEquals(480, rivers.getRouting().getMinimumUndergroundCourseLength()); + assertEquals(2.5D, rivers.getRouting().getValleyPreference(), 0D); + assertEquals(30D, rivers.getRouting().getUphillPenalty(), 0D); + assertEquals(3D, rivers.getRouting().getSlopePenalty(), 0D); + assertEquals(0.4D, rivers.getRouting().getConfluenceAttraction(), 0D); assertEquals(new KList<>(IrisRiverInlandOutlet.SINKHOLE_GROTTO), rivers.getRouting().getInlandOutlets()); assertEquals(24D, rivers.getSurface().getChannel().getWidth().getMax(), 0D); - assertEquals(9D, rivers.getSurface().getChannel().getSurfaceInset().getMax(), 0D); - assertEquals(5, rivers.getSurface().getHydraulics().getWaterfallMinimumDrop()); + assertEquals(2, rivers.getSurface().getChannel().getInset()); + assertEquals(14, rivers.getSurface().getChannel().getMaximumIncision()); + assertEquals(0.4D, rivers.getSurface().getChannel().getRoughness(), 0D); + assertEquals(24, rivers.getSurface().getChannel().getRoughnessWavelength()); + assertEquals(2, rivers.getSurface().getBanks().getFreeboard()); + assertEquals(2.5D, rivers.getSurface().getBanks().getShoreWidth(), 0D); + assertEquals(4D, rivers.getSurface().getBanks().getBlendSlope(), 0D); + assertEquals(6, rivers.getSurface().getBanks().getMinimumBlendWidth()); + assertEquals(48, rivers.getSurface().getBanks().getMaximumBlendWidth()); + assertFalse(rivers.getSurface().getBanks().isExposeCutStrata()); + assertEquals(3, rivers.getSurface().getFlow().getCascadeRun()); + assertEquals(8, rivers.getSurface().getFlow().getWaterfallMinimumDrop()); + assertEquals(2.2D, rivers.getSurface().getMouths().getFlareRatio(), 0D); + assertEquals(1, rivers.getSurface().getMouths().getMaximumOceanApron()); assertEquals(768, rivers.getSurface().getSources().getMinimumSpacing()); assertEquals(5, rivers.getGeometry().getDrops().getCascadeRunPerBlock()); assertEquals(2.4D, rivers.getGeometry().getDrops().getCascadeExponent(), 0D); @@ -252,10 +284,10 @@ public class IrisRiverConfigurationTest { assertEquals(4, rivers.getGeometry().getDrops().getMaximumFlowDepth()); assertEquals(2.2D, rivers.getGeometry().getDrops().getBasinWidthRatio(), 0D); assertEquals(11, rivers.getGeometry().getDrops().getMaximumBasinDepth()); - assertEquals(224, rivers.getSurface().getRidgeTunnels().getMaximumLength()); assertEquals(-96D, rivers.getUnderground().getFluidLevel().getMin(), 0D); assertEquals(896, rivers.getUnderground().getSources().getMinimumSpacing()); assertFalse(rivers.getUnderground().isConnectToExistingCaves()); + assertEquals(80, rivers.getUnderground().getMouthLevelingDistance()); assertEquals(40, rivers.getGrottos().getCoastal().getHorizontalRadius()); assertTrue(rivers.getGrottos().getInland().isEnabled()); assertTrue(rivers.getGrottos().getInland().isConnectSurfaceRivers()); @@ -273,7 +305,8 @@ public class IrisRiverConfigurationTest { assertEquals(IrisRiverPlacementMode.PREFERRED_HEADWATER, dimension.getRiverPolicy().getPlacement()); assertEquals(IrisRiverRoutingMode.PREFER, dimension.getRiverPolicy().getRouting()); assertFalse(dimension.getRiverPolicy().getOutletAdmission()); - assertEquals(Set.of("river/surface", "river/shore"), dimension.getRiverPolicy().getAllBiomeIds()); + assertEquals(2D, dimension.getRiverPolicy().getBankMultiplier(), 0D); + assertEquals(Set.of("river/surface", "river/shore", "river/bank"), dimension.getRiverPolicy().getAllBiomeIds()); assertEquals(IrisRiverPlacementMode.TRANSIT_ONLY, region.getRiverPolicy().getPlacement()); assertEquals(new KList<>("river/mouth"), region.getRiverPolicy().getMouthBiomes()); assertEquals(IrisRiverPlacementMode.REQUIRED_HEADWATER, biome.getRiverPolicy().getPlacement()); diff --git a/probe/src/main/java/art/arcane/iris/probe/HydrologyPackProbe.java b/probe/src/main/java/art/arcane/iris/probe/HydrologyPackProbe.java index e60df886f..779a825f8 100644 --- a/probe/src/main/java/art/arcane/iris/probe/HydrologyPackProbe.java +++ b/probe/src/main/java/art/arcane/iris/probe/HydrologyPackProbe.java @@ -3862,7 +3862,7 @@ public final class HydrologyPackProbe { position.z() ); String expectedBiome = witness.action() == HydrologyCaveAction.DRY_AIR - ? witness.layer().dryBiomeKey() + ? witness.layer().bankBiomeKey() : witness.layer().floodedCaveBiomeKey(); if (cell == null || cell.action() != witness.action() diff --git a/probe/src/main/java/art/arcane/iris/probe/HydrologyPlannerProbe.java b/probe/src/main/java/art/arcane/iris/probe/HydrologyPlannerProbe.java index 236ecde12..a37783854 100644 --- a/probe/src/main/java/art/arcane/iris/probe/HydrologyPlannerProbe.java +++ b/probe/src/main/java/art/arcane/iris/probe/HydrologyPlannerProbe.java @@ -231,7 +231,8 @@ public final class HydrologyPlannerProbe { true, 96, 8 - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4), new HydrologyPlannerSettings.Underground( undergroundDensity > 0D, @@ -289,6 +290,7 @@ public final class HydrologyPlannerProbe { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -396,7 +398,8 @@ public final class HydrologyPlannerProbe { true, 96, 10 - ), + , + HydrologyPlannerSettings.Banks.defaults()), new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4), new HydrologyPlannerSettings.Underground( true, @@ -460,6 +463,7 @@ public final class HydrologyPlannerProbe { 1D, 1D, 1D, + 1D, "parent", "surface", "mouth", @@ -492,6 +496,7 @@ public final class HydrologyPlannerProbe { 1D, 1D, 1D, + 1D, "ocean_parent", "surface", "mouth",