mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-22 16:17:44 +00:00
Surface course integration
- Build surface courses through the valley solver and segment labeler - Compile surface footprints through the erosion field and index them for cave containment - Drop the contained-surface fallback and the post-hoc surface rejection passes - Choose the river profile at the source
This commit is contained in:
1 parent
2c6792977f
commit
8a256e3226
17 files changed
+1002
-507
No files matched your search
+3
-3
@@ -22,7 +22,7 @@ public class ChannelProfileBuilderTest {
|
||||
@Test
|
||||
public void cruiseWidthIsConstantAndTapersFromTheHeadwater() {
|
||||
SurfaceCenterline centerline = straight(400);
|
||||
ChannelProfile profile = builder(1D, 1D).build(centerline, path(400), "water", false);
|
||||
ChannelProfile profile = builder(1D, 1D).build(centerline, "water", false);
|
||||
|
||||
assertEquals(400, profile.size());
|
||||
assertEquals(3D, profile.halfWidth()[200], 1.0E-9D);
|
||||
@@ -38,7 +38,7 @@ public class ChannelProfileBuilderTest {
|
||||
@Test
|
||||
public void directOceanCoursesFlareAtTheMouth() {
|
||||
SurfaceCenterline centerline = straight(400);
|
||||
ChannelProfile profile = builder(1D, 1D).build(centerline, path(400), "water", true);
|
||||
ChannelProfile profile = builder(1D, 1D).build(centerline, "water", true);
|
||||
|
||||
assertEquals(3D * 1.6D, profile.halfWidth()[399], 0.05D);
|
||||
assertEquals(3D, profile.halfWidth()[300], 1.0E-9D);
|
||||
@@ -47,7 +47,7 @@ public class ChannelProfileBuilderTest {
|
||||
@Test
|
||||
public void policyMultipliersScaleWidthDepthAndBank() {
|
||||
SurfaceCenterline centerline = straight(400);
|
||||
ChannelProfile profile = builder(2D, 0.5D).build(centerline, path(400), "water", false);
|
||||
ChannelProfile profile = builder(2D, 0.5D).build(centerline, "water", false);
|
||||
|
||||
assertEquals(6D, profile.halfWidth()[200], 1.0E-9D);
|
||||
assertEquals(1.5D, profile.depth()[200], 1.0E-9D);
|
||||
|
||||
+1
-1
@@ -158,7 +158,7 @@ public class ErosionFieldCompilerTest {
|
||||
List<HydrologyPoint> path = List.of(new HydrologyPoint(0, 0, 0), new HydrologyPoint(stations - 1, 0, 0));
|
||||
SurfaceCenterline centerline = SurfaceCenterline.densify(path);
|
||||
ChannelProfile channel = new ChannelProfileBuilder(surface, sampler, CONSTANT_GEOMETRY)
|
||||
.build(centerline, path, "water", terminal == SurfaceTerminal.OCEAN_MOUTH);
|
||||
.build(centerline, "water", terminal == SurfaceTerminal.OCEAN_MOUTH);
|
||||
ValleyProfile valley = new ValleyProfileSolver(surface, sampler, SEA_LEVEL, 64)
|
||||
.solve(centerline, channel, terminal, terminalHead);
|
||||
assertNull(valley.rejection());
|
||||
|
||||
+141
@@ -0,0 +1,141 @@
|
||||
package art.arcane.iris.engine.hydrology.surface;
|
||||
|
||||
import art.arcane.iris.engine.hydrology.HydraulicSegment;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyCandidateRejection;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyFeatureType;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyGeometrySampler;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyPlannerSettings;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyPoint;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyTerrainSample;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyTerrainSampler;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SurfaceCourseBuilderTest {
|
||||
private static final int SEA_LEVEL = 60;
|
||||
private static final long COURSE_ID = 4242L;
|
||||
private static final HydrologyGeometrySampler CONSTANT_GEOMETRY = request -> switch (request.field()) {
|
||||
case SURFACE_WIDTH -> 6;
|
||||
case SURFACE_DEPTH -> 3;
|
||||
default -> request.minimum();
|
||||
};
|
||||
|
||||
@Test
|
||||
public void slopedCourseToTheSeaProducesNonRisingLabelledSegmentsEndingAtSeaLevel() {
|
||||
HydrologyTerrainSampler sampler = (int x, int z) -> x >= 260
|
||||
? HydrologyTerrainSample.ocean(50, "ocean")
|
||||
: HydrologyTerrainSample.openLand(110 - x / 6, 0D, "land");
|
||||
SurfaceCourseResult result = builder(sampler).build(
|
||||
7L,
|
||||
COURSE_ID,
|
||||
"water",
|
||||
List.of(new HydrologyPoint(0, 0, 0), new HydrologyPoint(140, 0, 0), new HydrologyPoint(280, 0, 0)),
|
||||
SurfaceTerminal.OCEAN_MOUTH,
|
||||
SEA_LEVEL
|
||||
);
|
||||
|
||||
assertNull(result.rejection());
|
||||
List<HydraulicSegment> segments = result.segments();
|
||||
assertTrue(segments.size() > 3);
|
||||
assertEquals(0, segments.getFirst().start().x());
|
||||
assertEquals(SEA_LEVEL, result.lastHead());
|
||||
assertEquals(SEA_LEVEL, segments.getLast().downstreamHeadY());
|
||||
int previous = Integer.MAX_VALUE;
|
||||
for (HydraulicSegment segment : segments) {
|
||||
assertEquals(COURSE_ID, segment.courseId());
|
||||
assertTrue(segment.upstreamHeadY() <= previous);
|
||||
assertTrue(segment.downstreamHeadY() <= segment.upstreamHeadY());
|
||||
assertTrue(segment.type().isSurface());
|
||||
previous = segment.downstreamHeadY();
|
||||
}
|
||||
for (int index = 1; index < segments.size(); index++) {
|
||||
HydraulicSegment before = segments.get(index - 1);
|
||||
HydraulicSegment after = segments.get(index);
|
||||
assertEquals(before.end().x(), after.start().x());
|
||||
assertEquals(before.end().z(), after.start().z());
|
||||
assertEquals(before.downstreamHeadY(), after.upstreamHeadY());
|
||||
}
|
||||
assertTrue(result.pathEnd().x() >= 250);
|
||||
assertTrue(result.lastWidth() >= 6);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void flatCourseIsOneSurfacePool() {
|
||||
HydrologyTerrainSampler sampler = (int x, int z) -> HydrologyTerrainSample.openLand(80, 0D, "land");
|
||||
SurfaceCourseResult result = builder(sampler).build(
|
||||
7L,
|
||||
COURSE_ID,
|
||||
"water",
|
||||
List.of(new HydrologyPoint(0, 0, 0), new HydrologyPoint(200, 0, 0)),
|
||||
SurfaceTerminal.SINKHOLE,
|
||||
40
|
||||
);
|
||||
|
||||
assertNull(result.rejection());
|
||||
assertEquals(1, result.segments().size());
|
||||
assertEquals(HydrologyFeatureType.SURFACE_POOL, result.segments().getFirst().type());
|
||||
assertEquals(79, result.lastHead());
|
||||
assertEquals(201, result.segments().getFirst().centerline().size());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cliffProducesAWaterfallSegment() {
|
||||
HydrologyTerrainSampler sampler = (int x, int z) -> HydrologyTerrainSample.openLand(x < 100 ? 100 : 90, 0D, "land");
|
||||
SurfaceCourseResult result = builder(sampler).build(
|
||||
7L,
|
||||
COURSE_ID,
|
||||
"water",
|
||||
List.of(new HydrologyPoint(0, 0, 0), new HydrologyPoint(200, 0, 0)),
|
||||
SurfaceTerminal.SINKHOLE,
|
||||
40
|
||||
);
|
||||
|
||||
assertNull(result.rejection());
|
||||
assertEquals(3, result.segments().size());
|
||||
assertEquals(HydrologyFeatureType.SURFACE_POOL, result.segments().get(0).type());
|
||||
assertEquals(HydrologyFeatureType.WATERFALL, result.segments().get(1).type());
|
||||
assertEquals(10, result.segments().get(1).drop());
|
||||
assertEquals(HydrologyFeatureType.SURFACE_POOL, result.segments().get(2).type());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gentleSlopeProducesRifflesAndSteepSlopeProducesCascades() {
|
||||
HydrologyTerrainSampler gentle = (int x, int z) -> HydrologyTerrainSample.openLand(120 - x / 10, 0D, "land");
|
||||
SurfaceCourseResult gentleResult = builder(gentle).build(7L, COURSE_ID, "water",
|
||||
List.of(new HydrologyPoint(0, 0, 0), new HydrologyPoint(200, 0, 0)), SurfaceTerminal.SINKHOLE, 40);
|
||||
HydrologyTerrainSampler steep = (int x, int z) -> HydrologyTerrainSample.openLand(300 - x, 0D, "land");
|
||||
SurfaceCourseResult steepResult = builder(steep).build(7L, COURSE_ID, "water",
|
||||
List.of(new HydrologyPoint(0, 0, 0), new HydrologyPoint(200, 0, 0)), SurfaceTerminal.SINKHOLE, 40);
|
||||
|
||||
assertNull(gentleResult.rejection());
|
||||
assertNull(steepResult.rejection());
|
||||
assertTrue(gentleResult.segments().stream().anyMatch(segment -> segment.type() == HydrologyFeatureType.RIFFLE));
|
||||
assertTrue(gentleResult.segments().stream().noneMatch(segment -> segment.type() == HydrologyFeatureType.CASCADE));
|
||||
assertTrue(steepResult.segments().stream().anyMatch(segment -> segment.type() == HydrologyFeatureType.CASCADE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void submergedCourseIsRejected() {
|
||||
HydrologyTerrainSampler sampler = (int x, int z) -> HydrologyTerrainSample.openLand(50, 0D, "land");
|
||||
SurfaceCourseResult result = builder(sampler).build(
|
||||
7L,
|
||||
COURSE_ID,
|
||||
"water",
|
||||
List.of(new HydrologyPoint(0, 0, 0), new HydrologyPoint(200, 0, 0)),
|
||||
SurfaceTerminal.OCEAN_MOUTH,
|
||||
SEA_LEVEL
|
||||
);
|
||||
|
||||
assertEquals(HydrologyCandidateRejection.COURSE_TOO_SHORT, result.rejection());
|
||||
assertTrue(result.segments().isEmpty());
|
||||
}
|
||||
|
||||
private static SurfaceCourseBuilder builder(HydrologyTerrainSampler sampler) {
|
||||
return new SurfaceCourseBuilder(HydrologyPlannerSettings.defaults().surface(), sampler, CONSTANT_GEOMETRY, SEA_LEVEL, 64);
|
||||
}
|
||||
}
|
||||
+145
@@ -0,0 +1,145 @@
|
||||
package art.arcane.iris.engine.hydrology.surface;
|
||||
|
||||
import art.arcane.iris.engine.hydrology.HydraulicSegment;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyColumnLayer;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyFeatureType;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyGeometrySampler;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyPlannerSettings;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyPoint;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyTerrainSample;
|
||||
import art.arcane.iris.engine.hydrology.HydrologyTerrainSampler;
|
||||
import art.arcane.iris.engine.hydrology.RiverCourse;
|
||||
import art.arcane.iris.engine.hydrology.RiverCourseType;
|
||||
import art.arcane.iris.engine.hydrology.RiverFootprint;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.OptionalLong;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class SurfaceFootprintCompilerTest {
|
||||
private static final int SEA_LEVEL = 60;
|
||||
private static final HydrologyGeometrySampler CONSTANT_GEOMETRY = request -> switch (request.field()) {
|
||||
case SURFACE_WIDTH -> 6;
|
||||
case SURFACE_DEPTH -> 3;
|
||||
default -> request.minimum();
|
||||
};
|
||||
|
||||
@Test
|
||||
public void compiledLayersDescribeAContainedCarveOnlyChannelWithShoreAndBank() {
|
||||
HydrologyTerrainSampler sampler = (int x, int z) -> x >= 150 && z > 8
|
||||
? HydrologyTerrainSample.ocean(50, "ocean")
|
||||
: HydrologyTerrainSample.openLand(80 + Math.max(0, -z) / 2, 0D, "land");
|
||||
RiverCourse course = course(List.of(
|
||||
segment(1L, HydrologyFeatureType.SURFACE_POOL, 79, 79, points(0, 60, 79)),
|
||||
segment(2L, HydrologyFeatureType.RIFFLE, 79, 78, List.of(new HydrologyPoint(60, 79, 0), new HydrologyPoint(61, 78, 0))),
|
||||
segment(3L, HydrologyFeatureType.SURFACE_POOL, 78, 78, points(61, 140, 78))
|
||||
));
|
||||
SurfaceFootprint footprint = compiler(sampler).compile(course);
|
||||
|
||||
assertEquals(0, footprint.uncontainedWetCells());
|
||||
assertFalse(footprint.columns().isEmpty());
|
||||
HashSet<Long> keys = new HashSet<>();
|
||||
for (SurfaceLayerColumn column : footprint.columns()) {
|
||||
assertTrue(keys.add(RiverFootprint.pack(column.x(), column.z())));
|
||||
HydrologyColumnLayer layer = column.layer();
|
||||
assertTrue(layer.bedY() <= column.terrain().naturalHeight());
|
||||
assertEquals(layer.fluidHeadY(), layer.ceilingY());
|
||||
assertEquals(1L, layer.feature().courseId());
|
||||
assertTrue(layer.feature().type().isSurface());
|
||||
if (layer.channel()) {
|
||||
assertTrue(layer.fluidOwned());
|
||||
assertTrue(layer.connectedFluid());
|
||||
assertTrue(layer.terrainOwned());
|
||||
assertTrue(layer.fluidHeadY() < column.terrain().naturalHeight());
|
||||
assertTrue(layer.bedY() < layer.fluidHeadY());
|
||||
} else {
|
||||
assertTrue(layer.grading());
|
||||
assertTrue(layer.terrainOwned());
|
||||
assertFalse(layer.fluidOwned());
|
||||
assertEquals(layer.bedY(), layer.fluidHeadY());
|
||||
}
|
||||
}
|
||||
SurfaceLayerColumn center = column(footprint, 30, 0);
|
||||
assertNotNull(center);
|
||||
assertTrue(center.layer().channel());
|
||||
assertEquals(79, center.layer().fluidHeadY());
|
||||
assertTrue(center.layer().feature().source() == false);
|
||||
SurfaceLayerColumn source = column(footprint, 0, 0);
|
||||
assertNotNull(source);
|
||||
assertTrue(source.layer().feature().source());
|
||||
SurfaceLayerColumn shore = column(footprint, 30, 4);
|
||||
assertNotNull(shore);
|
||||
assertTrue(shore.layer().shore());
|
||||
assertEquals("land", shore.layer().shoreBiomeKey());
|
||||
SurfaceLayerColumn bank = column(footprint, 30, -8);
|
||||
assertNotNull(bank);
|
||||
assertTrue(bank.layer().grading());
|
||||
assertFalse(bank.layer().shore());
|
||||
assertNull(column(footprint, 160, 12));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void mouthOverTheOceanPublishesOnlyANonOwningApron() {
|
||||
HydrologyTerrainSampler sampler = (int x, int z) -> x >= 100
|
||||
? HydrologyTerrainSample.ocean(50, "ocean")
|
||||
: HydrologyTerrainSample.openLand(70, 0D, "land");
|
||||
RiverCourse course = course(List.of(
|
||||
segment(1L, HydrologyFeatureType.SURFACE_POOL, 69, 69, points(0, 96, 69)),
|
||||
segment(2L, HydrologyFeatureType.WATERFALL, 69, SEA_LEVEL, List.of(new HydrologyPoint(96, 69, 0), new HydrologyPoint(97, SEA_LEVEL, 0))),
|
||||
segment(3L, HydrologyFeatureType.MOUTH, SEA_LEVEL, SEA_LEVEL, points(97, 112, SEA_LEVEL))
|
||||
));
|
||||
SurfaceFootprint footprint = compiler(sampler).compile(course);
|
||||
|
||||
int aprons = 0;
|
||||
for (SurfaceLayerColumn column : footprint.columns()) {
|
||||
if (column.terrain().ocean()) {
|
||||
assertTrue(column.layer().oceanApron());
|
||||
assertFalse(column.layer().terrainOwned());
|
||||
assertFalse(column.layer().fluidOwned());
|
||||
assertEquals(HydrologyFeatureType.MOUTH, column.layer().feature().type());
|
||||
aprons++;
|
||||
} else {
|
||||
assertFalse(column.layer().oceanApron());
|
||||
}
|
||||
}
|
||||
assertTrue(aprons > 0);
|
||||
assertNull(column(footprint, 111, 0));
|
||||
}
|
||||
|
||||
private static SurfaceLayerColumn column(SurfaceFootprint footprint, int x, int z) {
|
||||
for (SurfaceLayerColumn column : footprint.columns()) {
|
||||
if (column.x() == x && column.z() == z) {
|
||||
return column;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static SurfaceFootprintCompiler compiler(HydrologyTerrainSampler sampler) {
|
||||
return new SurfaceFootprintCompiler(HydrologyPlannerSettings.defaults(), sampler, CONSTANT_GEOMETRY);
|
||||
}
|
||||
|
||||
private static RiverCourse course(List<HydraulicSegment> segments) {
|
||||
return new RiverCourse(1L, RiverCourseType.SURFACE, OptionalLong.of(9L), OptionalLong.of(8L), "water", 1, List.of(), segments);
|
||||
}
|
||||
|
||||
private static HydraulicSegment segment(long id, HydrologyFeatureType type, int upstream, int downstream, List<HydrologyPoint> centerline) {
|
||||
return new HydraulicSegment(id, 1L, type, upstream, downstream, 6, 3, false, false, centerline);
|
||||
}
|
||||
|
||||
private static List<HydrologyPoint> points(int fromX, int toX, int y) {
|
||||
ArrayList<HydrologyPoint> points = new ArrayList<>();
|
||||
for (int x = fromX; x <= toX; x++) {
|
||||
points.add(new HydrologyPoint(x, y, 0));
|
||||
}
|
||||
return points;
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -162,7 +162,7 @@ public class ValleyProfileSolverTest {
|
||||
|
||||
private static ChannelProfile channel(int stations, HydrologyTerrainSampler sampler) {
|
||||
return new ChannelProfileBuilder(HydrologyPlannerSettings.defaults().surface(), sampler, CONSTANT_GEOMETRY)
|
||||
.build(straight(stations), path(stations), "water", false);
|
||||
.build(straight(stations), "water", false);
|
||||
}
|
||||
|
||||
private static SurfaceCenterline straight(int stations) {
|
||||
|
||||
Reference in new issue
Block a user