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
+305 -1411

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,
File diff suppressed because it is too large. Load diff
@@ -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");
} }
}
public record Branching( // Route refinement is derived from the lattice, never authored.
int minimumSurfaceCourseLength, public static int refinementSpacing(int sampleSpacing) {
int minimumUndergroundCourseLength if (sampleSpacing % 4 == 0) {
) { return 4;
public Branching {
if (minimumSurfaceCourseLength < 0 || minimumSurfaceCourseLength > 32_768
|| minimumUndergroundCourseLength < 0 || minimumUndergroundCourseLength > 32_768) {
throw new IllegalArgumentException("Hydrology branching lengths are invalid.");
} }
return sampleSpacing % 2 == 0 ? 2 : 1;
}
public int refinementSpacing() {
return refinementSpacing(sampleSpacing);
} }
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)),