Prefer longer routes when ranking river sources

routing.lengthPreference adds route length to the source score so outlets take their longest legal course.
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-01 23:33:15 -04:00
1 parent 6945cbfd9b
commit 4b497a7d20
13 files changed
+28 -17

No files matched your search

@@ -278,6 +278,7 @@ final class PackHydrologyValidator {
PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "uphillPenalty", 0D, 128D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "uphillPenalty", 0D, 128D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "slopePenalty", 0D, 16D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "slopePenalty", 0D, 16D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "confluenceAttraction", 0D, 1D, errors); PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "confluenceAttraction", 0D, 1D, errors);
PackJsonFieldChecks.validateOptionalDoubleRange(path, routing, "lengthPreference", 0D, 8D, errors);
int tileSize = integerValue(routing, "tileSize", 2048); int tileSize = integerValue(routing, "tileSize", 2048);
int sampleSpacing = integerValue(routing, "sampleSpacing", 64); int sampleSpacing = integerValue(routing, "sampleSpacing", 64);
@@ -2113,9 +2113,10 @@ public final class HydrologyPlanner {
addSourceDiagnostic(node, surface, stable, HydrologyCandidateRejection.COURSE_TOO_SHORT, diagnostics); addSourceDiagnostic(node, surface, stable, HydrologyCandidateRejection.COURSE_TOO_SHORT, diagnostics);
continue; continue;
} }
// Elevation and route length share the same scale: 1000 route blocks count as 100 blocks of height.
double score = (required ? 1.0E15D : 0D) double score = (required ? 1.0E15D : 0D)
+ weight * 1.0E9D + weight * 1.0E9D
+ terrain.naturalHeight() * 1.0E5D + (terrain.naturalHeight() + routeLength * 0.1D * settings.routing().lengthPreference()) * 1.0E5D
+ routing.potential()[node.index()] + routing.potential()[node.index()]
+ HydrologyHash.unit(stable); + HydrologyHash.unit(stable);
candidates.add(new SourceCandidate(node.index(), stable, score, required)); candidates.add(new SourceCandidate(node.index(), stable, score, required));
@@ -35,7 +35,7 @@ public record HydrologyPlannerSettings(
Source undergroundSources = new Source(true, 0.25D, Integer.MIN_VALUE, 0, 1, 512); Source undergroundSources = new Source(true, 0.25D, Integer.MIN_VALUE, 0, 1, 512);
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new Routing(2048, 64, 8192, 8192, 384, 192, 1.5D, 24D, 2D, 0.2D), new Routing(2048, 64, 8192, 8192, 384, 192, 1.5D, 24D, 2D, 0.2D, 1D),
new Surface(true, surfaceSources, 4, 8, 2, 4, 10, 1.5D, Banks.defaults()), new Surface(true, surfaceSources, 4, 8, 2, 4, 10, 1.5D, Banks.defaults()),
new Hydraulics(8), new Hydraulics(8),
new Underground(true, undergroundSources, -48, 72, 3, 8, 1, 3, 6, 14, true), new Underground(true, undergroundSources, -48, 72, 3, 8, 1, 3, 6, 14, true),
@@ -151,7 +151,8 @@ public record HydrologyPlannerSettings(
double valleyPreference, double valleyPreference,
double uphillPenalty, double uphillPenalty,
double slopePenalty, double slopePenalty,
double confluenceAttraction double confluenceAttraction,
double lengthPreference
) { ) {
public Routing { public Routing {
if (tileSize < 32 || sampleSpacing < 4 || tileSize % sampleSpacing != 0) { if (tileSize < 32 || sampleSpacing < 4 || tileSize % sampleSpacing != 0) {
@@ -177,6 +178,7 @@ public record HydrologyPlannerSettings(
requireFiniteNonNegative(uphillPenalty, "uphillPenalty"); requireFiniteNonNegative(uphillPenalty, "uphillPenalty");
requireFiniteNonNegative(slopePenalty, "slopePenalty"); requireFiniteNonNegative(slopePenalty, "slopePenalty");
requireFiniteNonNegative(confluenceAttraction, "confluenceAttraction"); requireFiniteNonNegative(confluenceAttraction, "confluenceAttraction");
requireFiniteNonNegative(lengthPreference, "lengthPreference");
} }
// Route refinement is derived from the lattice, never authored. // Route refinement is derived from the lattice, never authored.
@@ -671,7 +671,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
routing.getValleyPreference(), routing.getValleyPreference(),
routing.getUphillPenalty(), routing.getUphillPenalty(),
routing.getSlopePenalty(), routing.getSlopePenalty(),
routing.getConfluenceAttraction() routing.getConfluenceAttraction(),
routing.getLengthPreference()
); );
HydrologyPlannerSettings.Source plannerSurfaceSources = new HydrologyPlannerSettings.Source( HydrologyPlannerSettings.Source plannerSurfaceSources = new HydrologyPlannerSettings.Source(
surfaceEnabled, surfaceEnabled,
@@ -70,4 +70,9 @@ public class IrisRiverRoutingConfig {
@MaxNumber(1) @MaxNumber(1)
@Desc("How strongly routes are drawn toward existing drainage.") @Desc("How strongly routes are drawn toward existing drainage.")
private double confluenceAttraction = 0.2D; private double confluenceAttraction = 0.2D;
@MinNumber(0)
@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;
} }
@@ -252,7 +252,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(256, 16, 1_089, 256, 16, 8, 0.5D, 12D, 0.5D, 0.2D), new HydrologyPlannerSettings.Routing(256, 16, 1_089, 256, 16, 8, 0.5D, 12D, 0.5D, 0.2D, 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
true, true,
surfaceSources, surfaceSources,
@@ -668,7 +668,7 @@ public class HydrologyPlannerGlobalInvariantTest {
12D, 12D,
0.5D, 0.5D,
0.1D 0.1D
), , 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
surfaceEnabled, surfaceEnabled,
surfaceSources, surfaceSources,
@@ -79,7 +79,7 @@ public class HydrologyPlannerSettingsTest {
0D, 0D,
0D, 0D,
0D 0D
); , 1D);
HydrologyPlannerSettings.Source sources = new HydrologyPlannerSettings.Source(true, 1D, 0, 0, 1, 0); HydrologyPlannerSettings.Source sources = new HydrologyPlannerSettings.Source(true, 1D, 0, 0, 1, 0);
HydrologyPlannerSettings.Surface surface = new HydrologyPlannerSettings.Surface( HydrologyPlannerSettings.Surface surface = new HydrologyPlannerSettings.Surface(
true, true,
@@ -581,7 +581,7 @@ public class HydrologyPlannerTest {
routing.valleyPreference(), routing.valleyPreference(),
routing.uphillPenalty(), routing.uphillPenalty(),
routing.slopePenalty(), routing.slopePenalty(),
routing.confluenceAttraction() routing.confluenceAttraction(), 1D
), ),
base.surface(), base.surface(),
base.hydraulics(), base.hydraulics(),
@@ -1733,7 +1733,7 @@ public class HydrologyPlannerTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 0, 0, 0.5D, 12D, 0.5D, 0.1D), new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 0, 0, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
surfaceDensity > 0D || surfaceSources.maximumPerTile() > 0, surfaceDensity > 0D || surfaceSources.maximumPerTile() > 0,
surfaceSources, surfaceSources,
@@ -1800,7 +1800,7 @@ public class HydrologyPlannerTest {
24D, 24D,
2D, 2D,
0.2D 0.2D
), , 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
true, true,
new HydrologyPlannerSettings.Source(true, 8D, 80, 0, 8, 128), new HydrologyPlannerSettings.Source(true, 8D, 80, 0, 8, 128),
@@ -96,7 +96,8 @@ public class HydrologyStyledGeometryTest {
widestSurfaceWidth = Math.max(widestSurfaceWidth, segment.width()); widestSurfaceWidth = Math.max(widestSurfaceWidth, segment.width());
} }
assertTrue(widestSurfaceWidth >= surfaceStart.width()); assertTrue(widestSurfaceWidth >= surfaceStart.width());
assertTrue(widestSurfaceWidth <= surfaceSettings.surface().maximumWidth() * 2); assertTrue(widestSurfaceWidth <= (int) Math.ceil(surfaceSettings.surface().maximumWidth()
* Math.max(2D, surfaceSettings.surface().banks().springWidthRatio())));
RiverCourse underground = courses(undergroundFirst, RiverCourseType.UNDERGROUND).getFirst(); RiverCourse underground = courses(undergroundFirst, RiverCourseType.UNDERGROUND).getFirst();
DrainageNode undergroundSource = undergroundFirst.node(underground.sourceNodeId().orElseThrow()).orElseThrow(); DrainageNode undergroundSource = undergroundFirst.node(underground.sourceNodeId().orElseThrow()).orElseThrow();
@@ -194,7 +195,7 @@ public class HydrologyStyledGeometryTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D), new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
surfaceDensity > 0D, surfaceDensity > 0D,
surfaceSources, surfaceSources,
@@ -243,7 +243,7 @@ public class HydrologyTileCacheTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(64, 16, 128, 64, 0, 0, 0D, 0D, 0D, 0D), new HydrologyPlannerSettings.Routing(64, 16, 128, 64, 0, 0, 0D, 0D, 0D, 0D, 1D),
new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 4, 1D, new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 4, 1D,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(3), new HydrologyPlannerSettings.Hydraulics(3),
@@ -282,7 +282,7 @@ public class HydrologyTileCacheTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(64, 16, 128, 96, 16, 8, 0.5D, 12D, 0.5D, 0.1D), new HydrologyPlannerSettings.Routing(64, 16, 128, 96, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 20, 1.5D, tileBoundedBanks()), new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 20, 1.5D, tileBoundedBanks()),
new HydrologyPlannerSettings.Hydraulics(3), new HydrologyPlannerSettings.Hydraulics(3),
new HydrologyPlannerSettings.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false), new HydrologyPlannerSettings.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false),
@@ -1530,7 +1530,7 @@ public class MantleHydrologyComponentTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D), new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D, 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
true, true,
surfaceSources, surfaceSources,
@@ -228,7 +228,7 @@ public final class HydrologyPlannerProbe {
12D, 12D,
0.5D, 0.5D,
0.1D 0.1D
), , 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
true, true,
surfaceSources, surfaceSources,
@@ -439,7 +439,7 @@ public final class HydrologyPlannerProbe {
12D, 12D,
0.5D, 0.5D,
0.2D 0.2D
), , 1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
true, true,
surfaceSources, surfaceSources,