Tributaries join the main stem at a confluence

- routing.tributaries lets an outlet keep extra surface courses; each is cut where its centerline first meets the stem and joined with a short connector
- A tributary must keep half the minimum course length and arrive at or above the stem's water
- Surface shape rejections carry the failing rule as their detail
This commit is contained in:
Brian Neumann-Fopiano
2026-09-02 00:54:22 -04:00
parent 185e2fa4ae
commit d23d6d5c9b
15 changed files with 360 additions and 37 deletions
@@ -282,6 +282,7 @@ final class PackHydrologyValidator {
PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "slopePenalty", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "confluenceAttraction", 0D, 1D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "lengthPreference", 0D, 8D, errors);
PackJsonFieldChecks.validateOptionalIntegerRange(path, routing, "tributaries", 0, 4, errors);
int tileSize = integerValue(routing, "tileSize", 2048);
int sampleSpacing = integerValue(routing, "sampleSpacing", 64);
@@ -1099,7 +1099,7 @@ public final class HydrologyPlanner {
graph = mergeGraphs(surfaceGraph, undergroundGraph);
}
}
List<RiverCourse> normalizedTrunkCourses = normalizeSharedTrunks(courses);
List<RiverCourse> normalizedTrunkCourses = normalizeSharedTrunks(courses, diagnostics);
List<RiverCourse> normalizedOutletCourses = normalizeOutletContinuations(normalizedTrunkCourses);
courses.clear();
courses.addAll(normalizedOutletCourses);
@@ -1277,7 +1277,7 @@ public final class HydrologyPlanner {
return false;
}
private List<RiverCourse> normalizeSharedTrunks(List<RiverCourse> courses) {
private List<RiverCourse> normalizeSharedTrunks(List<RiverCourse> courses, List<HydrologyDiagnosticCandidate> diagnostics) {
HashMap<CourseEdgeKey, RiverCourse> edgeOwners = new HashMap<>();
for (RiverCourse course : courses) {
if (course.sourceNodeId().isEmpty()) {
@@ -1317,6 +1317,14 @@ public final class HydrologyPlanner {
.get(ownedEdgeCount - 1)
.centerline()
.getLast();
if (course.type() == RiverCourseType.SURFACE) {
RiverCourse owner = edgeOwners.get(new CourseEdgeKey(course.type(), course.drainageEdges().get(ownedEdgeCount).id()));
RiverCourse tributary = surfaceTributary(course, owner, boundary, ownedEdgeCount, diagnostics);
if (tributary != null) {
normalized.add(tributary);
}
continue;
}
int retainedSegmentCount = retainedSegmentCount(course.segments(), boundary);
if (retainedSegmentCount < 1) {
normalized.add(course);
@@ -1336,6 +1344,137 @@ public final class HydrologyPlanner {
return List.copyOf(normalized);
}
/**
* Turns a surface course that lost its shared drainage to a longer trunk into a tributary: it is cut
* where its own centerline first comes within a channel width of the stem and joined to the nearest
* stem station with a short connector. The tributary must still be a real reach and must arrive at
* or above the stem's water, so it steps down into the river instead of pooling below it.
*/
private RiverCourse surfaceTributary(
RiverCourse course,
RiverCourse owner,
HydrologyPoint boundary,
int ownedEdgeCount,
List<HydrologyDiagnosticCandidate> diagnostics
) {
ArrayList<HydrologyPoint> stemStations = new ArrayList<>();
ArrayList<Integer> stemWidths = new ArrayList<>();
for (HydraulicSegment segment : owner.segments()) {
for (HydrologyPoint point : segment.centerline()) {
stemStations.add(point);
stemWidths.add(segment.width());
}
}
ArrayList<HydraulicSegment> retained = new ArrayList<>();
int stations = 0;
HydrologyPoint joinStation = null;
for (HydraulicSegment segment : course.segments()) {
List<HydrologyPoint> centerline = segment.centerline();
int joinIndex = -1;
for (int index = 0; index < centerline.size() && joinIndex < 0; index++) {
HydrologyPoint point = centerline.get(index);
for (int stemIndex = 0; stemIndex < stemStations.size(); stemIndex++) {
HydrologyPoint station = stemStations.get(stemIndex);
double reach = (segment.width() + stemWidths.get(stemIndex)) / 2D + 1D;
if (point.distanceSquared2D(station) <= reach * reach) {
joinIndex = index;
joinStation = station;
break;
}
}
}
if (joinIndex < 0) {
retained.add(segment);
stations += centerline.size();
continue;
}
if (joinIndex > 0 || retained.isEmpty()) {
List<HydrologyPoint> prefix = centerline.subList(0, joinIndex + 1);
int downstreamHead = prefix.getLast().y();
int drop = segment.upstreamHeadY() - downstreamHead;
HydrologyFeatureType type = drop == 0 && segment.type().isDrop()
? HydrologyFeatureType.SURFACE_POOL
: segment.type();
retained.add(new HydraulicSegment(
segment.id(),
segment.courseId(),
type,
segment.upstreamHeadY(),
downstreamHead,
segment.width(),
segment.depth(),
false,
false,
prefix
));
stations += prefix.size();
}
break;
}
if (joinStation == null) {
addTributaryDiagnostic(course, HydrologyCandidateRejection.NO_DRAINAGE_PATH, 0, diagnostics);
return null;
}
if (retained.isEmpty() || stations < settings.routing().minimumSurfaceCourseLength() / 2) {
addTributaryDiagnostic(course, HydrologyCandidateRejection.COURSE_TOO_SHORT, stations, diagnostics);
return null;
}
HydraulicSegment last = retained.getLast();
HydrologyPoint end = last.end();
int stemHead = joinStation.y();
if (last.downstreamHeadY() < stemHead) {
addTributaryDiagnostic(course, HydrologyCandidateRejection.SURFACE_HEAD_RANGE,
stemHead - last.downstreamHeadY(), diagnostics);
return null;
}
if (end.x() != joinStation.x() || end.z() != joinStation.z()) {
int drop = last.downstreamHeadY() - stemHead;
HydrologyFeatureType type = drop == 0
? HydrologyFeatureType.SURFACE_POOL
: drop >= settings.hydraulics().waterfallMinimumDrop()
? HydrologyFeatureType.WATERFALL
: drop == 1 ? HydrologyFeatureType.RIFFLE : HydrologyFeatureType.CASCADE;
retained.add(new HydraulicSegment(
HydrologyHash.mix(course.id(), SEGMENT_SALT, 0x4a4f494eL),
course.id(),
type,
last.downstreamHeadY(),
stemHead,
last.width(),
last.depth(),
false,
false,
List.of(end, new HydrologyPoint(joinStation.x(), stemHead, joinStation.z()))
));
}
return new RiverCourse(
course.id(),
course.type(),
course.sourceNodeId(),
course.outletId(),
course.profileKey(),
course.discharge(),
course.drainageEdges().subList(0, ownedEdgeCount),
retained
);
}
private void addTributaryDiagnostic(
RiverCourse course,
HydrologyCandidateRejection rejection,
int detail,
List<HydrologyDiagnosticCandidate> diagnostics
) {
diagnostics.add(new HydrologyDiagnosticCandidate(
HydrologyHash.mix(course.id(), DIAGNOSTIC_SALT, rejection.ordinal(), 0x5452494255544152L),
HydrologyCandidateKind.SOURCE,
HydrologyFeatureType.SURFACE_POOL,
course.segments().getFirst().start(),
rejection,
detail
));
}
private RiverCourse preferredTrunkCourse(RiverCourse first, RiverCourse second) {
double firstLength = edgeLength(first.drainageEdges(), first.drainageEdges().size());
double secondLength = edgeLength(second.drainageEdges(), second.drainageEdges().size());
@@ -2161,7 +2300,8 @@ public final class HydrologyPlanner {
? Math.max(1, sourceSettings.maximumPerTile())
: sourceSettings.maximumPerTile();
target = Math.min(maximum, target);
target = effectiveSourceTarget(surface && !enforceGlobalSpacing, target, routing.outlets().size());
int coursesPerOutlet = surface ? 1 + settings.routing().tributaries() : Integer.MAX_VALUE;
target = effectiveSourceTarget(surface && !enforceGlobalSpacing, target, routing.outlets().size(), coursesPerOutlet);
Comparator<SourceCandidate> candidateOrder = Comparator
.comparing(SourceCandidate::required)
.reversed();
@@ -2170,7 +2310,7 @@ public final class HydrologyPlanner {
.thenComparingLong(SourceCandidate::stableId));
prioritizeInlandSource(candidates, routing);
int guaranteed = Math.min(target, requiredMinimum);
int maximumCoursesPerOutlet = surface ? 1 : Integer.MAX_VALUE;
int maximumCoursesPerOutlet = coursesPerOutlet;
SourceAdmissionSelection admission = selectSourceAdmissionsByOutlet(
candidates,
routing,
@@ -2209,10 +2349,15 @@ public final class HydrologyPlanner {
}
static int effectiveSourceTarget(boolean outletBounded, int requestedTarget, int outletCount) {
if (requestedTarget < 0 || outletCount < 0) {
return effectiveSourceTarget(outletBounded, requestedTarget, outletCount, 1);
}
static int effectiveSourceTarget(boolean outletBounded, int requestedTarget, int outletCount, int coursesPerOutlet) {
if (requestedTarget < 0 || outletCount < 0 || coursesPerOutlet < 1) {
throw new IllegalArgumentException("Source target bounds cannot be negative.");
}
return outletBounded ? Math.min(requestedTarget, outletCount) : requestedTarget;
long bound = (long) outletCount * coursesPerOutlet;
return outletBounded ? (int) Math.min(requestedTarget, bound) : requestedTarget;
}
private SourceAdmissionSelection selectSourceAdmissionsByOutlet(
@@ -4144,7 +4289,11 @@ public final class HydrologyPlanner {
.thenComparingLong(SurfaceCourseDraft::courseId));
RiverCourse mainCourse = null;
HashMap<Long, SurfaceCourseBuild> mainRejections = new HashMap<>();
int tributaries = 0;
for (SurfaceCourseDraft draft : outletDrafts) {
if (mainCourse != null && tributaries >= settings.routing().tributaries()) {
break;
}
SurfaceCourseBuild candidate = buildSurfaceCourse(
draft.courseId(),
draft.profileKey(),
@@ -4152,12 +4301,19 @@ public final class HydrologyPlanner {
draft.path()
);
if (candidate.course() == null) {
mainRejections.put(draft.courseId(), candidate);
if (mainCourse == null) {
mainRejections.put(draft.courseId(), candidate);
}
continue;
}
mainCourse = candidate.course();
courses.add(mainCourse);
break;
if (mainCourse == null) {
mainCourse = candidate.course();
} else {
// A later draft to the same outlet shares the stem's tail; shared-trunk
// normalisation cuts it at the confluence into a tributary.
tributaries++;
}
courses.add(candidate.course());
}
if (mainCourse == null) {
for (SurfaceCourseDraft draft : outletDrafts) {
@@ -4236,8 +4392,11 @@ public final class HydrologyPlanner {
segments
);
}
if (path.organicSurfaceRequired() && !surfaceShapeAccepted(path.points())) {
return SurfaceCourseBuild.rejected(HydrologyCandidateRejection.SURFACE_SHAPE_UNSUPPORTED, 0);
if (path.organicSurfaceRequired()) {
int shapeRejection = surfaceShapeRejection(path.points());
if (shapeRejection != 0) {
return SurfaceCourseBuild.rejected(HydrologyCandidateRejection.SURFACE_SHAPE_UNSUPPORTED, shapeRejection);
}
}
int discharge = maximumSurfaceDischarge(path.edges());
return SurfaceCourseBuild.accepted(new RiverCourse(
@@ -4252,10 +4411,11 @@ public final class HydrologyPlanner {
));
}
private boolean surfaceShapeAccepted(List<HydrologyPoint> routedCenterline) {
/** 0 when the shape is organic; otherwise 1 too short, 2 too sinuous, 3 grid-locked, 4 sharp turns. */
private int surfaceShapeRejection(List<HydrologyPoint> routedCenterline) {
List<HydrologyPoint> centerline = withoutDuplicateRoutePoints(routedCenterline);
if (centerline.size() < 3) {
return false;
return 1;
}
double routedLength = centerlineLength(centerline);
HydrologyPoint start = centerline.getFirst();
@@ -4263,7 +4423,7 @@ public final class HydrologyPlanner {
double directLength = StrictMath.hypot(end.x() - start.x(), end.z() - start.z());
if (directLength <= 0D
|| routedLength / directLength > SURFACE_MAXIMUM_SINUOSITY) {
return false;
return 2;
}
List<HydrologyPoint> sampled = resampleRouteCenterline(
centerline,
@@ -4298,7 +4458,7 @@ public final class HydrologyPlanner {
if (routedLength >= 96D
&& (lockedLength / routedLength > SURFACE_MAXIMUM_GRID_LOCKED_FRACTION
|| longestLockedRun > SURFACE_MAXIMUM_GRID_LOCKED_RUN)) {
return false;
return 3;
}
ArrayList<Double> turnAngles = new ArrayList<>();
for (int pointIndex = 1; pointIndex < sampled.size() - 1; pointIndex++) {
@@ -4308,12 +4468,12 @@ public final class HydrologyPlanner {
sampled.get(pointIndex + 1)
);
if (turn > SURFACE_MAXIMUM_RENDERED_TURN_DEGREES) {
return false;
return 4;
}
turnAngles.add(turn);
}
if (turnAngles.isEmpty()) {
return false;
return 1;
}
for (int turnIndex = 0; turnIndex < turnAngles.size(); turnIndex++) {
double turn = turnAngles.get(turnIndex);
@@ -4322,7 +4482,7 @@ public final class HydrologyPlanner {
if (turn > SURFACE_MAXIMUM_ISOLATED_TURN_DEGREES
&& previous <= SURFACE_MAXIMUM_ISOLATED_NEIGHBOR_TURN_DEGREES
&& next <= SURFACE_MAXIMUM_ISOLATED_NEIGHBOR_TURN_DEGREES) {
return false;
return 4;
}
}
turnAngles.sort(Double::compare);
@@ -4331,9 +4491,9 @@ public final class HydrologyPlanner {
Math.max(0, Math.min(percentileIndex, turnAngles.size() - 1))
);
if (percentile > SURFACE_MAXIMUM_P95_TURN_DEGREES) {
return false;
return 4;
}
return true;
return 0;
}
private double centerlineLength(List<HydrologyPoint> points) {
@@ -37,7 +37,7 @@ public record HydrologyPlannerSettings(
Source undergroundSources = new Source(true, 0.25D, Integer.MIN_VALUE, 0, 1, 512);
return new HydrologyPlannerSettings(
63,
new Routing(2048, 64, 8192, 8192, 384, 192, 1.5D, 24D, 2D, 0.2D, 1D),
new Routing(2048, 64, 8192, 8192, 384, 192, 1.5D, 24D, 2D, 0.2D, 1D, 0),
new Surface(true, surfaceSources, 4, 8, 2, 4, 10, 1.5D, Banks.defaults()),
new Hydraulics(8),
new Underground(true, undergroundSources, -48, 72, 3, 8, 1, 3, 6, 14, true),
@@ -166,7 +166,8 @@ public record HydrologyPlannerSettings(
double uphillPenalty,
double slopePenalty,
double confluenceAttraction,
double lengthPreference
double lengthPreference,
int tributaries
) {
public Routing {
if (tileSize < 32 || sampleSpacing < 4 || tileSize % sampleSpacing != 0) {
@@ -193,6 +194,9 @@ public record HydrologyPlannerSettings(
requireFiniteNonNegative(slopePenalty, "slopePenalty");
requireFiniteNonNegative(confluenceAttraction, "confluenceAttraction");
requireFiniteNonNegative(lengthPreference, "lengthPreference");
if (tributaries < 0) {
throw new IllegalArgumentException("tributaries must not be negative.");
}
}
// Route refinement is derived from the lattice, never authored.
@@ -677,7 +677,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
routing.getUphillPenalty(),
routing.getSlopePenalty(),
routing.getConfluenceAttraction(),
routing.getLengthPreference()
routing.getLengthPreference(),
routing.getTributaries()
);
HydrologyPlannerSettings.Source plannerSurfaceSources = new HydrologyPlannerSettings.Source(
surfaceEnabled,
@@ -75,4 +75,9 @@ public class IrisRiverRoutingConfig {
@MaxNumber(8)
@Desc("How strongly longer source-to-outlet routes are preferred when choosing river sources; 0 ranks by elevation alone.")
private double lengthPreference = 1D;
@MinNumber(0)
@MaxNumber(4)
@Desc("Extra surface courses an outlet may accept as tributaries joining its main river; 0 keeps one river per outlet.")
private int tributaries = 1;
}
@@ -252,7 +252,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
);
return new HydrologyPlannerSettings(
63,
new HydrologyPlannerSettings.Routing(256, 16, 1_089, 256, 16, 8, 0.5D, 12D, 0.5D, 0.2D, 1D),
new HydrologyPlannerSettings.Routing(256, 16, 1_089, 256, 16, 8, 0.5D, 12D, 0.5D, 0.2D, 1D, 0),
new HydrologyPlannerSettings.Surface(
true,
surfaceSources,
@@ -668,7 +668,7 @@ public class HydrologyPlannerGlobalInvariantTest {
12D,
0.5D,
0.1D
, 1D),
, 1D, 0),
new HydrologyPlannerSettings.Surface(
surfaceEnabled,
surfaceSources,
@@ -79,7 +79,7 @@ public class HydrologyPlannerSettingsTest {
0D,
0D,
0D
, 1D);
, 1D, 0);
HydrologyPlannerSettings.Source sources = new HydrologyPlannerSettings.Source(true, 1D, 0, 0, 1, 0);
HydrologyPlannerSettings.Surface surface = new HydrologyPlannerSettings.Surface(
true,
@@ -102,7 +102,7 @@ public class HydrologyPlannerSurfacePoolTest {
private static HydrologyPlannerSettings settings(double density, int spacing, int minimumRadius, int maximumRadius, int depth) {
HydrologyPlannerSettings base = HydrologyPlannerSettings.defaults();
HydrologyPlannerSettings.Routing routing = new HydrologyPlannerSettings.Routing(
1024, 64, base.routing().maximumRouteNodes(), 4096, 384, 192, 1.5D, 24D, 2D, 0.2D, 1D);
1024, 64, base.routing().maximumRouteNodes(), 4096, 384, 192, 1.5D, 24D, 2D, 0.2D, 1D, 0);
return new HydrologyPlannerSettings(
base.seaLevel(),
routing,
@@ -581,7 +581,7 @@ public class HydrologyPlannerTest {
routing.valleyPreference(),
routing.uphillPenalty(),
routing.slopePenalty(),
routing.confluenceAttraction(), 1D
routing.confluenceAttraction(), 1D, 0
),
base.surface(),
base.hydraulics(),
@@ -1733,7 +1733,7 @@ public class HydrologyPlannerTest {
);
return new HydrologyPlannerSettings(
63,
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 0, 0, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 0, 0, 0.5D, 12D, 0.5D, 0.1D, 1D, 0),
new HydrologyPlannerSettings.Surface(
surfaceDensity > 0D || surfaceSources.maximumPerTile() > 0,
surfaceSources,
@@ -1800,7 +1800,7 @@ public class HydrologyPlannerTest {
24D,
2D,
0.2D
, 1D),
, 1D, 0),
new HydrologyPlannerSettings.Surface(
true,
new HydrologyPlannerSettings.Source(true, 8D, 80, 0, 8, 128),
@@ -0,0 +1,152 @@
package art.arcane.iris.engine.hydrology;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
public class HydrologyPlannerTributaryTest {
private static final HydrologyTileKey TILE = new HydrologyTileKey(0, 0);
@Test
public void aSecondSourceJoinsTheMainStemAsATributaryWhenAllowed() {
HydrologyTerrainSampler terrain = HydrologyPlannerTributaryTest::valley;
HydrologyTile single = new HydrologyPlanner(19L, settings(0), terrain).plan(TILE);
HydrologyTile split = new HydrologyPlanner(19L, settings(1), terrain).plan(TILE);
List<RiverCourse> singleCourses = surface(single);
List<RiverCourse> splitCourses = surface(split);
assertEquals(single.diagnosticCandidates().toString(), 1, singleCourses.size());
assertEquals(split.diagnosticCandidates().toString(), 2, splitCourses.size());
RiverCourse stem = null;
RiverCourse tributary = null;
for (RiverCourse course : splitCourses) {
if (stem == null || stations(course) > stations(stem)) {
tributary = stem;
stem = course;
} else {
tributary = course;
}
}
assertNotNull(stem);
assertNotNull(tributary);
assertEquals(stem.outletId(), tributary.outletId());
assertTrue(stem.sourceNodeId().getAsLong() != tributary.sourceNodeId().getAsLong());
HydrologyPoint confluence = tributary.segments().getLast().end();
Integer stemHead = null;
for (HydraulicSegment segment : stem.segments()) {
for (HydrologyPoint point : segment.centerline()) {
if (point.x() == confluence.x() && point.z() == confluence.z()) {
stemHead = point.y();
}
}
}
assertNotNull("tributary ends on the stem", stemHead);
assertTrue(confluence.y() >= stemHead);
assertTrue(stations(tributary) >= 192);
assertTrue(stations(tributary) < stations(stem));
for (HydraulicSegment segment : tributary.segments()) {
assertTrue(segment.upstreamHeadY() >= segment.downstreamHeadY());
}
}
private static int stations(RiverCourse course) {
int stations = 0;
for (HydraulicSegment segment : course.segments()) {
stations += segment.centerline().size();
}
return stations;
}
private static List<RiverCourse> surface(HydrologyTile tile) {
ArrayList<RiverCourse> courses = new ArrayList<>();
for (RiverCourse course : tile.courses()) {
if (course.type() == RiverCourseType.SURFACE) {
courses.add(course);
}
}
return courses;
}
/** A plain sloping east into the sea with a valley along z = 0 that both flanks drain into. */
private static HydrologyTerrainSample valley(int x, int z) {
if (x >= 900) {
return HydrologyTerrainSample.ocean(50, "sea");
}
// The valley floor wanders so the stem never runs straight along the lattice, and the eastward
// fall is steeper than the valley flanks so routes drift into the valley at a shallow angle.
int valleyCenter = (int) Math.round(Math.sin(x / 150D) * 40D);
int valley = Math.max(0, 12 - Math.abs(z - valleyCenter) / 20);
int wave = (int) Math.round(Math.sin(x / 29D + z / 43D) * 3D + Math.sin(z / 19D - x / 61D + 1D) * 2D);
int height = 240 - x / 5 - valley + wave;
boolean source = x >= 32 && x <= 96 && (Math.abs(z - 300) <= 40 || Math.abs(z - 150) <= 40);
return new HydrologyTerrainSample(
height,
0D,
false,
false,
height - 40,
height - 38,
true,
true,
source,
false,
false,
false,
0D,
source ? 1D : 0D,
0D,
1D,
1D,
1D,
1D,
1D,
"land",
"land",
"land",
"land",
"land",
"land",
List.of("default"),
List.of()
);
}
private static HydrologyPlannerSettings settings(int tributaries) {
HydrologyPlannerSettings base = HydrologyPlannerSettings.defaults();
HydrologyPlannerSettings.Routing routing = new HydrologyPlannerSettings.Routing(
1024, 64, base.routing().maximumRouteNodes(), 4096, 384, 192, 1.5D, 24D, 2D, 0.2D, 1D, tributaries);
HydrologyPlannerSettings.Source sources = new HydrologyPlannerSettings.Source(true, 4D, 0, 0, 4, 128);
HydrologyPlannerSettings.Surface surface = new HydrologyPlannerSettings.Surface(
true, sources, 4, 8, 2, 4, 40, 1.5D, HydrologyPlannerSettings.Banks.defaults());
HydrologyPlannerSettings.Underground underground = new HydrologyPlannerSettings.Underground(
false, base.underground().sources(), -48, 72, 3, 8, 1, 3, 6, 14, true);
HydrologyPlannerSettings.Outlets outlets = new HydrologyPlannerSettings.Outlets(
true,
base.outlets().coastalGrotto(),
base.outlets().inlandGrotto(),
false,
base.outlets().coastalCliffMinimumHeight(),
base.outlets().mouthLevelingDistance(),
base.outlets().maximumOceanApron(),
1
);
return new HydrologyPlannerSettings(
base.seaLevel(),
routing,
surface,
base.hydraulics(),
underground,
outlets,
base.geometry(),
List.of(),
List.of()
);
}
}
@@ -195,7 +195,7 @@ public class HydrologyStyledGeometryTest {
);
return new HydrologyPlannerSettings(
63,
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D, 0),
new HydrologyPlannerSettings.Surface(
surfaceDensity > 0D,
surfaceSources,
@@ -243,7 +243,7 @@ public class HydrologyTileCacheTest {
);
return new HydrologyPlannerSettings(
63,
new HydrologyPlannerSettings.Routing(64, 16, 128, 64, 0, 0, 0D, 0D, 0D, 0D, 1D),
new HydrologyPlannerSettings.Routing(64, 16, 128, 64, 0, 0, 0D, 0D, 0D, 0D, 1D, 0),
new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 4, 1D,
HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(3),
@@ -282,7 +282,7 @@ public class HydrologyTileCacheTest {
);
return new HydrologyPlannerSettings(
63,
new HydrologyPlannerSettings.Routing(64, 16, 128, 96, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Routing(64, 16, 128, 96, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D, 0),
new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 20, 1.5D, tileBoundedBanks()),
new HydrologyPlannerSettings.Hydraulics(3),
new HydrologyPlannerSettings.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false),
@@ -1530,7 +1530,7 @@ public class MantleHydrologyComponentTest {
);
return new HydrologyPlannerSettings(
63,
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D, 0),
new HydrologyPlannerSettings.Surface(
true,
surfaceSources,
@@ -229,7 +229,7 @@ public final class HydrologyPlannerProbe {
12D,
0.5D,
0.1D
, 1D),
, 1D, 0),
new HydrologyPlannerSettings.Surface(
true,
surfaceSources,
@@ -440,7 +440,7 @@ public final class HydrologyPlannerProbe {
12D,
0.5D,
0.2D
, 1D),
, 1D, 0),
new HydrologyPlannerSettings.Surface(
true,
surfaceSources,