Delete the dead v1 hydrology settings surface

- Surface inset, terrain blend and ridge tunnel settings gone; Hydraulics keeps only waterfallMinimumDrop
- Branching folded into Routing; refinementSpacing derived from the sample lattice
- Contained surface routing branch, surface incision helpers and 13 unreachable compiler members deleted
This commit is contained in:
Brian Neumann-Fopiano committed 2026-09-01 23:08:47 -04:00
1 parent 639786537b
commit 896e58e212
24 files changed
+254 -1360

No files matched your search

@@ -8,13 +8,11 @@ public enum HydrologyCandidateRejection {
SOURCE_QUOTA, SOURCE_QUOTA,
SOURCE_SPACING, SOURCE_SPACING,
OUTLET_LIMIT, OUTLET_LIMIT,
RIDGE_CONTAINMENT,
OUTLET_LEVEL, OUTLET_LEVEL,
CAVE_CONTAINMENT, CAVE_CONTAINMENT,
VOLUME_LIMIT, VOLUME_LIMIT,
SURFACE_HEAD_RANGE, SURFACE_HEAD_RANGE,
SURFACE_HEAD_DISTRIBUTION, SURFACE_HEAD_DISTRIBUTION,
SURFACE_RIDGE_TUNNEL_LIMIT,
SURFACE_EXPOSURE, SURFACE_EXPOSURE,
SURFACE_DROP_UNSUPPORTED, SURFACE_DROP_UNSUPPORTED,
SURFACE_CORRIDOR_UNSUPPORTED, SURFACE_CORRIDOR_UNSUPPORTED,
@@ -5,6 +5,8 @@ public enum HydrologyFeatureType {
RIFFLE, RIFFLE,
CASCADE, CASCADE,
WATERFALL, WATERFALL,
// No planner produces a ridge bore any more. The constant stays because its ordinal seeds the
// segment, outlet and feature ids of every constant after it on the cave and deep-fluid paths.
RIDGE_BORE, RIDGE_BORE,
UNDERGROUND_POOL, UNDERGROUND_POOL,
UNDERGROUND_DROP, UNDERGROUND_DROP,
@@ -12,19 +12,15 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set;
final class HydrologyFootprintCompiler { final class HydrologyFootprintCompiler {
private static final int FEATURE_ROLE_COUNT = 7; private static final int FEATURE_ROLE_COUNT = 7;
private static final int COURSE_FOOTPRINT_CACHE_SIZE = 8; private static final int COURSE_FOOTPRINT_CACHE_SIZE = 8;
private static final int VALIDATION_RASTER_CACHE_SIZE = 8; private static final int VALIDATION_RASTER_CACHE_SIZE = 8;
private static final int MINIMUM_ENDPOINT_PROFILE_LENGTH = 48;
private static final double MINIMUM_ENDPOINT_RAMP_WIDTHS = 2D;
private static final int[][] HORIZONTAL_NEIGHBORS = { private static final int[][] HORIZONTAL_NEIGHBORS = {
{1, 0}, {-1, 0}, {0, 1}, {0, -1} {1, 0}, {-1, 0}, {0, 1}, {0, -1}
}; };
@@ -53,7 +49,7 @@ final class HydrologyFootprintCompiler {
HydrologyPlannerSettings settings, HydrologyPlannerSettings settings,
HydrologyTerrainSampler sampler HydrologyTerrainSampler sampler
) { ) {
this(settings, sampler, HydrologyGeometrySampler.deterministic(0L, sampler)); this(settings, sampler, HydrologyGeometrySampler.deterministic(sampler));
} }
HydrologyFootprintCompiler( HydrologyFootprintCompiler(
@@ -172,404 +168,6 @@ final class HydrologyFootprintCompiler {
return fullMaterializationCount; return fullMaterializationCount;
} }
boolean surfaceHeadwaterRampSupported(RiverCourse course) {
Objects.requireNonNull(course, "course");
if (course.type() != RiverCourseType.SURFACE) {
return true;
}
if (settings.routing().branching().minimumSurfaceCourseLength()
< MINIMUM_ENDPOINT_PROFILE_LENGTH) {
return true;
}
RiverFootprint footprint = compileCourse(course);
List<HydrologyPoint> raster = surfaceCourseCenterline(course);
if (raster.size() < 2) {
return false;
}
HashSet<Long> ownedCells = new HashSet<>();
for (HydrologyColumnSample sample : footprint.columns().values()) {
HydrologyColumnLayer fluid = sample.primarySurfaceFluidLayer().orElse(null);
if (fluid != null
&& fluid.feature().courseId() == course.id()
&& fluid.feature().type().isSurface()
&& fluid.channel()
&& fluid.connectedFluid()
&& fluid.fluidOwned()
&& !fluid.oceanApron()) {
ownedCells.add(RiverFootprint.pack(sample.x(), sample.z()));
}
}
int maximumWidth = 1;
for (HydraulicSegment segment : course.segments()) {
if (segment.type().isSurface()) {
maximumWidth = Math.max(maximumWidth, segment.width());
}
}
HydrologyPoint source = raster.getFirst();
if (!ownedNear(ownedCells, source.x(), source.z(), maximumWidth)) {
return false;
}
int cruiseWidth = cruiseWidth(ownedCells, raster, maximumWidth);
double transitionLength = endpointTransitionLength(
ownedCells,
raster,
maximumWidth,
cruiseWidth
);
return transitionLength >= cruiseWidth * MINIMUM_ENDPOINT_RAMP_WIDTHS;
}
boolean surfaceIncisionContained(RiverCourse course) {
Objects.requireNonNull(course, "course");
if (course.type() != RiverCourseType.SURFACE) {
return true;
}
RiverFootprint footprint = compileCourse(course);
for (HydrologyColumnSample sample : footprint.columns().values()) {
HydrologyColumnLayer fluid = sample.primarySurfaceFluidLayer().orElse(null);
if (fluid == null
|| fluid.feature().courseId() != course.id()
|| fluid.feature().type() == HydrologyFeatureType.MOUTH
|| fluid.oceanApron()
|| fluid.fallingFluid()
|| !fluid.channel()
|| !fluid.fluidOwned()) {
continue;
}
HydrologyTerrainSample basis = sampleTerrainBasis(sample.x(), sample.z());
HydrologyTerrainSample terrain = sampleTerrain(sample.x(), sample.z());
if (terrain == null
|| basis == null
|| (terrain.naturalHeight() - fluid.fluidHeadY() > permittedSurfaceIncision(terrain)
&& basis.naturalHeight() - fluid.fluidHeadY() <= permittedSurfaceIncision(basis))) {
return false;
}
}
return true;
}
boolean surfaceIncisionContained(RiverCourse course, RiverFootprint footprint) {
Objects.requireNonNull(course, "course");
Objects.requireNonNull(footprint, "footprint");
if (course.type() != RiverCourseType.SURFACE) {
return true;
}
for (HydrologyColumnSample sample : footprint.columns().values()) {
for (HydrologyColumnLayer fluid : sample.layers()) {
if (fluid.feature().courseId() != course.id()
|| fluid.feature().type() == HydrologyFeatureType.MOUTH
|| fluid.oceanApron()
|| fluid.fallingFluid()
|| !fluid.channel()
|| !fluid.fluidOwned()) {
continue;
}
HydrologyTerrainSample basis = sampleTerrainBasis(sample.x(), sample.z());
HydrologyTerrainSample terrain = sampleTerrain(sample.x(), sample.z());
if (basis == null
|| terrain == null
|| (terrain.naturalHeight() - fluid.fluidHeadY()
> settings.surface().maximumIncision()
&& basis.naturalHeight() - fluid.fluidHeadY()
<= permittedSurfaceIncision(basis))) {
return false;
}
}
}
return true;
}
boolean surfaceBanksContained(RiverCourse course) {
Objects.requireNonNull(course, "course");
if (course.type() != RiverCourseType.SURFACE) {
return true;
}
RiverFootprint footprint = compileCourse(course);
if (!surfaceCenterlinePublished(course, footprint)
|| !surfaceTransitionCenterlinePublished(course, footprint)) {
return false;
}
for (HydrologyColumnSample sample : footprint.columns().values()) {
HydrologyColumnLayer fluid = sample.primarySurfaceFluidLayer().orElse(null);
if (fluid == null || fluid.oceanApron() || fluid.fallingFluid()) {
continue;
}
for (int[] offset : HORIZONTAL_NEIGHBORS) {
HydrologyColumnSample neighbor = footprint.sample(
sample.x() + offset[0],
sample.z() + offset[1]
).orElse(null);
if (ownsSurfaceFluid(neighbor, course.id())) {
continue;
}
if (neighbor != null
&& fluid.fluidHeadY() <= neighbor.seaLevel()
&& (neighbor.ocean() || neighbor.naturalHeight() <= neighbor.seaLevel())) {
continue;
}
if (neighbor == null || neighbor.terrainHeight() <= fluid.fluidHeadY()) {
return false;
}
}
}
return true;
}
private boolean surfaceCenterlinePublished(RiverCourse course, RiverFootprint footprint) {
for (HydraulicSegment segment : course.segments()) {
if (!segment.type().isSurface()) {
continue;
}
for (HydrologyPoint point : segment.centerline()) {
HydrologyColumnSample sample = footprint.sample(point.x(), point.z()).orElse(null);
if (ownsSurfaceFluid(sample, course.id())
|| segment.type() == HydrologyFeatureType.MOUTH
&& ownsOceanApron(sample, course.id())) {
continue;
}
return false;
}
}
return true;
}
private boolean surfaceTransitionCenterlinePublished(RiverCourse course, RiverFootprint footprint) {
for (int segmentIndex = 0; segmentIndex < course.segments().size(); segmentIndex++) {
HydraulicSegment segment = course.segments().get(segmentIndex);
if (!terrainRoofedSurfaceTransition(course, segment)) {
continue;
}
List<HydrologyPoint> centerline = continuousCenterline(segment);
for (int pointIndex = 0; pointIndex < centerline.size(); pointIndex++) {
HydrologyPoint point = centerline.get(pointIndex);
HydrologyColumnSample sample = footprint.sample(point.x(), point.z()).orElse(null);
if (ownsTransitionFluid(sample, course.id(), segment.id(), point.y())) {
continue;
}
boolean joinedStart = pointIndex == 0
&& segmentIndex > 0
&& segmentsJoin(course.segments().get(segmentIndex - 1), segment);
boolean joinedEnd = pointIndex == centerline.size() - 1
&& segmentIndex + 1 < course.segments().size()
&& segmentsJoin(segment, course.segments().get(segmentIndex + 1));
if ((joinedStart || joinedEnd) && ownsCourseFluid(sample, course.id(), point.y())) {
continue;
}
return false;
}
}
return true;
}
private boolean ownsTransitionFluid(
HydrologyColumnSample sample,
long courseId,
long segmentId,
int fluidHead
) {
if (sample == null) {
return false;
}
for (HydrologyColumnLayer layer : sample.layers()) {
if (layer.feature().courseId() == courseId
&& layer.feature().segmentId() == segmentId
&& layer.fluidHeadY() == fluidHead
&& layer.channel()
&& layer.connectedFluid()
&& layer.fluidOwned()) {
return true;
}
}
return false;
}
private boolean ownsCourseFluid(HydrologyColumnSample sample, long courseId, int fluidHead) {
if (sample == null) {
return false;
}
for (HydrologyColumnLayer layer : sample.layers()) {
if (layer.feature().courseId() == courseId
&& layer.fluidHeadY() == fluidHead
&& layer.channel()
&& layer.connectedFluid()
&& layer.fluidOwned()) {
return true;
}
}
return false;
}
private int permittedSurfaceIncision(HydrologyTerrainSample terrain) {
int maximumIncision = settings.surface().maximumIncision();
return Math.min(
maximumIncision,
(int) StrictMath.floor(maximumIncision * terrain.incisionMultiplier())
);
}
private boolean ownsSurfaceFluid(HydrologyColumnSample sample, long courseId) {
if (sample == null) {
return false;
}
HydrologyColumnLayer fluid = sample.primarySurfaceFluidLayer().orElse(null);
return fluid != null
&& fluid.fluidOwned()
&& fluid.feature().courseId() == courseId;
}
private boolean ownsOceanApron(HydrologyColumnSample sample, long courseId) {
if (sample == null) {
return false;
}
for (HydrologyColumnLayer layer : sample.layers()) {
if (layer.oceanApron() && layer.feature().courseId() == courseId) {
return true;
}
}
return false;
}
private List<HydrologyPoint> surfaceCourseCenterline(RiverCourse course) {
ArrayList<HydrologyPoint> points = new ArrayList<>();
for (HydraulicSegment segment : course.segments()) {
if (!segment.type().isSurface() && segment.type() != HydrologyFeatureType.RIDGE_BORE) {
break;
}
for (HydrologyPoint point : segment.centerline()) {
if (points.isEmpty()
|| point.x() != points.getLast().x()
|| point.z() != points.getLast().z()) {
points.add(point);
}
}
}
return rasterCenterline(points);
}
private List<HydrologyPoint> rasterCenterline(List<HydrologyPoint> points) {
ArrayList<HydrologyPoint> raster = new ArrayList<>();
for (int pairIndex = 0; pairIndex < points.size() - 1; pairIndex++) {
HydrologyPoint start = points.get(pairIndex);
HydrologyPoint end = points.get(pairIndex + 1);
int steps = Math.max(Math.abs(end.x() - start.x()), Math.abs(end.z() - start.z()));
if (steps == 0) {
continue;
}
int firstStep = raster.isEmpty() ? 0 : 1;
for (int step = firstStep; step <= steps; step++) {
double progress = step / (double) steps;
raster.add(new HydrologyPoint(
(int) StrictMath.round(start.x() + (end.x() - start.x()) * progress),
(int) StrictMath.round(start.y() + (end.y() - start.y()) * progress),
(int) StrictMath.round(start.z() + (end.z() - start.z()) * progress)
));
}
}
return List.copyOf(raster);
}
private boolean ownedNear(Set<Long> cells, int x, int z, int radius) {
for (int deltaZ = -radius; deltaZ <= radius; deltaZ++) {
for (int deltaX = -radius; deltaX <= radius; deltaX++) {
if (cells.contains(RiverFootprint.pack(x + deltaX, z + deltaZ))) {
return true;
}
}
}
return false;
}
private int cruiseWidth(Set<Long> cells, List<HydrologyPoint> raster, int maximumWidth) {
int start = Math.min(raster.size() - 2, Math.max(48, maximumWidth * 4));
int end = Math.min(raster.size() - 1, Math.max(start + 16, maximumWidth * 6));
ArrayList<Integer> widths = new ArrayList<>();
for (int offset = start; offset <= end; offset++) {
if (offset <= 0 || offset >= raster.size() - 1) {
continue;
}
int width = crossSectionWidth(
cells,
raster.get(offset),
raster.get(offset - 1),
raster.get(offset + 1),
maximumWidth
);
if (width > 0) {
widths.add(width);
}
}
if (widths.isEmpty()) {
return maximumWidth;
}
widths.sort(Integer::compare);
return Math.max(1, widths.get(widths.size() / 2));
}
private double endpointTransitionLength(
Set<Long> cells,
List<HydrologyPoint> raster,
int maximumWidth,
int cruiseWidth
) {
int endpointWidth = crossSectionWidth(
cells,
raster.getFirst(),
raster.getFirst(),
raster.get(1),
maximumWidth
);
int widthTolerance = Math.max(1, (int) StrictMath.ceil(cruiseWidth * 0.1D));
if (Math.abs(endpointWidth - cruiseWidth) <= widthTolerance) {
return 0D;
}
int search = Math.max(32, cruiseWidth * 6);
double distance = 0D;
int pointIndex = 1;
while (pointIndex < raster.size() && distance <= search) {
int previousIndex = pointIndex - 1;
int nextIndex = Math.min(raster.size() - 1, pointIndex + 1);
HydrologyPoint previous = raster.get(previousIndex);
HydrologyPoint station = raster.get(pointIndex);
HydrologyPoint next = raster.get(nextIndex);
distance += StrictMath.hypot(station.x() - previous.x(), station.z() - previous.z());
int width = crossSectionWidth(cells, station, previous, next, maximumWidth);
if (Math.abs(width - cruiseWidth) <= widthTolerance) {
return distance;
}
pointIndex++;
}
return distance;
}
private int crossSectionWidth(
Set<Long> cells,
HydrologyPoint center,
HydrologyPoint before,
HydrologyPoint after,
int maximumWidth
) {
double tangentX = after.x() - before.x();
double tangentZ = after.z() - before.z();
double tangentLength = StrictMath.hypot(tangentX, tangentZ);
if (tangentLength == 0D) {
tangentX = 1D;
tangentLength = 1D;
}
double perpendicularX = -tangentZ / tangentLength;
double perpendicularZ = tangentX / tangentLength;
int limit = Math.max(4, maximumWidth * 2);
HashSet<Long> sampled = new HashSet<>();
for (int offset = -limit; offset <= limit; offset++) {
int x = (int) StrictMath.round(center.x() + perpendicularX * offset);
int z = (int) StrictMath.round(center.z() + perpendicularZ * offset);
long packed = RiverFootprint.pack(x, z);
if (cells.contains(packed)) {
sampled.add(packed);
}
}
return sampled.size();
}
private ValidationCourseRaster compileValidationCourse(RiverCourse course) { private ValidationCourseRaster compileValidationCourse(RiverCourse course) {
CourseRasterKey rasterKey = new CourseRasterKey( CourseRasterKey rasterKey = new CourseRasterKey(
course.id(), course.id(),
@@ -825,7 +423,6 @@ final class HydrologyFootprintCompiler {
return false; return false;
} }
return segment.type().isSurface() return segment.type().isSurface()
|| segment.type() == HydrologyFeatureType.RIDGE_BORE
|| segment.type() == HydrologyFeatureType.UNDERGROUND_POOL || segment.type() == HydrologyFeatureType.UNDERGROUND_POOL
|| segment.type() == HydrologyFeatureType.UNDERGROUND_DROP || segment.type() == HydrologyFeatureType.UNDERGROUND_DROP
|| segment.type() == HydrologyFeatureType.SINKHOLE || segment.type() == HydrologyFeatureType.SINKHOLE
@@ -1145,21 +742,15 @@ final class HydrologyFootprintCompiler {
point.x(), point.x(),
point.z(), point.z(),
segment.id(), segment.id(),
settings.surface().minimumTerrainBlendWidth(), settings.surface().banks().minimumBlendWidth(),
settings.surface().maximumTerrainBlendWidth() settings.surface().banks().maximumBlendWidth()
); );
if (type == HydrologyFeatureType.WATERFALL) { if (type == HydrologyFeatureType.WATERFALL) {
shoreWidth = Math.min(2.5D, shoreWidth); shoreWidth = Math.min(2.5D, shoreWidth);
gradingWidth = Math.min(gradingWidth, Math.max(8D, channelRadius * 4D)); gradingWidth = Math.min(gradingWidth, Math.max(8D, channelRadius * 4D));
} }
} }
if (type == HydrologyFeatureType.RIDGE_BORE if (type == HydrologyFeatureType.UNDERGROUND_POOL
|| course.type() == RiverCourseType.SURFACE
&& type == HydrologyFeatureType.UNDERGROUND_DROP) {
ceiling = fluidHead + settings.surface().ridgeTunnelHeadroom();
archedChannel = !falling;
organicBoundary = true;
} else if (type == HydrologyFeatureType.UNDERGROUND_POOL
|| type == HydrologyFeatureType.UNDERGROUND_DROP || type == HydrologyFeatureType.UNDERGROUND_DROP
|| type == HydrologyFeatureType.SINKHOLE) { || type == HydrologyFeatureType.SINKHOLE) {
ceiling = fluidHead + geometrySampler.sample( ceiling = fluidHead + geometrySampler.sample(
@@ -1213,7 +804,7 @@ final class HydrologyFootprintCompiler {
if (course.type() == RiverCourseType.SURFACE && type.isSurface()) { if (course.type() == RiverCourseType.SURFACE && type.isSurface()) {
gradingWidth = Math.max( gradingWidth = Math.max(
gradingWidth, gradingWidth,
Math.min(settings.surface().maximumTerrainBlendWidth(), channelRadius * 2D) Math.min(settings.surface().banks().maximumBlendWidth(), channelRadius * 2D)
); );
} }
} }
@@ -1807,8 +1398,7 @@ final class HydrologyFootprintCompiler {
private boolean terrainRoofedSurfaceTransition(RiverCourse course, HydraulicSegment segment) { private boolean terrainRoofedSurfaceTransition(RiverCourse course, HydraulicSegment segment) {
return course.type() == RiverCourseType.SURFACE return course.type() == RiverCourseType.SURFACE
&& (segment.type() == HydrologyFeatureType.RIDGE_BORE && (segment.type() == HydrologyFeatureType.UNDERGROUND_POOL
|| segment.type() == HydrologyFeatureType.UNDERGROUND_POOL
|| segment.type() == HydrologyFeatureType.UNDERGROUND_DROP); || segment.type() == HydrologyFeatureType.UNDERGROUND_DROP);
} }
@@ -1943,8 +1533,7 @@ final class HydrologyFootprintCompiler {
} }
private HydrologyPlannerSettings.ChannelShape channelShape(HydrologyFeatureType type) { private HydrologyPlannerSettings.ChannelShape channelShape(HydrologyFeatureType type) {
if (type.isGrotto() || type == HydrologyFeatureType.DEEP_POOL if (type.isGrotto() || type == HydrologyFeatureType.DEEP_POOL) {
|| type == HydrologyFeatureType.RIDGE_BORE) {
return settings.geometry().grottos(); return settings.geometry().grottos();
} }
if (type.isUnderground() || type == HydrologyFeatureType.DEEP_CHANNEL) { if (type.isUnderground() || type == HydrologyFeatureType.DEEP_CHANNEL) {
@@ -37,10 +37,7 @@ public interface HydrologyGeometrySampler {
return value; return value;
} }
static HydrologyGeometrySampler deterministic( static HydrologyGeometrySampler deterministic(HydrologyTerrainSampler terrainSampler) {
long worldSeed,
HydrologyTerrainSampler terrainSampler
) {
Objects.requireNonNull(terrainSampler, "terrainSampler"); Objects.requireNonNull(terrainSampler, "terrainSampler");
return request -> { return request -> {
if (request.field() == Field.UNDERGROUND_FLUID_LEVEL) { if (request.field() == Field.UNDERGROUND_FLUID_LEVEL) {
@@ -52,7 +49,6 @@ public interface HydrologyGeometrySampler {
} }
if (request.field() == Field.SURFACE_WIDTH if (request.field() == Field.SURFACE_WIDTH
|| request.field() == Field.SURFACE_DEPTH || request.field() == Field.SURFACE_DEPTH
|| request.field() == Field.SURFACE_INSET
|| request.field() == Field.UNDERGROUND_WIDTH || request.field() == Field.UNDERGROUND_WIDTH
|| request.field() == Field.UNDERGROUND_DEPTH) { || request.field() == Field.UNDERGROUND_DEPTH) {
return request.minimum(); return request.minimum();
@@ -64,11 +60,6 @@ public interface HydrologyGeometrySampler {
request.z(), request.z(),
0x424c454e44L 0x424c454e44L
); );
case TARGET_POOL_LENGTH -> HydrologyHash.mix(
worldSeed,
request.stableId(),
0x504f4f4cL
);
case UNDERGROUND_HEADROOM -> HydrologyHash.mix( case UNDERGROUND_HEADROOM -> HydrologyHash.mix(
request.stableId(), request.stableId(),
request.x(), request.x(),
@@ -85,9 +76,7 @@ public interface HydrologyGeometrySampler {
enum Field { enum Field {
SURFACE_WIDTH, SURFACE_WIDTH,
SURFACE_DEPTH, SURFACE_DEPTH,
SURFACE_INSET,
SURFACE_BLEND_WIDTH, SURFACE_BLEND_WIDTH,
TARGET_POOL_LENGTH,
UNDERGROUND_FLUID_LEVEL, UNDERGROUND_FLUID_LEVEL,
UNDERGROUND_WIDTH, UNDERGROUND_WIDTH,
UNDERGROUND_DEPTH, UNDERGROUND_DEPTH,
@@ -67,7 +67,6 @@ public final class HydrologyPlanner {
private static final int SURFACE_COURSE_TRANSVERSE_CANDIDATES = 2; private static final int SURFACE_COURSE_TRANSVERSE_CANDIDATES = 2;
private static final int OPTIONAL_SOURCE_REJECTIONS_PER_TARGET = 4; private static final int OPTIONAL_SOURCE_REJECTIONS_PER_TARGET = 4;
private static final int SURFACE_OPTIONAL_SOURCE_REJECTIONS_PER_TARGET = 8; private static final int SURFACE_OPTIONAL_SOURCE_REJECTIONS_PER_TARGET = 8;
private static final double SURFACE_MAXIMUM_LOCAL_TURN_DEGREES = 35D;
private static final double SURFACE_MAXIMUM_RENDERED_TURN_DEGREES = 50D; private static final double SURFACE_MAXIMUM_RENDERED_TURN_DEGREES = 50D;
private static final double SURFACE_MAXIMUM_ISOLATED_TURN_DEGREES = 35D; private static final double SURFACE_MAXIMUM_ISOLATED_TURN_DEGREES = 35D;
private static final double SURFACE_MAXIMUM_ISOLATED_NEIGHBOR_TURN_DEGREES = 10D; private static final double SURFACE_MAXIMUM_ISOLATED_NEIGHBOR_TURN_DEGREES = 10D;
@@ -122,7 +121,7 @@ public final class HydrologyPlanner {
worldSeed, worldSeed,
settings, settings,
sampler, sampler,
HydrologyGeometrySampler.deterministic(worldSeed, sampler), HydrologyGeometrySampler.deterministic(sampler),
-4096, -4096,
footprint -> new HydrologyTerrainCaveVoxelView(sampler, settings.seaLevel(), -4096, 4096) footprint -> new HydrologyTerrainCaveVoxelView(sampler, settings.seaLevel(), -4096, 4096)
); );
@@ -138,7 +137,7 @@ public final class HydrologyPlanner {
worldSeed, worldSeed,
settings, settings,
sampler, sampler,
HydrologyGeometrySampler.deterministic(worldSeed, sampler), HydrologyGeometrySampler.deterministic(sampler),
0, 0,
footprint -> new HydrologyTerrainCaveVoxelView(sampler, settings.seaLevel(), worldHeight) footprint -> new HydrologyTerrainCaveVoxelView(sampler, settings.seaLevel(), worldHeight)
); );
@@ -154,7 +153,7 @@ public final class HydrologyPlanner {
worldSeed, worldSeed,
settings, settings,
sampler, sampler,
HydrologyGeometrySampler.deterministic(worldSeed, sampler), HydrologyGeometrySampler.deterministic(sampler),
Integer.MIN_VALUE, Integer.MIN_VALUE,
footprint -> caveView footprint -> caveView
); );
@@ -171,7 +170,7 @@ public final class HydrologyPlanner {
worldSeed, worldSeed,
settings, settings,
sampler, sampler,
HydrologyGeometrySampler.deterministic(worldSeed, sampler), HydrologyGeometrySampler.deterministic(sampler),
minimumY, minimumY,
caveViewFactory caveViewFactory
); );
@@ -235,7 +234,7 @@ public final class HydrologyPlanner {
this::sampleBasis, this::sampleBasis,
geometrySampler, geometrySampler,
settings.seaLevel(), settings.seaLevel(),
settings.routing().branching().minimumSurfaceCourseLength() settings.routing().minimumSurfaceCourseLength()
); );
} }
@@ -428,10 +427,7 @@ public final class HydrologyPlanner {
boolean fallbackSelected = false; boolean fallbackSelected = false;
while (fallbackOutletIndex < fallbackOutlets.size()) { while (fallbackOutletIndex < fallbackOutlets.size()) {
OutletCandidate fallbackOutlet = fallbackOutlets.get(fallbackOutletIndex++); OutletCandidate fallbackOutlet = fallbackOutlets.get(fallbackOutletIndex++);
for (int routeMode = 0; routeMode < 1; routeMode++) { RoutingPlan fallbackRouting = requireOrganicSurface(buildRouting(grid, List.of(fallbackOutlet)));
RoutingPlan fallbackRouting = routeMode == 0
? requireOrganicSurface(buildRouting(grid, List.of(fallbackOutlet)))
: buildContainedSurfaceRouting(grid, List.of(fallbackOutlet));
HashMap<HydrologyTileKey, SourceRoutingContext> fallbackContexts = new HashMap<>(); HashMap<HydrologyTileKey, SourceRoutingContext> fallbackContexts = new HashMap<>();
fallbackContexts.put(key, new SourceRoutingContext( fallbackContexts.put(key, new SourceRoutingContext(
grid, grid,
@@ -468,10 +464,6 @@ public final class HydrologyPlanner {
fallbackSelected = true; fallbackSelected = true;
break; break;
} }
if (fallbackSelected) {
break;
}
}
if (!fallbackSelected) { if (!fallbackSelected) {
break; break;
} }
@@ -1582,7 +1574,7 @@ public final class HydrologyPlanner {
settings.underground().maximumFluidY() settings.underground().maximumFluidY()
); );
if (settings.outlets().surfaceSinkholesEnabled() if (settings.outlets().surfaceSinkholesEnabled()
&& node.terrain().naturalHeight() - settings.surface().maximumSurfaceInset() <= poolY) { && node.terrain().naturalHeight() - settings.surface().banks().inset() <= poolY) {
continue; continue;
} }
long outletId = HydrologyHash.mix( long outletId = HydrologyHash.mix(
@@ -1712,7 +1704,7 @@ public final class HydrologyPlanner {
if (surface && settings.outlets().surfaceSinkholesEnabled()) { if (surface && settings.outlets().surfaceSinkholesEnabled()) {
poolY = Math.min( poolY = Math.min(
poolY, poolY,
landwardTerrain.naturalHeight() - settings.surface().maximumSurfaceInset() - 1 landwardTerrain.naturalHeight() - settings.surface().banks().inset() - 1
); );
poolY = Math.max( poolY = Math.max(
poolY, poolY,
@@ -1852,7 +1844,7 @@ public final class HydrologyPlanner {
&& downstream.terrain().surfaceSourceAllowed() && downstream.terrain().surfaceSourceAllowed()
&& downstream.terrain().naturalHeight() >= settings.surface().sources().minimumElevation() && downstream.terrain().naturalHeight() >= settings.surface().sources().minimumElevation()
&& distances[downstream.index()] && distances[downstream.index()]
>= settings.routing().branching().minimumSurfaceCourseLength()) { >= settings.routing().minimumSurfaceCourseLength()) {
capacity = Math.addExact( capacity = Math.addExact(
capacity, capacity,
1_000_000L 1_000_000L
@@ -1956,7 +1948,7 @@ public final class HydrologyPlanner {
OutletCandidate outlet = outlets.get(index); OutletCandidate outlet = outlets.get(index);
potential[outlet.landIndex()] = 0D; potential[outlet.landIndex()] = 0D;
outletIndex[outlet.landIndex()] = index; outletIndex[outlet.landIndex()] = index;
queue.add(new RoutingQueueEntry(outlet.landIndex(), 0D, index, Integer.MIN_VALUE)); queue.add(new RoutingQueueEntry(outlet.landIndex(), 0D, index));
} }
while (!queue.isEmpty()) { while (!queue.isEmpty()) {
RoutingQueueEntry currentEntry = queue.poll(); RoutingQueueEntry currentEntry = queue.poll();
@@ -1991,8 +1983,7 @@ public final class HydrologyPlanner {
queue.add(new RoutingQueueEntry( queue.add(new RoutingQueueEntry(
upstream.index(), upstream.index(),
candidatePotential, candidatePotential,
currentEntry.outletIndex(), currentEntry.outletIndex()
Integer.MIN_VALUE
)); ));
} }
} }
@@ -2002,7 +1993,6 @@ public final class HydrologyPlanner {
outletIndex, outletIndex,
routeLengths(grid, parent), routeLengths(grid, parent),
List.copyOf(outlets), List.copyOf(outlets),
false,
false false
); );
} }
@@ -2014,106 +2004,10 @@ public final class HydrologyPlanner {
routing.outletIndex(), routing.outletIndex(),
routing.routeLengths(), routing.routeLengths(),
routing.outlets(), routing.outlets(),
routing.containedSurface(),
true true
); );
} }
private RoutingPlan buildContainedSurfaceRouting(
SampledGrid grid,
List<OutletCandidate> outlets
) {
int nodeCount = grid.nodes().size();
double[] potential = new double[nodeCount];
int[] parent = new int[nodeCount];
int[] outletIndex = new int[nodeCount];
int[] minimumNeighborHeights = new int[nodeCount];
int[] requiredSurfaceHeads = new int[nodeCount];
for (GridNode node : grid.nodes()) {
minimumNeighborHeights[node.index()] = minimumNeighborHeight(grid, node);
}
Arrays.fill(potential, Double.POSITIVE_INFINITY);
Arrays.fill(parent, -1);
Arrays.fill(outletIndex, -1);
Arrays.fill(requiredSurfaceHeads, Integer.MAX_VALUE);
PriorityQueue<RoutingQueueEntry> queue = new PriorityQueue<>();
for (int index = 0; index < outlets.size(); index++) {
OutletCandidate outlet = outlets.get(index);
int requiredSurfaceHead = minimumSurfaceRoutingHead(grid.node(outlet.landIndex()));
if (requiredSurfaceHead > maximumSurfaceRoutingHead(grid.node(outlet.landIndex()))) {
continue;
}
potential[outlet.landIndex()] = 0D;
outletIndex[outlet.landIndex()] = index;
requiredSurfaceHeads[outlet.landIndex()] = requiredSurfaceHead;
queue.add(new RoutingQueueEntry(outlet.landIndex(), 0D, index, requiredSurfaceHead));
}
while (!queue.isEmpty()) {
RoutingQueueEntry currentEntry = queue.poll();
if (currentEntry.potential() != potential[currentEntry.nodeIndex()]
|| currentEntry.outletIndex() != outletIndex[currentEntry.nodeIndex()]
|| currentEntry.requiredSurfaceHead() != requiredSurfaceHeads[currentEntry.nodeIndex()]) {
continue;
}
GridNode downstream = grid.node(currentEntry.nodeIndex());
for (GridOffset offset : ROUTING_OFFSETS) {
GridNode upstream = grid.nodeAt(downstream.gridX() + offset.x(), downstream.gridZ() + offset.z());
if (upstream == null || upstream.terrain().ocean() || !upstream.terrain().transitAllowed()) {
continue;
}
int requiredSurfaceHead = currentEntry.requiredSurfaceHead();
if (maximumSurfaceRoutingHead(upstream) < requiredSurfaceHead) {
continue;
}
requiredSurfaceHead = Math.max(requiredSurfaceHead, minimumSurfaceRoutingHead(upstream));
double edgeCost = routeCost(
upstream,
downstream,
offset,
minimumNeighborHeights[downstream.index()]
);
double candidatePotential = currentEntry.potential() + edgeCost;
int existingParent = parent[upstream.index()];
boolean lowerSurfaceRequirement = requiredSurfaceHead < requiredSurfaceHeads[upstream.index()];
boolean equalSurfaceRequirement = requiredSurfaceHead == requiredSurfaceHeads[upstream.index()];
boolean replace = lowerSurfaceRequirement || equalSurfaceRequirement
&& candidatePotential < potential[upstream.index()] - 1.0E-9D;
if (!replace
&& equalSurfaceRequirement
&& StrictMath.abs(candidatePotential - potential[upstream.index()]) <= 1.0E-9D) {
replace = existingParent < 0 || downstream.id() < grid.node(existingParent).id();
}
if (!replace) {
continue;
}
potential[upstream.index()] = candidatePotential;
parent[upstream.index()] = downstream.index();
outletIndex[upstream.index()] = currentEntry.outletIndex();
requiredSurfaceHeads[upstream.index()] = requiredSurfaceHead;
queue.add(new RoutingQueueEntry(
upstream.index(),
candidatePotential,
currentEntry.outletIndex(),
requiredSurfaceHead
));
}
}
return new RoutingPlan(
potential,
parent,
outletIndex,
routeLengths(grid, parent),
List.copyOf(outlets),
true,
true
);
}
private int minimumSurfaceRoutingHead(GridNode node) {
int permittedIncision = permittedSurfaceIncision(node.terrain());
return Math.subtractExact(node.terrain().naturalHeight(), permittedIncision);
}
private int permittedSurfaceIncision(HydrologyTerrainSample terrain) { private int permittedSurfaceIncision(HydrologyTerrainSample terrain) {
return permittedSurfaceIncision( return permittedSurfaceIncision(
settings.surface().maximumIncision(), settings.surface().maximumIncision(),
@@ -2128,13 +2022,6 @@ public final class HydrologyPlanner {
); );
} }
private int maximumSurfaceRoutingHead(GridNode node) {
return Math.subtractExact(
node.terrain().naturalHeight(),
settings.surface().maximumSurfaceInset()
);
}
private double routeCost( private double routeCost(
GridNode upstream, GridNode upstream,
GridNode downstream, GridNode downstream,
@@ -2222,7 +2109,7 @@ public final class HydrologyPlanner {
addSourceDiagnostic(node, surface, stable, HydrologyCandidateRejection.ROUTE_LIMIT, diagnostics); addSourceDiagnostic(node, surface, stable, HydrologyCandidateRejection.ROUTE_LIMIT, diagnostics);
continue; continue;
} }
if (routeLength < settings.routing().branching().minimumCourseLength(surface)) { if (routeLength < settings.routing().minimumCourseLength(surface)) {
addSourceDiagnostic(node, surface, stable, HydrologyCandidateRejection.COURSE_TOO_SHORT, diagnostics); addSourceDiagnostic(node, surface, stable, HydrologyCandidateRejection.COURSE_TOO_SHORT, diagnostics);
continue; continue;
} }
@@ -2252,13 +2139,6 @@ public final class HydrologyPlanner {
Comparator<SourceCandidate> candidateOrder = Comparator Comparator<SourceCandidate> candidateOrder = Comparator
.comparing(SourceCandidate::required) .comparing(SourceCandidate::required)
.reversed(); .reversed();
if (surface && routing.containedSurface()) {
candidateOrder = candidateOrder.thenComparing(
Comparator.comparingInt((SourceCandidate candidate) ->
routing.routeLengths()[candidate.nodeIndex()])
.reversed()
);
}
candidates.sort(candidateOrder candidates.sort(candidateOrder
.thenComparing(Comparator.comparingDouble(SourceCandidate::score).reversed()) .thenComparing(Comparator.comparingDouble(SourceCandidate::score).reversed())
.thenComparingLong(SourceCandidate::stableId)); .thenComparingLong(SourceCandidate::stableId));
@@ -2535,7 +2415,7 @@ public final class HydrologyPlanner {
return false; return false;
} }
int routeLength = routing.routeLengths()[node.index()]; int routeLength = routing.routeLengths()[node.index()];
return routeLength >= settings.routing().branching().minimumCourseLength(surface) return routeLength >= settings.routing().minimumCourseLength(surface)
&& routeLength <= settings.routing().maximumRouteLength(); && routeLength <= settings.routing().maximumRouteLength();
} }
@@ -2712,8 +2592,7 @@ public final class HydrologyPlanner {
downstreamAnchor.z(), downstreamAnchor.z(),
continuation.x(), continuation.x(),
continuation.z(), continuation.z(),
transverseCandidates, transverseCandidates
routing.containedSurface()
); );
centerline = refinedEdgeCache.get( centerline = refinedEdgeCache.get(
refinedEdgeKey, refinedEdgeKey,
@@ -2723,8 +2602,7 @@ public final class HydrologyPlanner {
upstreamAnchor, upstreamAnchor,
downstreamAnchor, downstreamAnchor,
continuation, continuation,
transverseCandidates, transverseCandidates
routing.containedSurface()
) )
); );
refinedEdges.put(edgeId, centerline); refinedEdges.put(edgeId, centerline);
@@ -2742,8 +2620,7 @@ public final class HydrologyPlanner {
downstreamAnchor.z(), downstreamAnchor.z(),
continuation.x(), continuation.x(),
continuation.z(), continuation.z(),
ROUTE_FALLBACK_TRANSVERSE_CANDIDATES, ROUTE_FALLBACK_TRANSVERSE_CANDIDATES
routing.containedSurface()
); );
centerline = refinedEdgeCache.get( centerline = refinedEdgeCache.get(
refinedEdgeKey, refinedEdgeKey,
@@ -2753,13 +2630,12 @@ public final class HydrologyPlanner {
upstreamAnchor, upstreamAnchor,
downstreamAnchor, downstreamAnchor,
continuation, continuation,
ROUTE_FALLBACK_TRANSVERSE_CANDIDATES, ROUTE_FALLBACK_TRANSVERSE_CANDIDATES
routing.containedSurface()
) )
); );
refinedEdges.put(edgeId, centerline); refinedEdges.put(edgeId, centerline);
} }
if (centerline.isEmpty() && !routing.containedSurface()) { if (centerline.isEmpty()) {
centerline = constrainedTerrainFallbackEdge( centerline = constrainedTerrainFallbackEdge(
routeAnchor(upstream), routeAnchor(upstream),
routeAnchor(downstream) routeAnchor(downstream)
@@ -2989,12 +2865,9 @@ public final class HydrologyPlanner {
HydrologyPoint upstream, HydrologyPoint upstream,
HydrologyPoint downstream, HydrologyPoint downstream,
HydrologyPoint continuation, HydrologyPoint continuation,
int transverseCandidates, int transverseCandidates
boolean containedSurface
) { ) {
int refinement = containedSurface int refinement = settings.routing().refinementSpacing();
? Math.max(1, settings.routing().refinementSpacing() / 2)
: settings.routing().refinementSpacing();
int effectiveTransverseCandidates = transverseCandidates; int effectiveTransverseCandidates = transverseCandidates;
double deltaX = downstream.x() - upstream.x(); double deltaX = downstream.x() - upstream.x();
double deltaZ = downstream.z() - upstream.z(); double deltaZ = downstream.z() - upstream.z();
@@ -3015,7 +2888,7 @@ public final class HydrologyPlanner {
if (elevationTransition) { if (elevationTransition) {
endTangent = directTangent; endTangent = directTangent;
} }
double wormBlend = elevationTransition || containedSurface ? 0D : routeWormBlend(); double wormBlend = elevationTransition ? 0D : routeWormBlend();
double tangentMagnitude = distance * (ROUTE_TANGENT_SCALE + wormBlend * 0.42D); double tangentMagnitude = distance * (ROUTE_TANGENT_SCALE + wormBlend * 0.42D);
if (wormBlend > 0D) { if (wormBlend > 0D) {
startTangent = routeWormDirection(upstream, startTangent, wormBlend); startTangent = routeWormDirection(upstream, startTangent, wormBlend);
@@ -3040,7 +2913,7 @@ public final class HydrologyPlanner {
tangentMagnitude, tangentMagnitude,
distance, distance,
progress, progress,
elevationTransition || containedSurface ? 0D : 1D elevationTransition ? 0D : 1D
); );
List<RouteCandidate> candidates = routeCandidates( List<RouteCandidate> candidates = routeCandidates(
upstreamId, upstreamId,
@@ -3048,15 +2921,14 @@ public final class HydrologyPlanner {
nominal, nominal,
progress, progress,
refinement, refinement,
elevationTransition ? 0 : effectiveTransverseCandidates, elevationTransition ? 0 : effectiveTransverseCandidates
containedSurface
); );
if (candidates.isEmpty()) { if (candidates.isEmpty()) {
return List.of(); return List.of();
} }
layers.add(candidates); layers.add(candidates);
} }
return solveTerrainRoute(layers, distance / steps, containedSurface); return solveTerrainRoute(layers, distance / steps);
} }
private double routeWormBlend() { private double routeWormBlend() {
@@ -3267,8 +3139,7 @@ public final class HydrologyPlanner {
RoutePosition nominal, RoutePosition nominal,
double progress, double progress,
int refinement, int refinement,
int transverseCandidates, int transverseCandidates
boolean containedSurface
) { ) {
double searchEnvelope = StrictMath.sin(StrictMath.PI * progress); double searchEnvelope = StrictMath.sin(StrictMath.PI * progress);
LinkedHashMap<Long, RouteCandidate> candidates = new LinkedHashMap<>(); LinkedHashMap<Long, RouteCandidate> candidates = new LinkedHashMap<>();
@@ -3279,14 +3150,7 @@ public final class HydrologyPlanner {
int x = (int) StrictMath.round(nominal.x() - nominal.tangent().z() * offset); int x = (int) StrictMath.round(nominal.x() - nominal.tangent().z() * offset);
int z = (int) StrictMath.round(nominal.z() + nominal.tangent().x() * offset); int z = (int) StrictMath.round(nominal.z() + nominal.tangent().x() * offset);
HydrologyTerrainSample terrain = sampleLandBasis(x, z); HydrologyTerrainSample terrain = sampleLandBasis(x, z);
if (terrain == null if (terrain == null || !terrain.transitAllowed()) {
|| !terrain.transitAllowed()
|| containedSurface && !containedSurfaceRouteCandidate(
x,
z,
terrain,
nominal.tangent()
)) {
continue; continue;
} }
long packed = RiverFootprint.pack(x, z); long packed = RiverFootprint.pack(x, z);
@@ -3311,13 +3175,7 @@ public final class HydrologyPlanner {
HydrologyTerrainSample fallbackTerrain = sampleLandBasis(fallbackX, fallbackZ); HydrologyTerrainSample fallbackTerrain = sampleLandBasis(fallbackX, fallbackZ);
if (candidates.isEmpty() if (candidates.isEmpty()
&& fallbackTerrain != null && fallbackTerrain != null
&& fallbackTerrain.transitAllowed() && fallbackTerrain.transitAllowed()) {
&& (!containedSurface || containedSurfaceRouteCandidate(
fallbackX,
fallbackZ,
fallbackTerrain,
nominal.tangent()
))) {
long packed = RiverFootprint.pack(fallbackX, fallbackZ); long packed = RiverFootprint.pack(fallbackX, fallbackZ);
double fallbackOffset = (nominal.baseX() - nominal.x()) * -nominal.tangent().z() double fallbackOffset = (nominal.baseX() - nominal.x()) * -nominal.tangent().z()
+ (nominal.baseZ() - nominal.z()) * nominal.tangent().x(); + (nominal.baseZ() - nominal.z()) * nominal.tangent().x();
@@ -3435,20 +3293,13 @@ public final class HydrologyPlanner {
return north + (south - north) * progressZ; return north + (south - north) * progressZ;
} }
private RouteCandidate resolveRouteCandidate(RouteCandidate candidate, boolean containedSurface) { private RouteCandidate resolveRouteCandidate(RouteCandidate candidate) {
if (candidate.terrainResolved()) { if (candidate.terrainResolved()) {
return candidate; return candidate;
} }
HydrologyPoint point = candidate.point(); HydrologyPoint point = candidate.point();
HydrologyTerrainSample terrain = sampleLandBasis(point.x(), point.z()); HydrologyTerrainSample terrain = sampleLandBasis(point.x(), point.z());
if (terrain == null if (terrain == null || !terrain.transitAllowed()) {
|| !terrain.transitAllowed()
|| containedSurface && !containedSurfaceRouteCandidate(
point.x(),
point.z(),
terrain,
candidate.tangent()
)) {
return new RouteCandidate( return new RouteCandidate(
point, point,
candidate.offset(), candidate.offset(),
@@ -3476,40 +3327,9 @@ public final class HydrologyPlanner {
+ terrain.routingCost() * terrain.routingMultiplier(); + terrain.routingCost() * terrain.routingMultiplier();
} }
private boolean containedSurfaceRouteCandidate(
int x,
int z,
HydrologyTerrainSample terrain,
Direction tangent
) {
if (terrain.slope() >= settings.hydraulics().waterfallMinimumDrop()) {
return true;
}
int bankDistance = surfaceBankDistance(settings.surface().maximumWidth());
int head = Math.subtractExact(
terrain.naturalHeight(),
settings.surface().maximumSurfaceInset()
);
HydrologyPoint point = new HydrologyPoint(x, terrain.naturalHeight(), z);
return surfaceBankSupported(
point,
tangent,
bankDistance,
head,
Integer.MAX_VALUE
) && surfaceBankSupported(
point,
tangent,
-bankDistance,
head,
Integer.MAX_VALUE
);
}
private List<HydrologyPoint> solveTerrainRoute( private List<HydrologyPoint> solveTerrainRoute(
List<List<RouteCandidate>> layers, List<List<RouteCandidate>> layers,
double targetStepLength, double targetStepLength
boolean containedSurface
) { ) {
ArrayList<List<RouteCandidate>> availableLayers = new ArrayList<>(layers); ArrayList<List<RouteCandidate>> availableLayers = new ArrayList<>(layers);
HashSet<TerrainTransitionKey> rejectedTransitions = new HashSet<>(); HashSet<TerrainTransitionKey> rejectedTransitions = new HashSet<>();
@@ -3517,19 +3337,16 @@ public final class HydrologyPlanner {
RouteCandidate[] route = selectTerrainRoute( RouteCandidate[] route = selectTerrainRoute(
availableLayers, availableLayers,
targetStepLength, targetStepLength,
containedSurface,
rejectedTransitions rejectedTransitions
); );
if (route.length == 0) { if (route.length == 0) {
return List.of(); return List.of();
} }
if (!containedSurface) {
TerrainTransitionKey unsupportedTransition = unsupportedTerrainTransition(route); TerrainTransitionKey unsupportedTransition = unsupportedTerrainTransition(route);
if (unsupportedTransition != null) { if (unsupportedTransition != null) {
rejectedTransitions.add(unsupportedTransition); rejectedTransitions.add(unsupportedTransition);
continue; continue;
} }
}
int pitRemoval = removeTerrainPits(availableLayers, route); int pitRemoval = removeTerrainPits(availableLayers, route);
if (pitRemoval < 0) { if (pitRemoval < 0) {
return List.of(); return List.of();
@@ -3549,19 +3366,15 @@ public final class HydrologyPlanner {
if (points.size() < 2) { if (points.size() < 2) {
return List.of(); return List.of();
} }
return containedSurface ? List.copyOf(points) : smoothTerrainRoute(points); return smoothTerrainRoute(points);
} }
} }
private RouteCandidate[] selectTerrainRoute( private RouteCandidate[] selectTerrainRoute(
List<List<RouteCandidate>> layers, List<List<RouteCandidate>> layers,
double targetStepLength, double targetStepLength,
boolean containedSurface,
Set<TerrainTransitionKey> rejectedTransitions Set<TerrainTransitionKey> rejectedTransitions
) { ) {
if (containedSurface) {
return selectContainedSurfaceTerrainRoute(layers, targetStepLength);
}
int layerCount = layers.size(); int layerCount = layers.size();
double[][] costs = new double[layerCount][]; double[][] costs = new double[layerCount][];
int[][] predecessors = new int[layerCount][]; int[][] predecessors = new int[layerCount][];
@@ -3630,27 +3443,9 @@ public final class HydrologyPlanner {
return null; return null;
} }
private RouteCandidate[] selectContainedSurfaceTerrainRoute(
List<List<RouteCandidate>> layers,
double targetStepLength
) {
double maximumTurn = Math.max(
SURFACE_MAXIMUM_LOCAL_TURN_DEGREES,
settings.geometry().meanders().maximumTurnDegrees()
);
return selectCurvatureAwareTerrainRoute(
layers,
targetStepLength,
true,
maximumTurn,
1.25D
);
}
private RouteCandidate[] selectCurvatureAwareTerrainRoute( private RouteCandidate[] selectCurvatureAwareTerrainRoute(
List<List<RouteCandidate>> layers, List<List<RouteCandidate>> layers,
double targetStepLength, double targetStepLength,
boolean containedSurface,
double maximumTurn, double maximumTurn,
double turnCost double turnCost
) { ) {
@@ -3663,7 +3458,7 @@ public final class HydrologyPlanner {
for (int layerIndex = 0; layerIndex < layerCount; layerIndex++) { for (int layerIndex = 0; layerIndex < layerCount; layerIndex++) {
int layerSize = layers.get(layerIndex).size(); int layerSize = layers.get(layerIndex).size();
localPenalties[layerIndex] = new double[layerSize]; localPenalties[layerIndex] = new double[layerSize];
if (!containedSurface && layerIndex > 0 && layerIndex < layerCount - 1) { if (layerIndex > 0 && layerIndex < layerCount - 1) {
Arrays.fill(localPenalties[layerIndex], Double.NaN); Arrays.fill(localPenalties[layerIndex], Double.NaN);
} }
if (layerIndex == 0) { if (layerIndex == 0) {
@@ -3690,7 +3485,6 @@ public final class HydrologyPlanner {
boolean evaluated = evaluateSelectedSurfaceCosts( boolean evaluated = evaluateSelectedSurfaceCosts(
selection, selection,
layers, layers,
containedSurface,
localPenalties, localPenalties,
transitionCosts, transitionCosts,
targetStepLength targetStepLength
@@ -3825,7 +3619,6 @@ public final class HydrologyPlanner {
private boolean evaluateSelectedSurfaceCosts( private boolean evaluateSelectedSurfaceCosts(
CurvatureRouteSelection selection, CurvatureRouteSelection selection,
List<List<RouteCandidate>> layers, List<List<RouteCandidate>> layers,
boolean containedSurface,
double[][] localPenalties, double[][] localPenalties,
double[][][] transitionCosts, double[][][] transitionCosts,
double targetStepLength double targetStepLength
@@ -3838,14 +3631,13 @@ public final class HydrologyPlanner {
if (candidate.terrainResolved()) { if (candidate.terrainResolved()) {
continue; continue;
} }
RouteCandidate resolved = resolveRouteCandidate(candidate, containedSurface); RouteCandidate resolved = resolveRouteCandidate(candidate);
ArrayList<RouteCandidate> updatedLayer = new ArrayList<>(layers.get(layerIndex)); ArrayList<RouteCandidate> updatedLayer = new ArrayList<>(layers.get(layerIndex));
updatedLayer.set(indices[layerIndex], resolved); updatedLayer.set(indices[layerIndex], resolved);
layers.set(layerIndex, List.copyOf(updatedLayer)); layers.set(layerIndex, List.copyOf(updatedLayer));
route[layerIndex] = resolved; route[layerIndex] = resolved;
evaluated = true; evaluated = true;
} }
if (!containedSurface) {
for (int layerIndex = 1; layerIndex < route.length - 1; layerIndex++) { for (int layerIndex = 1; layerIndex < route.length - 1; layerIndex++) {
int candidateIndex = indices[layerIndex]; int candidateIndex = indices[layerIndex];
if (!Double.isNaN(localPenalties[layerIndex][candidateIndex])) { if (!Double.isNaN(localPenalties[layerIndex][candidateIndex])) {
@@ -3860,7 +3652,6 @@ public final class HydrologyPlanner {
); );
evaluated = true; evaluated = true;
} }
}
for (int layerIndex = 1; layerIndex < route.length; layerIndex++) { for (int layerIndex = 1; layerIndex < route.length; layerIndex++) {
int previousIndex = indices[layerIndex - 1]; int previousIndex = indices[layerIndex - 1];
int currentIndex = indices[layerIndex]; int currentIndex = indices[layerIndex];
@@ -3873,14 +3664,9 @@ public final class HydrologyPlanner {
transitionCosts[layerIndex][previousIndex][currentIndex] = Double.POSITIVE_INFINITY; transitionCosts[layerIndex][previousIndex][currentIndex] = Double.POSITIVE_INFINITY;
continue; continue;
} }
double transition = routeTransitionCost( double transition = routeTransitionCost(previous, current, targetStepLength);
previous,
current,
targetStepLength,
containedSurface
);
if (Double.isFinite(transition)) { if (Double.isFinite(transition)) {
transition += surfaceRouteTransitionPenalty(previous, current, containedSurface); transition += surfaceRouteTransitionPenalty(previous, current);
} }
transitionCosts[layerIndex][previousIndex][currentIndex] = transition; transitionCosts[layerIndex][previousIndex][currentIndex] = transition;
evaluated = true; evaluated = true;
@@ -3910,17 +3696,13 @@ public final class HydrologyPlanner {
private double surfaceRouteTransitionPenalty( private double surfaceRouteTransitionPenalty(
RouteCandidate start, RouteCandidate start,
RouteCandidate end, RouteCandidate end
boolean containedSurface
) { ) {
if (containedSurface) {
return 0D;
}
int minimumBankDistance = surfaceBankDistance(settings.surface().minimumWidth()); int minimumBankDistance = surfaceBankDistance(settings.surface().minimumWidth());
int maximumBankDistance = surfaceBankDistance(settings.surface().maximumWidth()); int maximumBankDistance = surfaceBankDistance(settings.surface().maximumWidth());
int maximumBankRise = Math.addExact( int maximumBankRise = Math.addExact(
settings.surface().maximumIncision(), settings.surface().maximumIncision(),
(int) StrictMath.ceil(settings.surface().minimumTerrainBlendWidth() * 0.5D) (int) StrictMath.ceil(settings.surface().banks().minimumBlendWidth() * 0.5D)
); );
Direction tangent = direction(start.point().x(), start.point().z(), end.point().x(), end.point().z()); Direction tangent = direction(start.point().x(), start.point().z(), end.point().x(), end.point().z());
int endpointCeiling = Math.max(start.point().y(), end.point().y()); int endpointCeiling = Math.max(start.point().y(), end.point().y());
@@ -3944,7 +3726,7 @@ public final class HydrologyPlanner {
} }
int head = Math.subtractExact( int head = Math.subtractExact(
terrain.naturalHeight(), terrain.naturalHeight(),
settings.surface().maximumSurfaceInset() settings.surface().banks().inset()
); );
penalty += surfaceRouteBankPenalty( penalty += surfaceRouteBankPenalty(
point, point,
@@ -3970,7 +3752,7 @@ public final class HydrologyPlanner {
if (terrain.slope() < settings.hydraulics().waterfallMinimumDrop()) { if (terrain.slope() < settings.hydraulics().waterfallMinimumDrop()) {
int head = Math.subtractExact( int head = Math.subtractExact(
terrain.naturalHeight(), terrain.naturalHeight(),
settings.surface().maximumSurfaceInset() settings.surface().banks().inset()
); );
penalty += surfaceRouteBankPenalty( penalty += surfaceRouteBankPenalty(
point, point,
@@ -4024,7 +3806,7 @@ public final class HydrologyPlanner {
private List<HydrologyPoint> smoothTerrainRoute(List<HydrologyPoint> route) { private List<HydrologyPoint> smoothTerrainRoute(List<HydrologyPoint> route) {
List<HydrologyPoint> current = List.copyOf(route); List<HydrologyPoint> current = List.copyOf(route);
List<HydrologyPoint> best = current; List<HydrologyPoint> best = current;
double bestTurnScore = containedSurfaceTurnScore(current); double bestTurnScore = routeTurnScore(current);
HydrologyPlannerSettings.Meanders meanders = settings.geometry().meanders(); HydrologyPlannerSettings.Meanders meanders = settings.geometry().meanders();
int maximumPasses = Math.max(8, meanders.smoothingPasses() * 4); int maximumPasses = Math.max(8, meanders.smoothingPasses() * 4);
for (int pass = 0; pass < maximumPasses; pass++) { for (int pass = 0; pass < maximumPasses; pass++) {
@@ -4047,7 +3829,7 @@ public final class HydrologyPlanner {
break; break;
} }
current = candidate; current = candidate;
double candidateTurnScore = containedSurfaceTurnScore(candidate); double candidateTurnScore = routeTurnScore(candidate);
if (candidateTurnScore < bestTurnScore) { if (candidateTurnScore < bestTurnScore) {
best = candidate; best = candidate;
bestTurnScore = candidateTurnScore; bestTurnScore = candidateTurnScore;
@@ -4136,13 +3918,9 @@ public final class HydrologyPlanner {
private double routeTransitionCost( private double routeTransitionCost(
RouteCandidate previous, RouteCandidate previous,
RouteCandidate current, RouteCandidate current,
double targetStepLength, double targetStepLength
boolean containedSurface
) { ) {
boolean supported = containedSurface if (!traversableTerrainTransition(previous.point(), current.point())) {
? containedSurfaceSolverTransitionSupported(previous.point(), current.point())
: traversableTerrainTransition(previous.point(), current.point());
if (!supported) {
return Double.POSITIVE_INFINITY; return Double.POSITIVE_INFINITY;
} }
return routeTransitionGeometryCost(previous, current, targetStepLength); return routeTransitionGeometryCost(previous, current, targetStepLength);
@@ -4192,81 +3970,6 @@ public final class HydrologyPlanner {
return true; return true;
} }
private boolean containedSurfaceSolverTransitionSupported(
HydrologyPoint start,
HydrologyPoint end
) {
List<HydrologyPoint> crossing = rasterLine(start, end);
HydrologyTerrainSample startTerrain = sampleLandBasis(start.x(), start.z());
HydrologyTerrainSample endTerrain = sampleLandBasis(end.x(), end.z());
if (startTerrain == null || endTerrain == null) {
return false;
}
int boundaryHeight = Math.min(startTerrain.naturalHeight(), endTerrain.naturalHeight());
int threshold = settings.hydraulics().waterfallMinimumDrop();
int surfaceInset = settings.surface().maximumSurfaceInset();
int upstreamHead = Math.subtractExact(start.y(), surfaceInset);
int downstreamHead = Math.subtractExact(end.y(), surfaceInset);
Direction tangent = direction(start.x(), start.z(), end.x(), end.z());
for (int pointIndex = 0; pointIndex < crossing.size(); pointIndex++) {
HydrologyPoint point = crossing.get(pointIndex);
HydrologyTerrainSample terrain;
if (pointIndex == 0) {
terrain = startTerrain;
} else if (pointIndex == crossing.size() - 1) {
terrain = endTerrain;
} else {
terrain = sampleLandBasis(point.x(), point.z());
}
if (terrain == null || !terrain.transitAllowed()) {
return false;
}
if (pointIndex > 0
&& pointIndex < crossing.size() - 1
&& boundaryHeight - terrain.naturalHeight() >= threshold) {
return false;
}
double progress = crossing.size() < 2
? 0D
: pointIndex / (double) (crossing.size() - 1);
int head = (int) StrictMath.round(
upstreamHead + (downstreamHead - upstreamHead) * progress
);
int permittedIncision = permittedSurfaceIncision(terrain);
if (terrain.naturalHeight() - head > permittedIncision
|| !containedSurfaceRouteCandidate(
point.x(),
point.z(),
terrain,
tangent
)) {
return false;
}
}
return true;
}
private boolean containedSurfaceTransitionSupported(
HydrologyPoint start,
HydrologyPoint end
) {
Direction tangent = direction(start.x(), start.z(), end.x(), end.z());
for (HydrologyPoint point : rasterLine(start, end)) {
HydrologyTerrainSample terrain = sampleLandBasis(point.x(), point.z());
if (terrain == null
|| !terrain.transitAllowed()
|| !containedSurfaceRouteCandidate(
point.x(),
point.z(),
terrain,
tangent
)) {
return false;
}
}
return true;
}
private boolean crossesTerrainCrevasse(HydrologyPoint start, HydrologyPoint end) { private boolean crossesTerrainCrevasse(HydrologyPoint start, HydrologyPoint end) {
List<HydrologyPoint> crossing = rasterLine(start, end); List<HydrologyPoint> crossing = rasterLine(start, end);
if (crossing.size() < 3) { if (crossing.size() < 3) {
@@ -4506,7 +4209,7 @@ public final class HydrologyPlanner {
|| routedLength / directLength > SURFACE_MAXIMUM_SINUOSITY) { || routedLength / directLength > SURFACE_MAXIMUM_SINUOSITY) {
return false; return false;
} }
List<HydrologyPoint> sampled = resampleContainedSurfaceCenterline( List<HydrologyPoint> sampled = resampleRouteCenterline(
centerline, centerline,
Math.max(16D, settings.routing().refinementSpacing() * 4D) Math.max(16D, settings.routing().refinementSpacing() * 4D)
); );
@@ -4587,7 +4290,7 @@ public final class HydrologyPlanner {
return length; return length;
} }
private List<HydrologyPoint> resampleContainedSurfaceCenterline( private List<HydrologyPoint> resampleRouteCenterline(
List<HydrologyPoint> points, List<HydrologyPoint> points,
double spacing double spacing
) { ) {
@@ -5540,8 +5243,7 @@ public final class HydrologyPlanner {
List<HydrologyPoint> points = surfaceCourseCenterline( List<HydrologyPoint> points = surfaceCourseCenterline(
Objects.requireNonNull(grid, "Surface course routing grid is required"), Objects.requireNonNull(grid, "Surface course routing grid is required"),
surfaceGuide, surfaceGuide,
geometryId, geometryId
routing.containedSurface()
); );
if (points.size() < 2) { if (points.size() < 2) {
return null; return null;
@@ -5563,18 +5265,7 @@ public final class HydrologyPlanner {
private List<HydrologyPoint> surfaceCourseCenterline( private List<HydrologyPoint> surfaceCourseCenterline(
SampledGrid grid, SampledGrid grid,
List<HydrologyPoint> guide, List<HydrologyPoint> guide,
long geometryId, long geometryId
boolean containedSurface
) {
return surfaceCourseCenterline(grid, guide, geometryId, containedSurface, !containedSurface);
}
private List<HydrologyPoint> surfaceCourseCenterline(
SampledGrid grid,
List<HydrologyPoint> guide,
long geometryId,
boolean containedSurface,
boolean lazyCandidates
) { ) {
if (guide.size() < 2) { if (guide.size() < 2) {
return List.of(); return List.of();
@@ -5586,9 +5277,7 @@ public final class HydrologyPlanner {
return List.of(); return List.of();
} }
ArrayList<List<RouteCandidate>> layers = new ArrayList<>(nominal.size()); ArrayList<List<RouteCandidate>> layers = new ArrayList<>(nominal.size());
int transverseCandidates = containedSurface int transverseCandidates = SURFACE_COURSE_TRANSVERSE_CANDIDATES;
? ROUTE_TRANSVERSE_CANDIDATES
: SURFACE_COURSE_TRANSVERSE_CANDIDATES;
for (int pointIndex = 0; pointIndex < nominal.size(); pointIndex++) { for (int pointIndex = 0; pointIndex < nominal.size(); pointIndex++) {
SurfaceCurvePoint point = nominal.get(pointIndex); SurfaceCurvePoint point = nominal.get(pointIndex);
Direction tangent = surfaceCurveTangent(nominal, pointIndex); Direction tangent = surfaceCurveTangent(nominal, pointIndex);
@@ -5607,8 +5296,7 @@ public final class HydrologyPlanner {
tangent tangent
); );
long candidateId = HydrologyHash.mix(geometryId, pointIndex); long candidateId = HydrologyHash.mix(geometryId, pointIndex);
List<RouteCandidate> candidates = lazyCandidates List<RouteCandidate> candidates = unresolvedSurfaceRouteCandidates(
? unresolvedSurfaceRouteCandidates(
grid, grid,
geometryId, geometryId,
candidateId, candidateId,
@@ -5616,15 +5304,6 @@ public final class HydrologyPlanner {
progress, progress,
refinement, refinement,
transverseCandidates transverseCandidates
)
: routeCandidates(
geometryId,
candidateId,
position,
progress,
refinement,
transverseCandidates,
containedSurface
); );
if (candidates.isEmpty()) { if (candidates.isEmpty()) {
return List.of(); return List.of();
@@ -5640,12 +5319,11 @@ public final class HydrologyPlanner {
RouteCandidate[] route = selectCurvatureAwareTerrainRoute( RouteCandidate[] route = selectCurvatureAwareTerrainRoute(
availableLayers, availableLayers,
refinement, refinement,
containedSurface,
maximumTurn, maximumTurn,
SURFACE_COURSE_TURN_COST SURFACE_COURSE_TURN_COST
); );
if (route.length == 0) { if (route.length == 0) {
if (lazyCandidates && addExhaustedSurfaceRouteFallbacks( if (addExhaustedSurfaceRouteFallbacks(
grid, grid,
availableLayers, availableLayers,
nominal, nominal,
@@ -5675,9 +5353,6 @@ public final class HydrologyPlanner {
if (points.size() < 2 || !traversableRoute(points)) { if (points.size() < 2 || !traversableRoute(points)) {
return List.of(); return List.of();
} }
if (containedSurface) {
return smoothContainedSurfaceRoute(points);
}
return smoothTerrainRoute(points); return smoothTerrainRoute(points);
} }
} }
@@ -5747,65 +5422,8 @@ public final class HydrologyPlanner {
return true; return true;
} }
private List<HydrologyPoint> smoothContainedSurfaceRoute(List<HydrologyPoint> route) { private double routeTurnScore(List<HydrologyPoint> route) {
List<HydrologyPoint> current = List.copyOf(route); List<HydrologyPoint> sampled = resampleRouteCenterline(
List<HydrologyPoint> best = current;
double bestScore = containedSurfaceTurnScore(best);
int maximumPasses = Math.max(8, settings.geometry().meanders().smoothingPasses() * 4);
double[] strengths = new double[]{0.25D, 0.1875D, 0.125D, 0.0625D};
for (int pass = 0; pass < maximumPasses; pass++) {
List<HydrologyPoint> accepted = null;
for (double strength : strengths) {
List<HydrologyPoint> candidate = smoothContainedSurfaceRoutePass(current, strength);
if (candidate.size() >= 2 && containedSurfaceRouteSupported(candidate)) {
accepted = candidate;
break;
}
}
if (accepted == null || accepted.equals(current)) {
break;
}
current = accepted;
double score = containedSurfaceTurnScore(current);
if (score < bestScore) {
best = current;
bestScore = score;
}
}
return best;
}
private List<HydrologyPoint> smoothContainedSurfaceRoutePass(
List<HydrologyPoint> route,
double strength
) {
ArrayList<HydrologyPoint> smoothed = new ArrayList<>(route.size());
smoothed.add(route.getFirst());
for (int pointIndex = 1; pointIndex < route.size() - 1; pointIndex++) {
HydrologyPoint previous = route.get(pointIndex - 1);
HydrologyPoint point = route.get(pointIndex);
HydrologyPoint next = route.get(pointIndex + 1);
int x = (int) StrictMath.round(
previous.x() * strength
+ point.x() * (1D - strength * 2D)
+ next.x() * strength
);
int z = (int) StrictMath.round(
previous.z() * strength
+ point.z() * (1D - strength * 2D)
+ next.z() * strength
);
HydrologyTerrainSample terrain = sampleLandBasis(x, z);
smoothed.add(terrain == null || !terrain.transitAllowed()
? point
: new HydrologyPoint(x, terrain.naturalHeight(), z));
}
smoothed.add(route.getLast());
return withoutDuplicateRoutePoints(smoothed);
}
private double containedSurfaceTurnScore(List<HydrologyPoint> route) {
List<HydrologyPoint> sampled = resampleContainedSurfaceCenterline(
route, route,
Math.max(16D, settings.routing().refinementSpacing() * 4D) Math.max(16D, settings.routing().refinementSpacing() * 4D)
); );
@@ -5829,27 +5447,12 @@ public final class HydrologyPlanner {
return percentile + maximum * 0.05D; return percentile + maximum * 0.05D;
} }
private boolean containedSurfaceRouteSupported(List<HydrologyPoint> points) {
if (!traversableRoute(points) || containsTerrainPit(points)) {
return false;
}
for (int pointIndex = 0; pointIndex < points.size() - 1; pointIndex++) {
if (!containedSurfaceTransitionSupported(
points.get(pointIndex),
points.get(pointIndex + 1)
)) {
return false;
}
}
return true;
}
private double surfaceRouteCandidateBankPenalty(RouteCandidate candidate) { private double surfaceRouteCandidateBankPenalty(RouteCandidate candidate) {
int minimumBankDistance = surfaceBankDistance(settings.surface().minimumWidth()); int minimumBankDistance = surfaceBankDistance(settings.surface().minimumWidth());
int maximumBankDistance = surfaceBankDistance(settings.surface().maximumWidth()); int maximumBankDistance = surfaceBankDistance(settings.surface().maximumWidth());
int maximumBankRise = Math.addExact( int maximumBankRise = Math.addExact(
settings.surface().maximumIncision(), settings.surface().maximumIncision(),
(int) StrictMath.ceil(settings.surface().minimumTerrainBlendWidth() * 0.5D) (int) StrictMath.ceil(settings.surface().banks().minimumBlendWidth() * 0.5D)
); );
HydrologyPoint point = candidate.point(); HydrologyPoint point = candidate.point();
HydrologyTerrainSample terrain = sampleLandBasis(point.x(), point.z()); HydrologyTerrainSample terrain = sampleLandBasis(point.x(), point.z());
@@ -5861,7 +5464,7 @@ public final class HydrologyPlanner {
} }
int head = Math.subtractExact( int head = Math.subtractExact(
terrain.naturalHeight(), terrain.naturalHeight(),
settings.surface().maximumSurfaceInset() settings.surface().banks().inset()
); );
return surfaceRouteBankPenalty( return surfaceRouteBankPenalty(
point, point,
@@ -6120,24 +5723,6 @@ public final class HydrologyPlanner {
return pointX * pointX + pointZ * pointZ; return pointX * pointX + pointZ * pointZ;
} }
private boolean surfaceBankSupported(
HydrologyPoint point,
Direction tangent,
int signedDistance,
int head,
int maximumBankRise
) {
int bankX = (int) StrictMath.round(point.x() - tangent.z() * signedDistance);
int bankZ = (int) StrictMath.round(point.z() + tangent.x() * signedDistance);
HydrologyTerrainSample bank = sampleLandBasis(bankX, bankZ);
if (bank == null) {
return false;
}
int rise = bank.naturalHeight() - head;
return rise >= SURFACE_MINIMUM_NATURAL_BANK_RISE && (rise <= maximumBankRise
|| bank.slope() >= settings.hydraulics().waterfallMinimumDrop());
}
private HydrologyTerrainSample surfaceBankTerrain( private HydrologyTerrainSample surfaceBankTerrain(
HydrologyPoint point, HydrologyPoint point,
Direction tangent, Direction tangent,
@@ -7031,8 +6616,7 @@ public final class HydrologyPlanner {
int downstreamZ, int downstreamZ,
int continuationX, int continuationX,
int continuationZ, int continuationZ,
int transverseCandidates, int transverseCandidates
boolean containedSurface
) { ) {
} }
@@ -7131,16 +6715,11 @@ public final class HydrologyPlanner {
private record RoutingQueueEntry( private record RoutingQueueEntry(
int nodeIndex, int nodeIndex,
double potential, double potential,
int outletIndex, int outletIndex
int requiredSurfaceHead
) )
implements Comparable<RoutingQueueEntry> { implements Comparable<RoutingQueueEntry> {
@Override @Override
public int compareTo(RoutingQueueEntry other) { public int compareTo(RoutingQueueEntry other) {
int surfaceHeadComparison = Integer.compare(requiredSurfaceHead, other.requiredSurfaceHead);
if (surfaceHeadComparison != 0) {
return surfaceHeadComparison;
}
int potentialComparison = Double.compare(potential, other.potential); int potentialComparison = Double.compare(potential, other.potential);
if (potentialComparison != 0) { if (potentialComparison != 0) {
return potentialComparison; return potentialComparison;
@@ -7156,7 +6735,6 @@ public final class HydrologyPlanner {
int[] outletIndex, int[] outletIndex,
int[] routeLengths, int[] routeLengths,
List<OutletCandidate> outlets, List<OutletCandidate> outlets,
boolean containedSurface,
boolean organicSurfaceRequired boolean organicSurfaceRequired
) { ) {
} }
@@ -35,9 +35,9 @@ 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, 8, 8192, 8192, new Branching(384, 192), 1.5D, 24D, 2D, 0.2D), new Routing(2048, 64, 8192, 8192, 384, 192, 1.5D, 24D, 2D, 0.2D),
new Surface(true, surfaceSources, 4, 8, 2, 4, 1, 1, 10, 1.5D, 4, 32, false, 1, 1, Banks.defaults()), new Surface(true, surfaceSources, 4, 8, 2, 4, 10, 1.5D, Banks.defaults()),
new Hydraulics(80, 180, 1, 7, 24, 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),
new Outlets( new Outlets(
true, true,
@@ -97,7 +97,7 @@ public record HydrologyPlannerSettings(
int radius = 0; int radius = 0;
int routeDisplacement = Math.multiplyExact(routing.sampleSpacing(), 3); int routeDisplacement = Math.multiplyExact(routing.sampleSpacing(), 3);
if (surface.enabled() && surface.sources().enabled()) { if (surface.enabled() && surface.sources().enabled()) {
int blendWidth = Math.max(surface.maximumTerrainBlendWidth(), surface.banks().maximumBlendWidth()); int blendWidth = surface.banks().maximumBlendWidth();
int surfaceRadius = (int) StrictMath.ceil( int surfaceRadius = (int) StrictMath.ceil(
surface.maximumWidth() / 2D + surface.shoreWidth() + blendWidth surface.maximumWidth() / 2D + surface.shoreWidth() + blendWidth
); );
@@ -105,7 +105,7 @@ public record HydrologyPlannerSettings(
surfaceRadius, surfaceRadius,
(int) StrictMath.ceil(geometry.drops().basinWidth( (int) StrictMath.ceil(geometry.drops().basinWidth(
geometry.drops().flowWidth(surface.maximumWidth()) geometry.drops().flowWidth(surface.maximumWidth())
) / 2D + surface.shoreWidth() + surface.maximumTerrainBlendWidth()) ) / 2D + surface.shoreWidth() + blendWidth)
); );
surfaceRadius = Math.max(surfaceRadius, outlets.coastalGrotto().horizontalRadius()); surfaceRadius = Math.max(surfaceRadius, outlets.coastalGrotto().horizontalRadius());
surfaceRadius = Math.max(surfaceRadius, outlets.inlandGrotto().horizontalRadius()); surfaceRadius = Math.max(surfaceRadius, outlets.inlandGrotto().horizontalRadius());
@@ -144,18 +144,17 @@ public record HydrologyPlannerSettings(
public record Routing( public record Routing(
int tileSize, int tileSize,
int sampleSpacing, int sampleSpacing,
int refinementSpacing,
int maximumRouteNodes, int maximumRouteNodes,
int maximumRouteLength, int maximumRouteLength,
Branching branching, int minimumSurfaceCourseLength,
int minimumUndergroundCourseLength,
double valleyPreference, double valleyPreference,
double uphillPenalty, double uphillPenalty,
double slopePenalty, double slopePenalty,
double confluenceAttraction double confluenceAttraction
) { ) {
public Routing { public Routing {
if (tileSize < 32 || sampleSpacing < 4 || refinementSpacing < 1 || branching == null if (tileSize < 32 || sampleSpacing < 4 || tileSize % sampleSpacing != 0) {
|| tileSize % sampleSpacing != 0 || sampleSpacing % refinementSpacing != 0) {
throw new IllegalArgumentException("Routing sizes must form an exact bounded tile lattice."); throw new IllegalArgumentException("Routing sizes must form an exact bounded tile lattice.");
} }
int latticeWidth = tileSize / sampleSpacing + 1; int latticeWidth = tileSize / sampleSpacing + 1;
@@ -166,8 +165,12 @@ public record HydrologyPlannerSettings(
if (maximumRouteNodes < latticeNodes || maximumRouteNodes > 1_000_000 || maximumRouteLength < 1) { if (maximumRouteNodes < latticeNodes || maximumRouteNodes > 1_000_000 || maximumRouteLength < 1) {
throw new IllegalArgumentException("maximumRouteNodes must contain the complete lattice and remain bounded."); throw new IllegalArgumentException("maximumRouteNodes must contain the complete lattice and remain bounded.");
} }
if (branching.minimumSurfaceCourseLength() > maximumRouteLength if (minimumSurfaceCourseLength < 0 || minimumSurfaceCourseLength > 32_768
|| branching.minimumUndergroundCourseLength() > maximumRouteLength) { || minimumUndergroundCourseLength < 0 || minimumUndergroundCourseLength > 32_768) {
throw new IllegalArgumentException("Hydrology course lengths are invalid.");
}
if (minimumSurfaceCourseLength > maximumRouteLength
|| minimumUndergroundCourseLength > maximumRouteLength) {
throw new IllegalArgumentException("Minimum course lengths cannot exceed maximum route length."); throw new IllegalArgumentException("Minimum course lengths cannot exceed maximum route length.");
} }
requireFiniteNonNegative(valleyPreference, "valleyPreference"); requireFiniteNonNegative(valleyPreference, "valleyPreference");
@@ -175,17 +178,17 @@ public record HydrologyPlannerSettings(
requireFiniteNonNegative(slopePenalty, "slopePenalty"); requireFiniteNonNegative(slopePenalty, "slopePenalty");
requireFiniteNonNegative(confluenceAttraction, "confluenceAttraction"); requireFiniteNonNegative(confluenceAttraction, "confluenceAttraction");
} }
// Route refinement is derived from the lattice, never authored.
public static int refinementSpacing(int sampleSpacing) {
if (sampleSpacing % 4 == 0) {
return 4;
}
return sampleSpacing % 2 == 0 ? 2 : 1;
} }
public record Branching( public int refinementSpacing() {
int minimumSurfaceCourseLength, return refinementSpacing(sampleSpacing);
int minimumUndergroundCourseLength
) {
public Branching {
if (minimumSurfaceCourseLength < 0 || minimumSurfaceCourseLength > 32_768
|| minimumUndergroundCourseLength < 0 || minimumUndergroundCourseLength > 32_768) {
throw new IllegalArgumentException("Hydrology branching lengths are invalid.");
}
} }
public int minimumCourseLength(boolean surface) { public int minimumCourseLength(boolean surface) {
@@ -221,15 +224,8 @@ public record HydrologyPlannerSettings(
int maximumWidth, int maximumWidth,
int minimumDepth, int minimumDepth,
int maximumDepth, int maximumDepth,
int minimumSurfaceInset,
int maximumSurfaceInset,
int maximumIncision, int maximumIncision,
double shoreWidth, double shoreWidth,
int minimumTerrainBlendWidth,
int maximumTerrainBlendWidth,
boolean ridgeTunnelsEnabled,
int maximumRidgeTunnelLength,
int ridgeTunnelHeadroom,
Banks banks Banks banks
) { ) {
public Surface { public Surface {
@@ -238,14 +234,9 @@ public record HydrologyPlannerSettings(
} }
requireRange(minimumWidth, maximumWidth, 1, 256, "surface width"); requireRange(minimumWidth, maximumWidth, 1, 256, "surface width");
requireRange(minimumDepth, maximumDepth, 1, 64, "surface depth"); requireRange(minimumDepth, maximumDepth, 1, 64, "surface depth");
requireRange(minimumSurfaceInset, maximumSurfaceInset, 1, 64, "surface inset");
if (maximumIncision < 0 || !Double.isFinite(shoreWidth) || shoreWidth < 0D || shoreWidth > 32D) { if (maximumIncision < 0 || !Double.isFinite(shoreWidth) || shoreWidth < 0D || shoreWidth > 32D) {
throw new IllegalArgumentException("Surface incision and shore width are invalid."); throw new IllegalArgumentException("Surface incision and shore width are invalid.");
} }
requireRange(minimumTerrainBlendWidth, maximumTerrainBlendWidth, 0, 128, "terrain blend width");
if (maximumRidgeTunnelLength < 0 || ridgeTunnelHeadroom < 0) {
throw new IllegalArgumentException("Ridge tunnel bounds cannot be negative.");
}
} }
} }
@@ -280,21 +271,11 @@ public record HydrologyPlannerSettings(
} }
} }
public record Hydraulics( // The routing gates read one hydraulic threshold: the drop that makes a reach a waterfall.
int minimumTargetPoolLength, public record Hydraulics(int waterfallMinimumDrop) {
int maximumTargetPoolLength,
int riffleDrop,
int maximumGradualDrop,
int maximumGradualLength,
int waterfallMinimumDrop
) {
public Hydraulics { public Hydraulics {
requireRange(minimumTargetPoolLength, maximumTargetPoolLength, 1, 4096, "target pool length"); if (waterfallMinimumDrop < 1) {
if (riffleDrop < 0 || maximumGradualDrop < riffleDrop throw new IllegalArgumentException("waterfallMinimumDrop must be at least one block.");
|| maximumGradualLength < Math.max(1, maximumGradualDrop)
|| waterfallMinimumDrop != maximumGradualDrop + 1
|| minimumTargetPoolLength < maximumGradualLength) {
throw new IllegalArgumentException("Hydraulic transition thresholds are inconsistent.");
} }
} }
} }
@@ -88,7 +88,7 @@ public record RiverCourse(
HydraulicSegment lip = segments.get(segments.size() - 3); HydraulicSegment lip = segments.get(segments.size() - 3);
HydraulicSegment sinkhole = segments.get(segments.size() - 2); HydraulicSegment sinkhole = segments.get(segments.size() - 2);
HydraulicSegment grotto = segments.getLast(); HydraulicSegment grotto = segments.getLast();
if (!lip.type().isSurface() && lip.type() != HydrologyFeatureType.RIDGE_BORE if (!lip.type().isSurface()
|| sinkhole.type() != HydrologyFeatureType.SINKHOLE || sinkhole.type() != HydrologyFeatureType.SINKHOLE
|| sinkhole.drop() <= 0 || sinkhole.drop() <= 0
|| !sinkhole.receivingPool() || !sinkhole.receivingPool()
@@ -65,9 +65,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
private static final int BIOME_PATCH_SCALE = 32; private static final int BIOME_PATCH_SCALE = 32;
private static final long SURFACE_WIDTH_SALT = 0x5355524657494454L; private static final long SURFACE_WIDTH_SALT = 0x5355524657494454L;
private static final long SURFACE_DEPTH_SALT = 0x5355524644455054L; private static final long SURFACE_DEPTH_SALT = 0x5355524644455054L;
private static final long SURFACE_INSET_SALT = 0x53555246494e5345L;
private static final long SURFACE_BLEND_SALT = 0x53555246424c454eL; private static final long SURFACE_BLEND_SALT = 0x53555246424c454eL;
private static final long TARGET_POOL_SALT = 0x544152474554504fL;
private static final long UNDERGROUND_LEVEL_SALT = 0x554e4445524c564cL; private static final long UNDERGROUND_LEVEL_SALT = 0x554e4445524c564cL;
private static final long UNDERGROUND_WIDTH_SALT = 0x554e444552574944L; private static final long UNDERGROUND_WIDTH_SALT = 0x554e444552574944L;
private static final long UNDERGROUND_DEPTH_SALT = 0x554e444552444550L; private static final long UNDERGROUND_DEPTH_SALT = 0x554e444552444550L;
@@ -556,9 +554,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
IrisUndergroundRiverConfig underground = rivers.getUnderground(); IrisUndergroundRiverConfig underground = rivers.getUnderground();
ProceduralStream<Double> surfaceWidth = styledStream(context, channel.getWidth(), SURFACE_WIDTH_SALT); ProceduralStream<Double> surfaceWidth = styledStream(context, channel.getWidth(), SURFACE_WIDTH_SALT);
ProceduralStream<Double> surfaceDepth = styledStream(context, channel.getDepth(), SURFACE_DEPTH_SALT); ProceduralStream<Double> surfaceDepth = styledStream(context, channel.getDepth(), SURFACE_DEPTH_SALT);
ProceduralStream<Double> surfaceInset = ProceduralStream.ofDouble((x, z) -> (double) channel.getInset());
ProceduralStream<Double> surfaceBlend = ProceduralStream.ofDouble((x, z) -> (double) banks.getMaximumBlendWidth()); ProceduralStream<Double> surfaceBlend = ProceduralStream.ofDouble((x, z) -> (double) banks.getMaximumBlendWidth());
ProceduralStream<Double> targetPool = ProceduralStream.ofDouble((x, z) -> 128D);
ProceduralStream<Double> undergroundLevel = styledStream( ProceduralStream<Double> undergroundLevel = styledStream(
context, context,
underground.getFluidLevel(), underground.getFluidLevel(),
@@ -589,9 +585,7 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
ProceduralStream<Double> stream = switch (request.field()) { ProceduralStream<Double> stream = switch (request.field()) {
case SURFACE_WIDTH -> surfaceWidth; case SURFACE_WIDTH -> surfaceWidth;
case SURFACE_DEPTH -> surfaceDepth; case SURFACE_DEPTH -> surfaceDepth;
case SURFACE_INSET -> surfaceInset;
case SURFACE_BLEND_WIDTH -> surfaceBlend; case SURFACE_BLEND_WIDTH -> surfaceBlend;
case TARGET_POOL_LENGTH -> targetPool;
case UNDERGROUND_FLUID_LEVEL -> undergroundLevel; case UNDERGROUND_FLUID_LEVEL -> undergroundLevel;
case UNDERGROUND_WIDTH -> undergroundWidth; case UNDERGROUND_WIDTH -> undergroundWidth;
case UNDERGROUND_DEPTH -> undergroundDepth; case UNDERGROUND_DEPTH -> undergroundDepth;
@@ -639,7 +633,6 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
IrisUndergroundRiverConfig underground = rivers.getUnderground(); IrisUndergroundRiverConfig underground = rivers.getUnderground();
IrisUndergroundRiverSourceConfig undergroundSources = underground.getSources(); IrisUndergroundRiverSourceConfig undergroundSources = underground.getSources();
int routeNodes = maximumRouteNodes(routing); int routeNodes = maximumRouteNodes(routing);
int refinementSpacing = refinementSpacing(routing.getSampleSpacing());
int minimumWorldY = dimension.getMinHeight(); int minimumWorldY = dimension.getMinHeight();
boolean riversEnabled = rivers.isEnabled(); boolean riversEnabled = rivers.isEnabled();
boolean surfaceEnabled = riversEnabled && surface.isEnabled(); boolean surfaceEnabled = riversEnabled && surface.isEnabled();
@@ -647,13 +640,10 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
HydrologyPlannerSettings.Routing plannerRouting = new HydrologyPlannerSettings.Routing( HydrologyPlannerSettings.Routing plannerRouting = new HydrologyPlannerSettings.Routing(
routing.getTileSize(), routing.getTileSize(),
routing.getSampleSpacing(), routing.getSampleSpacing(),
refinementSpacing,
routeNodes, routeNodes,
routing.getMaximumRouteLength(), routing.getMaximumRouteLength(),
new HydrologyPlannerSettings.Branching(
routing.getMinimumSurfaceCourseLength(), routing.getMinimumSurfaceCourseLength(),
routing.getMinimumUndergroundCourseLength() routing.getMinimumUndergroundCourseLength(),
),
routing.getValleyPreference(), routing.getValleyPreference(),
routing.getUphillPenalty(), routing.getUphillPenalty(),
routing.getSlopePenalty(), routing.getSlopePenalty(),
@@ -674,15 +664,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
maximumInt(channel.getWidth()), maximumInt(channel.getWidth()),
minimumInt(channel.getDepth()), minimumInt(channel.getDepth()),
maximumInt(channel.getDepth()), maximumInt(channel.getDepth()),
channel.getInset(),
channel.getInset(),
channel.getMaximumIncision(), channel.getMaximumIncision(),
banks.getShoreWidth(), banks.getShoreWidth(),
banks.getMinimumBlendWidth(),
banks.getMaximumBlendWidth(),
false,
1,
1,
new HydrologyPlannerSettings.Banks( new HydrologyPlannerSettings.Banks(
channel.getInset(), channel.getInset(),
banks.getFreeboard(), banks.getFreeboard(),
@@ -697,14 +680,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
banks.isExposeCutStrata() banks.isExposeCutStrata()
) )
); );
HydrologyPlannerSettings.Hydraulics plannerHydraulics = new HydrologyPlannerSettings.Hydraulics( HydrologyPlannerSettings.Hydraulics plannerHydraulics =
80, new HydrologyPlannerSettings.Hydraulics(flow.getWaterfallMinimumDrop());
180,
1,
flow.getWaterfallMinimumDrop() - 1,
24,
flow.getWaterfallMinimumDrop()
);
HydrologyPlannerSettings.Source plannerUndergroundSources = new HydrologyPlannerSettings.Source( HydrologyPlannerSettings.Source plannerUndergroundSources = new HydrologyPlannerSettings.Source(
undergroundEnabled, undergroundEnabled,
undergroundSources.getDensity(), undergroundSources.getDensity(),
@@ -760,17 +737,10 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
plannerUnderground, plannerUnderground,
plannerOutlets, plannerOutlets,
plannerGeometry, plannerGeometry,
deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, refinementSpacing) deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, plannerRouting.refinementSpacing())
); );
} }
static int refinementSpacing(int sampleSpacing) {
if (sampleSpacing % 4 == 0) {
return 4;
}
return sampleSpacing % 2 == 0 ? 2 : 1;
}
private static HydrologyPlannerSettings.Geometry geometry( private static HydrologyPlannerSettings.Geometry geometry(
IrisRiverGeometryConfig geometry, IrisRiverGeometryConfig geometry,
HydrologyPlannerSettings.ChannelShape surfaceShape HydrologyPlannerSettings.ChannelShape surfaceShape
@@ -877,7 +847,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
return 0; return 0;
} }
int configuredLength = Math.max( int configuredLength = Math.max(
refinementSpacing(routing.getSampleSpacing()), configuration.getSpacing() / 3); HydrologyPlannerSettings.Routing.refinementSpacing(routing.getSampleSpacing()),
configuration.getSpacing() / 3);
return Math.min(configuredLength, routing.getTileSize() / 2); return Math.min(configuredLength, routing.getTileSize() / 2);
} }
@@ -31,12 +31,12 @@ public class HydrologyCaveCourseFilterTest {
}; };
@Test @Test
public void rejectedRidgeBoreIsAbsentFromCoursesFootprintRendererAndLocator() { public void rejectedCaveCourseIsAbsentFromCoursesFootprintRendererAndLocator() {
HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent"); HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent");
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
22L, 22L,
11L, 11L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -81,19 +81,19 @@ public class HydrologyCaveCourseFilterTest {
assertTrue(tile.footprint().isEmpty()); assertTrue(tile.footprint().isEmpty());
assertTrue(tile.features().isEmpty()); assertTrue(tile.features().isEmpty());
assertTrue(tile.renderAt(8, 8).features().isEmpty()); assertTrue(tile.renderAt(8, 8).features().isEmpty());
assertTrue(tile.nearestFeature(HydrologyFeatureType.RIDGE_BORE, 8, 8, 16).isEmpty()); assertTrue(tile.nearestFeature(HydrologyFeatureType.UNDERGROUND_POOL, 8, 8, 16).isEmpty());
assertEquals(1, tile.diagnosticCandidates().size()); assertEquals(1, tile.diagnosticCandidates().size());
assertEquals(HydrologyCandidateRejection.CAVE_CONTAINMENT, assertEquals(HydrologyCandidateRejection.CAVE_CONTAINMENT,
tile.diagnosticCandidates().getFirst().rejection()); tile.diagnosticCandidates().getFirst().rejection());
} }
@Test @Test
public void generatedSurfaceConnectedCaveAroundRidgeBoreIsSealed() { public void generatedSurfaceConnectedCaveAroundAnUndergroundReachIsSealed() {
HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent"); HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent");
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
42L, 42L,
41L, 41L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -136,7 +136,7 @@ public class HydrologyCaveCourseFilterTest {
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
420L, 420L,
410L, 410L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -218,12 +218,12 @@ public class HydrologyCaveCourseFilterTest {
} }
@Test @Test
public void ridgeBoreAboveFinalTerrainRejectsTheWholeCourse() { public void undergroundReachAboveFinalTerrainRejectsTheWholeCourse() {
HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent"); HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent");
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
52L, 52L,
51L, 51L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -255,12 +255,12 @@ public class HydrologyCaveCourseFilterTest {
} }
@Test @Test
public void surfaceRidgeExposurePreflightChecksTheFullActionBoundaryBeforeVoxelExpansion() { public void surfaceExposurePreflightChecksTheFullActionBoundaryBeforeVoxelExpansion() {
HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent"); HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent");
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
152L, 152L,
151L, 151L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -294,12 +294,12 @@ public class HydrologyCaveCourseFilterTest {
} }
@Test @Test
public void ridgeSurfaceOpeningExemptsItsCompleteSixNeighborShell() { public void caveSurfaceOpeningExemptsItsCompleteSixNeighborShell() {
HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent"); HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent");
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
162L, 162L,
161L, 161L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -505,7 +505,7 @@ public class HydrologyCaveCourseFilterTest {
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
192L, 192L,
191L, 191L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -598,12 +598,12 @@ public class HydrologyCaveCourseFilterTest {
} }
@Test @Test
public void acceptedRidgeBoreRetainsItsExactPlanOnTheImmutableTile() { public void acceptedCaveCourseRetainsItsExactPlanOnTheImmutableTile() {
HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent"); HydrologyTerrainSample terrain = HydrologyTerrainSample.openLand(80, 0D, "parent");
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
82L, 82L,
81L, 81L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -959,7 +959,7 @@ public class HydrologyCaveCourseFilterTest {
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
102L, 102L,
101L, 101L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -1034,7 +1034,7 @@ public class HydrologyCaveCourseFilterTest {
HydraulicSegment ridge = new HydraulicSegment( HydraulicSegment ridge = new HydraulicSegment(
92L, 92L,
91L, 91L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
20, 20,
20, 20,
2, 2,
@@ -16,6 +16,57 @@ import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
public class HydrologyFootprintCompilerTest { public class HydrologyFootprintCompilerTest {
private static final int[][] CARDINALS = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};
/**
* Published dry cells cardinally adjacent to owned water whose final terrain sits below the
* water head plus the configured freeboard.
*/
private static int unsupportedBankCells(RiverFootprint footprint, long courseId) {
int freeboard = HydrologyPlannerSettings.defaults().surface().banks().freeboard();
int unsupported = 0;
for (HydrologyColumnSample sample : footprint.columns().values()) {
HydrologyColumnLayer fluid = sample.primarySurfaceFluidLayer().orElse(null);
if (fluid == null
|| fluid.feature().courseId() != courseId
|| fluid.oceanApron()
|| fluid.fallingFluid()) {
continue;
}
for (int[] offset : CARDINALS) {
HydrologyColumnSample neighbor = footprint
.sample(sample.x() + offset[0], sample.z() + offset[1])
.orElse(null);
if (neighbor == null || neighbor.primarySurfaceFluidLayer().isPresent()) {
continue;
}
if (neighbor.terrainHeight() < fluid.fluidHeadY() + freeboard) {
unsupported++;
}
}
}
return unsupported;
}
/** Deepest cut between natural terrain and the published water head over an owned channel. */
private static int maximumSurfaceCut(RiverFootprint footprint, long courseId) {
int maximum = 0;
for (HydrologyColumnSample sample : footprint.columns().values()) {
HydrologyColumnLayer fluid = sample.primarySurfaceFluidLayer().orElse(null);
if (fluid == null
|| fluid.feature().courseId() != courseId
|| fluid.feature().type() == HydrologyFeatureType.MOUTH
|| fluid.oceanApron()
|| fluid.fallingFluid()
|| !fluid.channel()
|| !fluid.fluidOwned()) {
continue;
}
maximum = Math.max(maximum, sample.naturalHeight() - fluid.fluidHeadY());
}
return maximum;
}
@Test @Test
public void surfaceChannelNeverRaisesTerrainOrPublishesUnsupportedFluid() { public void surfaceChannelNeverRaisesTerrainOrPublishesUnsupportedFluid() {
HydraulicSegment segment = new HydraulicSegment( HydraulicSegment segment = new HydraulicSegment(
@@ -78,7 +129,7 @@ public class HydrologyFootprintCompilerTest {
RiverFootprint footprint = new HydrologyFootprintCompiler( RiverFootprint footprint = new HydrologyFootprintCompiler(
HydrologyPlannerSettings.defaults(), HydrologyPlannerSettings.defaults(),
terrain, terrain,
HydrologyGeometrySampler.deterministic(1L, terrain) HydrologyGeometrySampler.deterministic(terrain)
).compile(List.of(course)); ).compile(List.of(course));
for (int x = 0; x <= 8; x++) { for (int x = 0; x <= 8; x++) {
@@ -162,7 +213,7 @@ public class HydrologyFootprintCompilerTest {
} }
@Test @Test
public void surfaceChannelAtTerrainLevelIsReportedAsUncontained() { public void surfaceChannelAtTerrainLevelHasNoContainingBank() {
HydraulicSegment segment = new HydraulicSegment( HydraulicSegment segment = new HydraulicSegment(
15L, 15L,
14L, 14L,
@@ -180,7 +231,7 @@ public class HydrologyFootprintCompilerTest {
HydrologyFootprintCompiler compiler = compiler(terrain); HydrologyFootprintCompiler compiler = compiler(terrain);
RiverFootprint footprint = compiler.compile(List.of(course)); RiverFootprint footprint = compiler.compile(List.of(course));
assertFalse(compiler.surfaceBanksContained(course)); assertTrue(unsupportedBankCells(footprint, 14L) > 0);
for (HydrologyColumnSample sample : footprint.columns().values()) { for (HydrologyColumnSample sample : footprint.columns().values()) {
assertTrue(sample.terrainHeight() <= sample.naturalHeight()); assertTrue(sample.terrainHeight() <= sample.naturalHeight());
} }
@@ -1026,51 +1077,6 @@ public class HydrologyFootprintCompilerTest {
} }
} }
@Test
public void surfaceBankGradingTapersAnIntersectingBoreInsteadOfOpeningItsSide() {
HydraulicSegment surface = new HydraulicSegment(
321L,
320L,
HydrologyFeatureType.SURFACE_POOL,
70,
70,
6,
2,
false,
false,
List.of(new HydrologyPoint(0, 70, 0), new HydrologyPoint(8, 70, 0))
);
HydraulicSegment ridge = new HydraulicSegment(
322L,
320L,
HydrologyFeatureType.RIDGE_BORE,
74,
74,
4,
2,
false,
false,
List.of(new HydrologyPoint(0, 74, 8), new HydrologyPoint(8, 74, 8))
);
HydrologyTerrainSampler terrain = (int x, int z) -> HydrologyTerrainSample.openLand(80, 0D, "parent");
RiverFootprint footprint = compiler(terrain).compile(List.of(new RiverCourse(
320L,
RiverCourseType.SURFACE,
OptionalLong.of(1L),
OptionalLong.of(2L),
"water",
1,
List.of(),
List.of(surface, ridge)
)));
HydrologyColumnSample gradedBank = footprint.sample(4, 8).orElseThrow();
assertTrue(gradedBank.layers().stream().noneMatch(
(HydrologyColumnLayer layer) -> layer.feature().type() == HydrologyFeatureType.RIDGE_BORE
));
assertTrue(gradedBank.terrainHeight() < gradedBank.naturalHeight());
}
@Test @Test
public void compactValidationMatchesFullRasterAcrossOverlapsOceanAndSurfaceOnlyCourses() { public void compactValidationMatchesFullRasterAcrossOverlapsOceanAndSurfaceOnlyCourses() {
RiverCourse mixed = new RiverCourse( RiverCourse mixed = new RiverCourse(
@@ -1389,64 +1395,6 @@ public class HydrologyFootprintCompilerTest {
int sourceDepth = source.fluidHeadY() - source.bedY(); int sourceDepth = source.fluidHeadY() - source.bedY();
int cruiseDepth = cruise.fluidHeadY() - cruise.bedY(); int cruiseDepth = cruise.fluidHeadY() - cruise.bedY();
assertTrue(Math.abs(sourceDepth - cruiseDepth) <= 1); assertTrue(Math.abs(sourceDepth - cruiseDepth) <= 1);
assertTrue(compiler.surfaceHeadwaterRampSupported(course));
}
@Test
public void incompleteSurfaceBoreCenterlineRejectsTheWholeCourse() {
HydraulicSegment entry = new HydraulicSegment(
532L,
531L,
HydrologyFeatureType.SURFACE_POOL,
70,
70,
8,
3,
false,
false,
List.of(new HydrologyPoint(0, 70, 0), new HydrologyPoint(16, 70, 0))
);
HydraulicSegment bore = new HydraulicSegment(
533L,
531L,
HydrologyFeatureType.RIDGE_BORE,
70,
70,
8,
3,
false,
false,
List.of(new HydrologyPoint(16, 70, 0), new HydrologyPoint(48, 70, 0))
);
HydraulicSegment exit = new HydraulicSegment(
534L,
531L,
HydrologyFeatureType.SURFACE_POOL,
70,
70,
8,
3,
false,
false,
List.of(new HydrologyPoint(48, 70, 0), new HydrologyPoint(64, 70, 0))
);
HydrologyTerrainSampler terrain = (int x, int z) -> HydrologyTerrainSample.openLand(
x == 32 ? 70 : 82,
0D,
"parent"
);
RiverCourse course = new RiverCourse(
531L,
RiverCourseType.SURFACE,
OptionalLong.of(1L),
OptionalLong.of(2L),
"water",
1,
List.of(),
List.of(entry, bore, exit)
);
assertFalse(compiler(terrain).surfaceBanksContained(course));
} }
@Test @Test
@@ -1519,17 +1467,17 @@ public class HydrologyFootprintCompilerTest {
RiverFootprint lowBankFootprint = lowBankCompiler.compile(List.of(course)); RiverFootprint lowBankFootprint = lowBankCompiler.compile(List.of(course));
HydrologyColumnSample lowBank = lowBankFootprint.sample(12, lowBankZ).orElseThrow(); HydrologyColumnSample lowBank = lowBankFootprint.sample(12, lowBankZ).orElseThrow();
assertTrue(highCompiler.surfaceBanksContained(course)); assertEquals(0, unsupportedBankCells(highFootprint, 528L));
assertEquals(70, lowBank.naturalHeight()); assertEquals(70, lowBank.naturalHeight());
assertEquals(70, lowBank.terrainHeight()); assertEquals(70, lowBank.terrainHeight());
assertFalse(lowBankCompiler.surfaceBanksContained(course)); assertTrue(unsupportedBankCells(lowBankFootprint, 528L) > 0);
for (HydrologyColumnSample sample : lowBankFootprint.columns().values()) { for (HydrologyColumnSample sample : lowBankFootprint.columns().values()) {
assertTrue(sample.terrainHeight() <= sample.naturalHeight()); assertTrue(sample.terrainHeight() <= sample.naturalHeight());
} }
} }
@Test @Test
public void surfaceIncisionContainmentUsesFinalTerrainHeight() { public void publishedSurfaceChannelStaysWithinTheConfiguredIncision() {
HydraulicSegment channel = new HydraulicSegment( HydraulicSegment channel = new HydraulicSegment(
529L, 529L,
528L, 528L,
@@ -1543,28 +1491,16 @@ public class HydrologyFootprintCompilerTest {
List.of(new HydrologyPoint(0, 70, 0), new HydrologyPoint(24, 70, 0)) List.of(new HydrologyPoint(0, 70, 0), new HydrologyPoint(24, 70, 0))
); );
RiverCourse course = course(528L, RiverCourseType.SURFACE, channel); RiverCourse course = course(528L, RiverCourseType.SURFACE, channel);
HydrologyFootprintCompiler supported = compiler((int x, int z) -> HydrologyFootprintCompiler compiler = compiler((int x, int z) ->
HydrologyTerrainSample.openLand(76, 0D, "parent")); HydrologyTerrainSample.openLand(76, 0D, "parent"));
HydrologyTerrainSample routingBasis = HydrologyTerrainSample.openLand(76, 0D, "parent");
HydrologyFootprintCompiler excessive = new HydrologyFootprintCompiler(
HydrologyPlannerSettings.defaults(),
new HydrologyFootprintCompiler.Sampling(
(int x, int z) -> HydrologyTerrainSample.openLand(83, 0D, "parent"),
request -> request.minimum(),
new CountingNaturalSampler(
routingBasis,
HydrologyRoutingTerrainSampler.NaturalClassification.LAND
)
)
);
assertTrue(supported.surfaceIncisionContained(course)); RiverFootprint footprint = compiler.compile(List.of(course));
RiverFootprint excessiveFootprint = excessive.compile(List.of(course));
assertFalse( assertTrue(
"center=" + excessiveFootprint.sample(12, 0), "center=" + footprint.sample(12, 0),
excessive.surfaceIncisionContained(course) maximumSurfaceCut(footprint, 528L)
<= HydrologyPlannerSettings.defaults().surface().maximumIncision()
); );
assertFalse(excessive.surfaceIncisionContained(course, excessiveFootprint));
} }
@Test @Test
@@ -1592,7 +1528,6 @@ public class HydrologyFootprintCompilerTest {
HydrologyFootprintCompiler compiler = compiler(terrain); HydrologyFootprintCompiler compiler = compiler(terrain);
assertTrue(compiler.compile(List.of(course)).isEmpty()); assertTrue(compiler.compile(List.of(course)).isEmpty());
assertFalse(compiler.surfaceBanksContained(course));
} }
@Test @Test
@@ -153,7 +153,7 @@ public class HydrologyModelTest {
HydraulicSegment containedLip = new HydraulicSegment( HydraulicSegment containedLip = new HydraulicSegment(
4L, 4L,
9L, 9L,
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.SURFACE_POOL,
80, 80,
80, 80,
4, 4,
@@ -252,8 +252,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(256, 16, 4, 1_089, 256, new HydrologyPlannerSettings.Routing(256, 16, 1_089, 256, 16, 8, 0.5D, 12D, 0.5D, 0.2D),
new HydrologyPlannerSettings.Branching(16, 8), 0.5D, 12D, 0.5D, 0.2D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
true, true,
surfaceSources, surfaceSources,
@@ -261,18 +260,10 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
20, 20,
2, 2,
5, 5,
4,
8,
12, 12,
1.5D, 1.5D,
4,
10,
true,
96,
10
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
true, true,
undergroundSources, undergroundSources,
@@ -343,16 +334,8 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
baseSurface.maximumWidth(), baseSurface.maximumWidth(),
baseSurface.minimumDepth(), baseSurface.minimumDepth(),
baseSurface.maximumDepth(), baseSurface.maximumDepth(),
baseSurface.minimumSurfaceInset(),
baseSurface.maximumSurfaceInset(),
64, 64,
baseSurface.shoreWidth(), baseSurface.shoreWidth(),
baseSurface.minimumTerrainBlendWidth(),
baseSurface.maximumTerrainBlendWidth(),
baseSurface.ridgeTunnelsEnabled(),
baseSurface.maximumRidgeTunnelLength(),
baseSurface.ridgeTunnelHeadroom()
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
base.hydraulics(), base.hydraulics(),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
@@ -639,33 +639,31 @@ public class HydrologyPlannerGlobalInvariantTest {
boolean undergroundEnabled, boolean undergroundEnabled,
List<HydrologyPlannerSettings.DeepFluid> deepFluids List<HydrologyPlannerSettings.DeepFluid> deepFluids
) { ) {
return settingsWithRefinement( return plannerSettings(
surfaceSources, surfaceSources,
undergroundSources, undergroundSources,
surfaceEnabled, surfaceEnabled,
undergroundEnabled, undergroundEnabled,
deepFluids, deepFluids
4
); );
} }
private HydrologyPlannerSettings settingsWithRefinement( private HydrologyPlannerSettings plannerSettings(
HydrologyPlannerSettings.Source surfaceSources, HydrologyPlannerSettings.Source surfaceSources,
HydrologyPlannerSettings.Source undergroundSources, HydrologyPlannerSettings.Source undergroundSources,
boolean surfaceEnabled, boolean surfaceEnabled,
boolean undergroundEnabled, boolean undergroundEnabled,
List<HydrologyPlannerSettings.DeepFluid> deepFluids, List<HydrologyPlannerSettings.DeepFluid> deepFluids
int refinementSpacing
) { ) {
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing( new HydrologyPlannerSettings.Routing(
TILE_SIZE, TILE_SIZE,
16, 16,
refinementSpacing,
512, 512,
256, 256,
new HydrologyPlannerSettings.Branching(16, 8), 16,
8,
0.5D, 0.5D,
12D, 12D,
0.5D, 0.5D,
@@ -678,18 +676,10 @@ public class HydrologyPlannerGlobalInvariantTest {
4, 4,
1, 1,
2, 2,
4,
8,
128, 128,
1D, 1D,
1,
2,
false,
0,
0
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
undergroundEnabled, undergroundEnabled,
undergroundSources, undergroundSources,
@@ -724,13 +714,12 @@ public class HydrologyPlannerGlobalInvariantTest {
boolean surfaceEnabled, boolean surfaceEnabled,
boolean undergroundEnabled boolean undergroundEnabled
) { ) {
HydrologyPlannerSettings base = settingsWithRefinement( HydrologyPlannerSettings base = plannerSettings(
surfaceSources, surfaceSources,
undergroundSources, undergroundSources,
surfaceEnabled, surfaceEnabled,
undergroundEnabled, undergroundEnabled,
List.of(), List.of()
8
); );
HydrologyPlannerSettings.Surface surface = base.surface(); HydrologyPlannerSettings.Surface surface = base.surface();
HydrologyPlannerSettings.Underground underground = base.underground(); HydrologyPlannerSettings.Underground underground = base.underground();
@@ -744,16 +733,8 @@ public class HydrologyPlannerGlobalInvariantTest {
surface.minimumWidth(), surface.minimumWidth(),
surface.minimumDepth(), surface.minimumDepth(),
surface.maximumDepth(), surface.maximumDepth(),
surface.minimumSurfaceInset(),
surface.maximumSurfaceInset(),
surface.maximumIncision(), surface.maximumIncision(),
surface.shoreWidth(), surface.shoreWidth(),
surface.minimumTerrainBlendWidth(),
surface.maximumTerrainBlendWidth(),
surface.ridgeTunnelsEnabled(),
surface.maximumRidgeTunnelLength(),
surface.ridgeTunnelHeadroom()
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
base.hydraulics(), base.hydraulics(),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
@@ -38,7 +38,7 @@ public class HydrologyPlannerRoutingSamplerTest {
settings, settings,
detailSampler, detailSampler,
routingSampler, routingSampler,
HydrologyGeometrySampler.deterministic(17L, detailSampler), HydrologyGeometrySampler.deterministic(detailSampler),
-4096, -4096,
footprint -> new HydrologyTerrainCaveVoxelView(detailSampler, settings.seaLevel(), -4096, 4096) footprint -> new HydrologyTerrainCaveVoxelView(detailSampler, settings.seaLevel(), -4096, 4096)
); );
@@ -70,7 +70,7 @@ public class HydrologyPlannerRoutingSamplerTest {
settings, settings,
detailSampler, detailSampler,
routingSampler, routingSampler,
HydrologyGeometrySampler.deterministic(19L, detailSampler), HydrologyGeometrySampler.deterministic(detailSampler),
-4096, -4096,
footprint -> new HydrologyTerrainCaveVoxelView(detailSampler, settings.seaLevel(), -4096, 4096) footprint -> new HydrologyTerrainCaveVoxelView(detailSampler, settings.seaLevel(), -4096, 4096)
); );
@@ -108,7 +108,7 @@ public class HydrologyPlannerRoutingSamplerTest {
settings, settings,
detailSampler, detailSampler,
routingSampler, routingSampler,
HydrologyGeometrySampler.deterministic(23L, detailSampler), HydrologyGeometrySampler.deterministic(detailSampler),
-4096, -4096,
footprint -> new HydrologyTerrainCaveVoxelView(detailSampler, settings.seaLevel(), -4096, 4096) footprint -> new HydrologyTerrainCaveVoxelView(detailSampler, settings.seaLevel(), -4096, 4096)
); );
@@ -11,11 +11,19 @@ import static org.junit.Assert.assertTrue;
public class HydrologyPlannerSettingsTest { public class HydrologyPlannerSettingsTest {
@Test @Test
public void branchingUsesIndependentSurfaceAndUndergroundCourseFloors() { public void routingUsesIndependentSurfaceAndUndergroundCourseFloors() {
HydrologyPlannerSettings.Branching branching = new HydrologyPlannerSettings.Branching(384, 192); HydrologyPlannerSettings.Routing routing = HydrologyPlannerSettings.defaults().routing();
assertEquals(384, branching.minimumCourseLength(true)); assertEquals(384, routing.minimumCourseLength(true));
assertEquals(192, branching.minimumCourseLength(false)); assertEquals(192, routing.minimumCourseLength(false));
}
@Test
public void refinementSpacingIsDerivedFromTheSampleLattice() {
assertEquals(4, HydrologyPlannerSettings.Routing.refinementSpacing(64));
assertEquals(2, HydrologyPlannerSettings.Routing.refinementSpacing(50));
assertEquals(1, HydrologyPlannerSettings.Routing.refinementSpacing(45));
assertEquals(4, HydrologyPlannerSettings.defaults().routing().refinementSpacing());
} }
@Test @Test
@@ -63,10 +71,10 @@ public class HydrologyPlannerSettingsTest {
HydrologyPlannerSettings.Routing routing = new HydrologyPlannerSettings.Routing( HydrologyPlannerSettings.Routing routing = new HydrologyPlannerSettings.Routing(
512, 512,
512, 512,
64,
4, 4,
256, 256,
new HydrologyPlannerSettings.Branching(0, 0), 0,
0,
0D, 0D,
0D, 0D,
0D, 0D,
@@ -80,25 +88,10 @@ public class HydrologyPlannerSettingsTest {
1, 1,
1, 1,
1, 1,
1,
1,
0, 0,
1D, 1D,
0,
0,
false,
0,
0
,
HydrologyPlannerSettings.Banks.defaults()); HydrologyPlannerSettings.Banks.defaults());
HydrologyPlannerSettings.Hydraulics hydraulics = new HydrologyPlannerSettings.Hydraulics( HydrologyPlannerSettings.Hydraulics hydraulics = new HydrologyPlannerSettings.Hydraulics(1);
8,
8,
0,
0,
1,
1
);
HydrologyPlannerSettings.Grotto grotto = new HydrologyPlannerSettings.Grotto(true, 1, 1, 1, 1); HydrologyPlannerSettings.Grotto grotto = new HydrologyPlannerSettings.Grotto(true, 1, 1, 1, 1);
HydrologyPlannerSettings.Outlets outlets = new HydrologyPlannerSettings.Outlets( HydrologyPlannerSettings.Outlets outlets = new HydrologyPlannerSettings.Outlets(
true, true,
@@ -168,20 +161,13 @@ public class HydrologyPlannerSettingsTest {
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Surface( assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Surface(
true, true,
new HydrologyPlannerSettings.Source(true, 1D, 0, 0, 1, 0), new HydrologyPlannerSettings.Source(true, 1D, 0, 0, 1, 0),
1, 1, 1, 1, 1, 1, 0, 1D, 0, 0, false, 0, 0, 1, 1, 1, 1, 0, 1D,
null)); null));
} }
@Test @Test
public void inconsistentHydraulicThresholdsAreRejected() { public void nonPositiveWaterfallThresholdsAreRejected() {
assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Hydraulics( assertThrows(IllegalArgumentException.class, () -> new HydrologyPlannerSettings.Hydraulics(0));
8,
16,
0,
2,
2,
2
));
} }
@Test @Test
@@ -522,35 +522,14 @@ public class HydrologyPlannerTest {
assertFalse("diagnostics=" + tile.diagnosticCandidates(), surfaceCourses(tile).isEmpty()); assertFalse("diagnostics=" + tile.diagnosticCandidates(), surfaceCourses(tile).isEmpty());
boolean aggregateTransition = false; boolean aggregateTransition = false;
for (RiverCourse course : surfaceCourses(tile)) { for (RiverCourse course : surfaceCourses(tile)) {
double recovery = Double.POSITIVE_INFINITY;
boolean transitionActive = false;
for (HydraulicSegment segment : course.segments()) { for (HydraulicSegment segment : course.segments()) {
boolean surfaceDrop = segment.type().isSurface() && segment.type().isDrop(); if (segment.drop() < settings.hydraulics().waterfallMinimumDrop()) {
if (surfaceDrop) {
if (!transitionActive) {
assertTrue(
"segments=" + course.segments(),
!Double.isFinite(recovery)
|| recovery >= settings.hydraulics().minimumTargetPoolLength()
);
}
recovery = 0D;
transitionActive = !segment.receivingPool();
if (segment.type() == HydrologyFeatureType.WATERFALL
&& segment.drop() >= settings.hydraulics().waterfallMinimumDrop()) {
aggregateTransition = true;
}
continue; continue;
} }
if (segment.type() == HydrologyFeatureType.UNDERGROUND_DROP if (segment.type() == HydrologyFeatureType.WATERFALL
&& segment.drop() >= settings.hydraulics().waterfallMinimumDrop()) { || segment.type() == HydrologyFeatureType.UNDERGROUND_DROP) {
aggregateTransition = true; aggregateTransition = true;
} }
if (transitionActive && segment.receivingPool()) {
transitionActive = false;
} else if (!transitionActive && Double.isFinite(recovery)) {
recovery += segmentLength(segment);
}
} }
} }
assertTrue("courses=" + surfaceCourses(tile), aggregateTransition); assertTrue("courses=" + surfaceCourses(tile), aggregateTransition);
@@ -597,10 +576,8 @@ public class HydrologyPlannerTest {
new HydrologyPlannerSettings.Routing( new HydrologyPlannerSettings.Routing(
routing.tileSize(), routing.tileSize(),
routing.sampleSpacing(), routing.sampleSpacing(),
routing.refinementSpacing(),
routing.maximumRouteNodes(), routing.maximumRouteNodes(),
routing.maximumRouteLength(), routing.maximumRouteLength(), 96, 0,
new HydrologyPlannerSettings.Branching(96, 0),
routing.valleyPreference(), routing.valleyPreference(),
routing.uphillPenalty(), routing.uphillPenalty(),
routing.slopePenalty(), routing.slopePenalty(),
@@ -792,7 +769,7 @@ public class HydrologyPlannerTest {
333L, 333L,
settings, settings,
coast, coast,
HydrologyGeometrySampler.deterministic(333L, coast), HydrologyGeometrySampler.deterministic(coast),
-4096, -4096,
footprint -> solidCaveView() footprint -> solidCaveView()
).plan(TILE); ).plan(TILE);
@@ -1134,16 +1111,8 @@ public class HydrologyPlannerTest {
base.surface().maximumWidth(), base.surface().maximumWidth(),
base.surface().minimumDepth(), base.surface().minimumDepth(),
8, 8,
base.surface().minimumSurfaceInset(),
base.surface().maximumSurfaceInset(),
128, 128,
base.surface().shoreWidth(), base.surface().shoreWidth(),
base.surface().minimumTerrainBlendWidth(),
base.surface().maximumTerrainBlendWidth(),
base.surface().ridgeTunnelsEnabled(),
base.surface().maximumRidgeTunnelLength(),
base.surface().ridgeTunnelHeadroom()
,
HydrologyPlannerSettings.Banks.defaults()); HydrologyPlannerSettings.Banks.defaults());
HydrologyPlannerSettings shaped = new HydrologyPlannerSettings( HydrologyPlannerSettings shaped = new HydrologyPlannerSettings(
base.seaLevel(), base.seaLevel(),
@@ -1432,16 +1401,8 @@ public class HydrologyPlannerTest {
surface.maximumWidth(), surface.maximumWidth(),
surface.minimumDepth(), surface.minimumDepth(),
surface.maximumDepth(), surface.maximumDepth(),
surface.minimumSurfaceInset(),
surface.maximumSurfaceInset(),
surface.maximumIncision(), surface.maximumIncision(),
surface.shoreWidth(), surface.shoreWidth(),
surface.minimumTerrainBlendWidth(),
surface.maximumTerrainBlendWidth(),
surface.ridgeTunnelsEnabled(),
32,
surface.ridgeTunnelHeadroom()
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
base.hydraulics(), base.hydraulics(),
base.underground(), base.underground(),
@@ -1772,8 +1733,7 @@ public class HydrologyPlannerTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(128, 16, 4, 512, 256, new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 0, 0, 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Branching(0, 0), 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
surfaceDensity > 0D || surfaceSources.maximumPerTile() > 0, surfaceDensity > 0D || surfaceSources.maximumPerTile() > 0,
surfaceSources, surfaceSources,
@@ -1781,18 +1741,10 @@ public class HydrologyPlannerTest {
18, 18,
2, 2,
4, 4,
4,
8,
10, 10,
1.5D, 1.5D,
4,
8,
true,
64,
8
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
undergroundDensity > 0D, undergroundDensity > 0D,
undergroundSources, undergroundSources,
@@ -1842,10 +1794,8 @@ public class HydrologyPlannerTest {
new HydrologyPlannerSettings.Routing( new HydrologyPlannerSettings.Routing(
512, 512,
64, 64,
4,
1024, 1024,
2048, 2048, 64, 32,
new HydrologyPlannerSettings.Branching(64, 32),
1.5D, 1.5D,
24D, 24D,
2D, 2D,
@@ -1858,18 +1808,10 @@ public class HydrologyPlannerTest {
surface.maximumWidth(), surface.maximumWidth(),
surface.minimumDepth(), surface.minimumDepth(),
surface.maximumDepth(), surface.maximumDepth(),
surface.minimumSurfaceInset(),
surface.maximumSurfaceInset(),
96, 96,
surface.shoreWidth(), surface.shoreWidth(),
surface.minimumTerrainBlendWidth(),
surface.maximumTerrainBlendWidth(),
surface.ridgeTunnelsEnabled(),
192,
surface.ridgeTunnelHeadroom()
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 4, 12, 5), new HydrologyPlannerSettings.Hydraulics(5),
base.underground(), base.underground(),
new HydrologyPlannerSettings.Outlets( new HydrologyPlannerSettings.Outlets(
true, true,
@@ -2172,16 +2114,8 @@ public class HydrologyPlannerTest {
surface.maximumWidth(), surface.maximumWidth(),
surface.minimumDepth(), surface.minimumDepth(),
surface.maximumDepth(), surface.maximumDepth(),
surface.minimumSurfaceInset(),
surface.maximumSurfaceInset(),
surface.maximumIncision(), surface.maximumIncision(),
surface.shoreWidth(), surface.shoreWidth(),
surface.minimumTerrainBlendWidth(),
surface.maximumTerrainBlendWidth(),
surface.ridgeTunnelsEnabled(),
surface.maximumRidgeTunnelLength(),
surface.ridgeTunnelHeadroom()
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
settings.hydraulics(), settings.hydraulics(),
settings.underground(), settings.underground(),
@@ -192,8 +192,7 @@ public class HydrologyStyledGeometryTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(128, 16, 4, 512, 256, new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Branching(16, 8), 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
surfaceDensity > 0D, surfaceDensity > 0D,
surfaceSources, surfaceSources,
@@ -201,18 +200,10 @@ public class HydrologyStyledGeometryTest {
18, 18,
2, 2,
4, 4,
4,
8,
10, 10,
1.5D, 1.5D,
4,
8,
true,
96,
8
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
undergroundDensity > 0D, undergroundDensity > 0D,
undergroundSources, undergroundSources,
@@ -243,10 +243,10 @@ public class HydrologyTileCacheTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(64, 16, 4, 128, 64, new HydrologyPlannerSettings.Routing(64, 16, 128, 64, 0, 0, 0D, 0D, 0D, 0D),
new HydrologyPlannerSettings.Branching(0, 0), 0D, 0D, 0D, 0D), new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 4, 1D,
new HydrologyPlannerSettings.Surface(false, disabled, 2, 4, 1, 2, 2, 4, 4, 1D, 2, 4, false, 0, 0, HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 2, 8, 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),
new HydrologyPlannerSettings.Outlets( new HydrologyPlannerSettings.Outlets(
false, false,
@@ -282,10 +282,9 @@ public class HydrologyTileCacheTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(64, 16, 4, 128, 96, new HydrologyPlannerSettings.Routing(64, 16, 128, 96, 16, 8, 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Branching(16, 8), 0.5D, 12D, 0.5D, 0.1D), new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 20, 1.5D, tileBoundedBanks()),
new HydrologyPlannerSettings.Surface(true, surfaceSources, 4, 8, 2, 3, 4, 8, 20, 1.5D, 2, 4, true, 96, 8, tileBoundedBanks()), new HydrologyPlannerSettings.Hydraulics(3),
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.Underground(false, disabled, -32, 32, 2, 4, 1, 2, 3, 4, false),
new HydrologyPlannerSettings.Outlets( new HydrologyPlannerSettings.Outlets(
true, true,
@@ -114,14 +114,14 @@ public class IrisHydrologyRuntimeSettingsTest {
assertEquals(8, settings.hydraulics().waterfallMinimumDrop()); assertEquals(8, settings.hydraulics().waterfallMinimumDrop());
assertEquals(96, settings.outlets().mouthLevelingDistance()); assertEquals(96, settings.outlets().mouthLevelingDistance());
assertEquals(4, settings.routing().refinementSpacing()); assertEquals(4, settings.routing().refinementSpacing());
assertEquals(500, settings.routing().branching().minimumSurfaceCourseLength()); assertEquals(500, settings.routing().minimumSurfaceCourseLength());
assertEquals(250, settings.routing().branching().minimumUndergroundCourseLength()); assertEquals(250, settings.routing().minimumUndergroundCourseLength());
assertEquals(2.5D, settings.routing().valleyPreference(), 0D); assertEquals(2.5D, settings.routing().valleyPreference(), 0D);
assertEquals(30D, settings.routing().uphillPenalty(), 0D); assertEquals(30D, settings.routing().uphillPenalty(), 0D);
assertEquals(3D, settings.routing().slopePenalty(), 0D); assertEquals(3D, settings.routing().slopePenalty(), 0D);
assertEquals(0.4D, settings.routing().confluenceAttraction(), 0D); assertEquals(0.4D, settings.routing().confluenceAttraction(), 0D);
assertEquals(2, IrisHydrologyRuntime.refinementSpacing(50)); assertEquals(2, HydrologyPlannerSettings.Routing.refinementSpacing(50));
assertEquals(1, IrisHydrologyRuntime.refinementSpacing(45)); assertEquals(1, HydrologyPlannerSettings.Routing.refinementSpacing(45));
} }
@Test @Test
@@ -172,7 +172,7 @@ public class ErosionFieldCompilerTest {
return new HydrologyPlannerSettings.Surface( return new HydrologyPlannerSettings.Surface(
true, true,
defaults.sources(), defaults.sources(),
4, 8, 2, 4, 1, 1, 10, 1.5D, 4, 32, false, 1, 1, 4, 8, 2, 4, 10, 1.5D,
new HydrologyPlannerSettings.Banks(1, 1, 3D, 4, 32, 0D, 16, 2, 6, 1.6D, true) new HydrologyPlannerSettings.Banks(1, 1, 3D, 4, 32, 0D, 16, 2, 6, 1.6D, true)
); );
} }
@@ -746,10 +746,10 @@ public class MantleHydrologyComponentTest {
} }
@Test @Test
public void sameCourseSurfaceFluidOwnsAChangedRidgeBoundary() { public void sameCourseSurfaceFluidOwnsAChangedCaveBoundary() {
long courseId = 33L; long courseId = 33L;
HydrologyColumnLayer ridge = layerForCourse( HydrologyColumnLayer ridge = layerForCourse(
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
34L, 34L,
courseId, courseId,
10L, 10L,
@@ -1115,7 +1115,7 @@ public class MantleHydrologyComponentTest {
"river" "river"
); );
HydrologyColumnLayer cave = layer( HydrologyColumnLayer cave = layer(
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
72L, 72L,
15L, 15L,
20, 20,
@@ -1158,7 +1158,7 @@ public class MantleHydrologyComponentTest {
"river" "river"
); );
HydrologyColumnLayer cave = layer( HydrologyColumnLayer cave = layer(
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
72L, 72L,
15L, 15L,
20, 20,
@@ -1192,7 +1192,7 @@ public class MantleHydrologyComponentTest {
"river" "river"
); );
HydrologyColumnLayer cave = layer( HydrologyColumnLayer cave = layer(
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
74L, 74L,
17L, 17L,
20, 20,
@@ -1245,7 +1245,7 @@ public class MantleHydrologyComponentTest {
"lava" "lava"
); );
HydrologyColumnLayer cave = layer( HydrologyColumnLayer cave = layer(
HydrologyFeatureType.RIDGE_BORE, HydrologyFeatureType.UNDERGROUND_POOL,
76L, 76L,
19L, 19L,
17, 17,
@@ -1530,8 +1530,7 @@ public class MantleHydrologyComponentTest {
); );
return new HydrologyPlannerSettings( return new HydrologyPlannerSettings(
63, 63,
new HydrologyPlannerSettings.Routing(128, 16, 8, 512, 256, new HydrologyPlannerSettings.Routing(128, 16, 512, 256, 16, 8, 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Branching(16, 8), 0.5D, 12D, 0.5D, 0.1D),
new HydrologyPlannerSettings.Surface( new HydrologyPlannerSettings.Surface(
true, true,
surfaceSources, surfaceSources,
@@ -1539,18 +1538,10 @@ public class MantleHydrologyComponentTest {
2, 2,
2, 2,
2, 2,
4,
8,
128, 128,
0D, 0D,
0,
0,
false,
0,
0
,
HydrologyPlannerSettings.Banks.defaults()), HydrologyPlannerSettings.Banks.defaults()),
new HydrologyPlannerSettings.Hydraulics(8, 16, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
false, false,
disabledSources, disabledSources,
@@ -1576,7 +1576,7 @@ public final class HydrologyPackProbe {
publishedMorphology.observe( publishedMorphology.observe(
tile, tile,
settings.surface().minimumWidth(), settings.surface().minimumWidth(),
settings.routing().branching().minimumSurfaceCourseLength() settings.routing().minimumSurfaceCourseLength()
); );
HydrologyPlannerSettings.Routing routing = settings.routing(); HydrologyPlannerSettings.Routing routing = settings.routing();
int sampleSpacing = routing.sampleSpacing(); int sampleSpacing = routing.sampleSpacing();
@@ -1704,7 +1704,7 @@ public final class HydrologyPackProbe {
List<ChannelIncision> incisions = channelIncisions( List<ChannelIncision> incisions = channelIncisions(
tile.footprint(), tile.footprint(),
scopedCourseIds, scopedCourseIds,
settings.surface().minimumSurfaceInset(), settings.surface().banks().inset(),
settings.surface().maximumIncision() settings.surface().maximumIncision()
); );
for (ChannelIncision incision : incisions) { for (ChannelIncision incision : incisions) {
@@ -220,10 +220,10 @@ public final class HydrologyPlannerProbe {
new HydrologyPlannerSettings.Routing( new HydrologyPlannerSettings.Routing(
128, 128,
16, 16,
4,
512, 512,
256, 256,
new HydrologyPlannerSettings.Branching(32, 16), 32,
16,
0.5D, 0.5D,
12D, 12D,
0.5D, 0.5D,
@@ -236,18 +236,11 @@ public final class HydrologyPlannerProbe {
18, 18,
2, 2,
4, 4,
4,
8,
10, 10,
1.5D, 1.5D,
4,
8,
true,
96,
8,
HydrologyPlannerSettings.Banks.defaults() HydrologyPlannerSettings.Banks.defaults()
), ),
new HydrologyPlannerSettings.Hydraulics(12, 28, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
undergroundDensity > 0D, undergroundDensity > 0D,
undergroundSources, undergroundSources,
@@ -438,10 +431,10 @@ public final class HydrologyPlannerProbe {
new HydrologyPlannerSettings.Routing( new HydrologyPlannerSettings.Routing(
256, 256,
16, 16,
4,
1_089, 1_089,
256, 256,
new HydrologyPlannerSettings.Branching(32, 16), 32,
16,
0.5D, 0.5D,
12D, 12D,
0.5D, 0.5D,
@@ -454,18 +447,11 @@ public final class HydrologyPlannerProbe {
20, 20,
2, 2,
5, 5,
4,
8,
12, 12,
1.5D, 1.5D,
4,
10,
true,
96,
10,
HydrologyPlannerSettings.Banks.defaults() HydrologyPlannerSettings.Banks.defaults()
), ),
new HydrologyPlannerSettings.Hydraulics(12, 40, 1, 3, 8, 4), new HydrologyPlannerSettings.Hydraulics(4),
new HydrologyPlannerSettings.Underground( new HydrologyPlannerSettings.Underground(
true, true,
undergroundSources, undergroundSources,
@@ -23,7 +23,7 @@ public final class HydrologyPlannerProbeTest {
} }
@Test @Test
public void requiredCoverageExcludesTheRemovedRidgeBore() { public void requiredCoverageExcludesTheUnproducedRidgeBore() {
assertFalse(HydrologyPlannerProbe.REQUIRED_FEATURE_TYPES.contains(HydrologyFeatureType.RIDGE_BORE)); assertFalse(HydrologyPlannerProbe.REQUIRED_FEATURE_TYPES.contains(HydrologyFeatureType.RIDGE_BORE));
assertEquals( assertEquals(
EnumSet.complementOf(EnumSet.of(HydrologyFeatureType.RIDGE_BORE)), EnumSet.complementOf(EnumSet.of(HydrologyFeatureType.RIDGE_BORE)),