mirror of
https://github.com/VolmitSoftware/Iris.git
synced 2026-10-12 03:24:14 +00:00
Standing surface pools
- hydrology.surfacePools defines bowls filled with their own fluid; riverPolicy.surfacePools opts areas in - Pools are independent SURFACE_POOL courses with one STANDING_POOL segment, shaped by the erosion field, kept clear of rivers and off slopes - Locators accept the pool selector and pool ids; validator, renderer, probes and docs follow
This commit is contained in:
@@ -92,7 +92,7 @@ final class PackHydrologyValidator {
|
||||
biomes,
|
||||
errors
|
||||
);
|
||||
HydrologyValidation hydrology = validateHydrology(dimensionPath, dimension, errors, warnings);
|
||||
HydrologyValidation hydrology = validateHydrology(dimensionPath, dimension, biomes, errors, warnings);
|
||||
if (!hydrology.active()) {
|
||||
continue;
|
||||
}
|
||||
@@ -103,6 +103,7 @@ final class PackHydrologyValidator {
|
||||
dimension,
|
||||
dimensionPolicy,
|
||||
hydrology.profileIds(),
|
||||
hydrology.surfacePoolIds(),
|
||||
regions,
|
||||
biomes,
|
||||
imageMaps,
|
||||
@@ -116,6 +117,7 @@ final class PackHydrologyValidator {
|
||||
private static HydrologyValidation validateHydrology(
|
||||
String dimensionPath,
|
||||
JSONObject dimension,
|
||||
Map<String, JSONObject> biomes,
|
||||
List<String> errors,
|
||||
List<String> warnings
|
||||
) {
|
||||
@@ -156,7 +158,8 @@ final class PackHydrologyValidator {
|
||||
profileIds,
|
||||
errors
|
||||
);
|
||||
return new HydrologyValidation(riversEnabled, deepFluidsActive, profileIds);
|
||||
Set<String> surfacePoolIds = validateSurfacePools(dimensionPath + ".hydrology", hydrology, profileIds, biomes, errors);
|
||||
return new HydrologyValidation(riversEnabled, deepFluidsActive || !surfacePoolIds.isEmpty(), profileIds, surfacePoolIds);
|
||||
}
|
||||
|
||||
private static Set<String> validateRivers(
|
||||
@@ -596,6 +599,108 @@ final class PackHydrologyValidator {
|
||||
return Set.copyOf(profileIds);
|
||||
}
|
||||
|
||||
private static Set<String> validateSurfacePools(
|
||||
String path,
|
||||
JSONObject hydrology,
|
||||
Set<String> riverProfileIds,
|
||||
Map<String, JSONObject> biomes,
|
||||
List<String> errors
|
||||
) {
|
||||
if (!hydrology.has("surfacePools")) {
|
||||
return Set.of();
|
||||
}
|
||||
Object rawPools = hydrology.opt("surfacePools");
|
||||
if (!(rawPools instanceof JSONArray pools)) {
|
||||
errors.add(path + ".surfacePools must be an array.");
|
||||
return Set.of();
|
||||
}
|
||||
if (pools.length() > MAX_DEEP_FLUIDS) {
|
||||
errors.add(path + ".surfacePools must contain at most " + MAX_DEEP_FLUIDS + " entries.");
|
||||
}
|
||||
LinkedHashSet<String> ids = new LinkedHashSet<>();
|
||||
for (int index = 0; index < pools.length(); index++) {
|
||||
String poolPath = path + ".surfacePools[" + index + "]";
|
||||
JSONObject pool = pools.optJSONObject(index);
|
||||
if (pool == null) {
|
||||
errors.add(poolPath + " must be an object.");
|
||||
continue;
|
||||
}
|
||||
String id = requiredId(poolPath, pool, errors);
|
||||
if (id != null) {
|
||||
if (HydrologyFeatureQuery.isReservedKeyword(id)) {
|
||||
errors.add(poolPath + ".id '" + id + "' is a reserved hydrology feature selector.");
|
||||
} else if (riverProfileIds.contains(id)) {
|
||||
errors.add(poolPath + ".id '" + id + "' collides with a river profile.");
|
||||
} else if (!ids.add(id)) {
|
||||
errors.add(poolPath + ".id must be unique inside hydrology.surfacePools.");
|
||||
}
|
||||
}
|
||||
validateFluidPalette(poolPath, pool, true, errors);
|
||||
PackJsonFieldChecks.validateOptionalDoubleRange(poolPath, pool, "density", 0D, 64D, errors);
|
||||
PackJsonFieldChecks.validateOptionalIntegerRange(poolPath, pool, "spacing", 32, 8192, errors);
|
||||
PackJsonFieldChecks.validateOptionalIntegerRange(poolPath, pool, "minimumRadius", 2, 16, errors);
|
||||
PackJsonFieldChecks.validateOptionalIntegerRange(poolPath, pool, "maximumRadius", 2, 16, errors);
|
||||
PackJsonFieldChecks.validateOptionalIntegerRange(poolPath, pool, "depth", 1, 8, errors);
|
||||
if (integerValue(pool, "minimumRadius", 4) > integerValue(pool, "maximumRadius", 7)) {
|
||||
errors.add(poolPath + ".minimumRadius must not exceed maximumRadius.");
|
||||
}
|
||||
Object rawBiome = pool.opt("biome");
|
||||
if (rawBiome instanceof String biome && !biome.isBlank() && !biomes.containsKey(biome)) {
|
||||
errors.add(poolPath + ".biome references unknown biome '" + biome + "'.");
|
||||
}
|
||||
}
|
||||
return Set.copyOf(ids);
|
||||
}
|
||||
|
||||
private static void validatePolicyPoolsInDimension(
|
||||
String path,
|
||||
JSONObject policy,
|
||||
String dimensionKey,
|
||||
Set<String> pools,
|
||||
List<String> errors
|
||||
) {
|
||||
if (policy == null) {
|
||||
return;
|
||||
}
|
||||
JSONArray references = policy.optJSONArray("surfacePools");
|
||||
if (references == null) {
|
||||
return;
|
||||
}
|
||||
for (int index = 0; index < references.length(); index++) {
|
||||
String pool = references.optString(index, null);
|
||||
if (pool != null && ID_PATTERN.matcher(pool).matches() && !pools.contains(pool)) {
|
||||
addDistinct(errors, path + ".surfacePools[" + index + "] references unknown surface pool '"
|
||||
+ pool + "' in Dimension '" + dimensionKey + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void validatePolicyPools(String path, JSONObject policy, List<String> errors) {
|
||||
if (!policy.has("surfacePools") || policy.opt("surfacePools") == JSONObject.NULL) {
|
||||
return;
|
||||
}
|
||||
Object rawPools = policy.opt("surfacePools");
|
||||
if (!(rawPools instanceof JSONArray pools)) {
|
||||
errors.add(path + ".surfacePools must be an array or null.");
|
||||
return;
|
||||
}
|
||||
if (pools.length() > MAX_POLICY_REFERENCES) {
|
||||
errors.add(path + ".surfacePools must contain at most " + MAX_POLICY_REFERENCES + " entries.");
|
||||
}
|
||||
Set<String> seen = new HashSet<>();
|
||||
for (int index = 0; index < pools.length(); index++) {
|
||||
Object raw = pools.opt(index);
|
||||
String referencePath = path + ".surfacePools[" + index + "]";
|
||||
if (!(raw instanceof String id) || !ID_PATTERN.matcher(id).matches()) {
|
||||
errors.add(referencePath + " must name a surface pool id.");
|
||||
continue;
|
||||
}
|
||||
if (!seen.add(id)) {
|
||||
errors.add(referencePath + " duplicates surface pool '" + id + "'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean validateDeepFluids(
|
||||
String path,
|
||||
JSONObject hydrology,
|
||||
@@ -787,6 +892,7 @@ final class PackHydrologyValidator {
|
||||
validateNullableEnum(path, policy, "routing", ROUTING_MODES, errors);
|
||||
validateNullableBoolean(path, policy, "outletAdmission", errors);
|
||||
validatePolicyProfiles(path, policy, errors);
|
||||
validatePolicyPools(path, policy, errors);
|
||||
for (String biomeField : POLICY_BIOME_FIELDS) {
|
||||
validatePolicyBiomes(path, policy, biomeField, biomes, errors);
|
||||
}
|
||||
@@ -803,6 +909,7 @@ final class PackHydrologyValidator {
|
||||
JSONObject dimension,
|
||||
JSONObject dimensionPolicy,
|
||||
Set<String> profiles,
|
||||
Set<String> pools,
|
||||
Map<String, JSONObject> regions,
|
||||
Map<String, JSONObject> biomes,
|
||||
Map<String, JSONObject> imageMaps,
|
||||
@@ -816,6 +923,13 @@ final class PackHydrologyValidator {
|
||||
profiles,
|
||||
errors
|
||||
);
|
||||
validatePolicyPoolsInDimension(
|
||||
dimensionPath + ".riverPolicy",
|
||||
dimensionPolicy,
|
||||
dimensionKey,
|
||||
pools,
|
||||
errors
|
||||
);
|
||||
|
||||
Deque<String> pending = new ArrayDeque<>();
|
||||
addPolicyBiomeReferences(pending, dimensionPolicy);
|
||||
@@ -835,6 +949,13 @@ final class PackHydrologyValidator {
|
||||
profiles,
|
||||
errors
|
||||
);
|
||||
validatePolicyPoolsInDimension(
|
||||
"Region '" + regionKey + "'.riverPolicy",
|
||||
policy,
|
||||
dimensionKey,
|
||||
pools,
|
||||
errors
|
||||
);
|
||||
addPolicyBiomeReferences(pending, policy);
|
||||
for (String biomeField : REGION_BIOME_FIELDS) {
|
||||
addStringArray(pending, region.optJSONArray(biomeField));
|
||||
@@ -867,6 +988,13 @@ final class PackHydrologyValidator {
|
||||
profiles,
|
||||
errors
|
||||
);
|
||||
validatePolicyPoolsInDimension(
|
||||
"Biome '" + biomeKey + "'.riverPolicy",
|
||||
policy,
|
||||
dimensionKey,
|
||||
pools,
|
||||
errors
|
||||
);
|
||||
addPolicyBiomeReferences(pending, policy);
|
||||
addStringArray(pending, biome.optJSONArray("children"));
|
||||
addBiomeReference(pending, biome.optString("carvingBiome", null));
|
||||
@@ -1374,10 +1502,11 @@ final class PackHydrologyValidator {
|
||||
private record HydrologyValidation(
|
||||
boolean riversEnabled,
|
||||
boolean deepFluidsActive,
|
||||
Set<String> profileIds
|
||||
Set<String> profileIds,
|
||||
Set<String> surfacePoolIds
|
||||
) {
|
||||
private static HydrologyValidation inactive() {
|
||||
return new HydrologyValidation(false, false, Set.of());
|
||||
return new HydrologyValidation(false, false, Set.of(), Set.of());
|
||||
}
|
||||
|
||||
private boolean active() {
|
||||
|
||||
@@ -39,6 +39,7 @@ import art.arcane.iris.engine.object.IrisInterpolator;
|
||||
import art.arcane.iris.engine.object.IrisMaterialPalette;
|
||||
import art.arcane.iris.engine.object.IrisRegion;
|
||||
import art.arcane.iris.engine.object.IrisDeepFluidConfig;
|
||||
import art.arcane.iris.engine.object.IrisSurfacePoolConfig;
|
||||
import art.arcane.iris.engine.object.IrisHydrology;
|
||||
import art.arcane.iris.engine.object.IrisRiverHydrology;
|
||||
import art.arcane.iris.engine.object.IrisRiverProfile;
|
||||
@@ -553,6 +554,20 @@ public class IrisComplex implements DataProvider {
|
||||
throw new IllegalArgumentException("Duplicate hydrology fluid profile: " + profileKey);
|
||||
}
|
||||
}
|
||||
for (IrisSurfacePoolConfig pool : hydrology.getSurfacePools()) {
|
||||
if (pool == null) {
|
||||
throw new IllegalArgumentException("hydrology.surfacePools cannot contain null entries");
|
||||
}
|
||||
String profileKey = requireHydrologyKey(pool.getId(), "hydrology.surfacePools[].id");
|
||||
ProceduralStream<PlatformBlockState> stream = configuredFluidStream(
|
||||
pool.getFluidPalette(),
|
||||
rng.nextParallelRNG(9900 + streamIndex++),
|
||||
"hydrology surface pool " + profileKey
|
||||
);
|
||||
if (streams.putIfAbsent(profileKey, stream) != null) {
|
||||
throw new IllegalArgumentException("Duplicate hydrology fluid profile: " + profileKey);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(streams);
|
||||
}
|
||||
|
||||
|
||||
@@ -64,6 +64,7 @@ public final class IrisRenderer {
|
||||
private static final int RIVER_MOUTH = new Color(54, 164, 205).getRGB();
|
||||
private static final int RIVER_WATERFALL = new Color(190, 236, 255).getRGB();
|
||||
private static final int CONFIRMED_DEEP_LAVA = new Color(194, 50, 22).getRGB();
|
||||
private static final int STANDING_POOL = new Color(232, 120, 40).getRGB();
|
||||
private static final int PROJECTED_SOURCE = new Color(225, 75, 178).getRGB();
|
||||
private static final int PROJECTED_OUTLET = new Color(235, 145, 54).getRGB();
|
||||
private static final int PROJECTED_DEEP_FLUID = new Color(175, 70, 118).getRGB();
|
||||
@@ -199,6 +200,7 @@ public final class IrisRenderer {
|
||||
case INLAND_GROTTO -> INLAND_GROTTO;
|
||||
case MOUTH -> RIVER_MOUTH;
|
||||
case DEEP_POOL, DEEP_CHANNEL -> CONFIRMED_DEEP_LAVA;
|
||||
case STANDING_POOL -> STANDING_POOL;
|
||||
};
|
||||
}
|
||||
|
||||
@@ -215,7 +217,7 @@ public final class IrisRenderer {
|
||||
return switch (kind) {
|
||||
case SOURCE -> PROJECTED_SOURCE;
|
||||
case OUTLET -> PROJECTED_OUTLET;
|
||||
case DEEP_FLUID -> PROJECTED_DEEP_FLUID;
|
||||
case DEEP_FLUID, POOL -> PROJECTED_DEEP_FLUID;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -3,5 +3,6 @@ package art.arcane.iris.engine.hydrology;
|
||||
public enum HydrologyCandidateKind {
|
||||
SOURCE,
|
||||
OUTLET,
|
||||
DEEP_FLUID
|
||||
DEEP_FLUID,
|
||||
POOL
|
||||
}
|
||||
|
||||
+2
-1
@@ -19,5 +19,6 @@ public enum HydrologyCandidateRejection {
|
||||
SURFACE_TRANSITION_UNSUPPORTED,
|
||||
SURFACE_SEGMENT_UNSUPPORTED,
|
||||
SURFACE_SINKHOLE_CLEARANCE,
|
||||
SURFACE_SHAPE_UNSUPPORTED
|
||||
SURFACE_SHAPE_UNSUPPORTED,
|
||||
POLICY_EXCLUDED;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,11 @@ public record HydrologyFeatureQuery(Set<HydrologyFeatureType> types, String prof
|
||||
HydrologyFeatureType.DEEP_POOL,
|
||||
HydrologyFeatureType.DEEP_CHANNEL
|
||||
);
|
||||
private static final Set<HydrologyFeatureType> PROFILED = Set.of(
|
||||
HydrologyFeatureType.DEEP_POOL,
|
||||
HydrologyFeatureType.DEEP_CHANNEL,
|
||||
HydrologyFeatureType.STANDING_POOL
|
||||
);
|
||||
private static final Map<String, Set<HydrologyFeatureType>> BUILT_INS = builtIns();
|
||||
|
||||
public HydrologyFeatureQuery {
|
||||
@@ -53,7 +58,7 @@ public record HydrologyFeatureQuery(Set<HydrologyFeatureType> types, String prof
|
||||
String key = normalizeKeyword(normalized);
|
||||
Set<HydrologyFeatureType> builtInTypes = BUILT_INS.get(key);
|
||||
return builtInTypes == null
|
||||
? new HydrologyFeatureQuery(DEEP, normalized)
|
||||
? new HydrologyFeatureQuery(PROFILED, normalized)
|
||||
: new HydrologyFeatureQuery(builtInTypes, null);
|
||||
}
|
||||
|
||||
@@ -91,6 +96,7 @@ public record HydrologyFeatureQuery(Set<HydrologyFeatureType> types, String prof
|
||||
builtIns.put("mouth", Set.of(HydrologyFeatureType.MOUTH));
|
||||
builtIns.put("ridge_tunnel", Set.of(HydrologyFeatureType.RIDGE_BORE));
|
||||
builtIns.put("deep", DEEP);
|
||||
builtIns.put("pool", Set.of(HydrologyFeatureType.STANDING_POOL));
|
||||
return Collections.unmodifiableMap(builtIns);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,11 +15,13 @@ public enum HydrologyFeatureType {
|
||||
INLAND_GROTTO,
|
||||
MOUTH,
|
||||
DEEP_POOL,
|
||||
DEEP_CHANNEL;
|
||||
DEEP_CHANNEL,
|
||||
// A standing surface pool (lava or any fluid) with no course: a bowl cut into the ground with a lip.
|
||||
STANDING_POOL;
|
||||
|
||||
public boolean isSurface() {
|
||||
return switch (this) {
|
||||
case SURFACE_POOL, RIFFLE, CASCADE, WATERFALL, MOUTH -> true;
|
||||
case SURFACE_POOL, RIFFLE, CASCADE, WATERFALL, MOUTH, STANDING_POOL -> true;
|
||||
default -> false;
|
||||
};
|
||||
}
|
||||
@@ -59,6 +61,7 @@ public enum HydrologyFeatureType {
|
||||
case DEEP_CHANNEL -> 10;
|
||||
case DEEP_POOL -> 11;
|
||||
case SURFACE_POOL -> 12;
|
||||
case STANDING_POOL -> 13;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,10 @@ import art.arcane.iris.engine.hydrology.cave.CaveVoxelView;
|
||||
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveCandidate;
|
||||
import art.arcane.iris.engine.hydrology.cave.HydrologyCaveContainmentPlanner;
|
||||
import art.arcane.iris.engine.hydrology.cave.HydrologyCavePlan;
|
||||
import art.arcane.iris.engine.hydrology.surface.ChannelProfile;
|
||||
import art.arcane.iris.engine.hydrology.surface.ValleyProfile;
|
||||
import art.arcane.iris.engine.hydrology.surface.ValleyProfileSolver;
|
||||
import art.arcane.iris.engine.hydrology.surface.SurfaceCenterline;
|
||||
import art.arcane.iris.engine.hydrology.surface.SurfaceCourseBuilder;
|
||||
import art.arcane.iris.engine.hydrology.surface.SurfaceCourseResult;
|
||||
import art.arcane.iris.engine.hydrology.surface.SurfaceTerminal;
|
||||
@@ -45,6 +49,7 @@ public final class HydrologyPlanner {
|
||||
private static final long COURSE_SALT = 0x434f55525345L;
|
||||
private static final long SEGMENT_SALT = 0x5345474d454e54L;
|
||||
private static final long DEEP_FLUID_SALT = 0x44454550464cL;
|
||||
private static final long SURFACE_POOL_SALT = 0x504f4f4cL;
|
||||
private static final long DEEP_FLUID_X_OFFSET_SALT = 0x44454550584fL;
|
||||
private static final long DEEP_FLUID_Z_OFFSET_SALT = 0x444545505a4fL;
|
||||
private static final long DEEP_CHANNEL_HEADING_SALT = 0x44454550484447L;
|
||||
@@ -1101,6 +1106,7 @@ public final class HydrologyPlanner {
|
||||
if (includeDeepFluids) {
|
||||
compileDeepFluidCourses(key, courses, diagnostics);
|
||||
}
|
||||
compileSurfacePools(key, courses, diagnostics);
|
||||
HydrologyFootprintCompiler.ValidationRaster validation = footprintCompiler.compileValidation(courses);
|
||||
HydrologyObservedPlannedSurface plannedSurface = new HydrologyObservedPlannedSurface(
|
||||
validation.plannedSurface()
|
||||
@@ -6112,6 +6118,183 @@ public final class HydrologyPlanner {
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Standing surface pools: a jittered lattice of candidate sites per pool profile, admitted where the
|
||||
* policy lists the pool, the ground is open land clear of every accepted course, and the bowl fits
|
||||
* the incision cap. Each accepted pool is an independent course with one STANDING_POOL segment.
|
||||
*/
|
||||
private void compileSurfacePools(
|
||||
HydrologyTileKey key,
|
||||
List<RiverCourse> courses,
|
||||
List<HydrologyDiagnosticCandidate> diagnostics
|
||||
) {
|
||||
int tileSize = settings.routing().tileSize();
|
||||
for (HydrologyPlannerSettings.SurfacePool pool : settings.surfacePools()) {
|
||||
if (!pool.enabled() || pool.maximumPerTile() == 0) {
|
||||
continue;
|
||||
}
|
||||
long profileSeed = HydrologyHash.mix(worldSeed, SURFACE_POOL_SALT, HydrologyHash.text(pool.id()));
|
||||
int target = Math.min(pool.maximumPerTile(), expectedCount(pool.density(), HydrologyHash.mix(
|
||||
profileSeed, key.tileX(), key.tileZ())));
|
||||
int accepted = 0;
|
||||
for (DeepSite site : poolSites(key, pool, profileSeed, tileSize)) {
|
||||
if (accepted >= target) {
|
||||
addPoolDiagnostic(site, HydrologyCandidateRejection.SOURCE_QUOTA, diagnostics);
|
||||
continue;
|
||||
}
|
||||
HydrologyCandidateRejection rejection = poolAdmission(pool, site, courses);
|
||||
if (rejection != null) {
|
||||
addPoolDiagnostic(site, rejection, diagnostics);
|
||||
continue;
|
||||
}
|
||||
RiverCourse course = buildSurfacePoolCourse(pool, site);
|
||||
if (course == null) {
|
||||
addPoolDiagnostic(site, HydrologyCandidateRejection.SURFACE_CORRIDOR_UNSUPPORTED, diagnostics);
|
||||
continue;
|
||||
}
|
||||
courses.add(course);
|
||||
accepted++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private ArrayList<DeepSite> poolSites(
|
||||
HydrologyTileKey key,
|
||||
HydrologyPlannerSettings.SurfacePool pool,
|
||||
long profileSeed,
|
||||
int tileSize
|
||||
) {
|
||||
int minimumX = key.minimumBlockX(tileSize);
|
||||
int minimumZ = key.minimumBlockZ(tileSize);
|
||||
int spacing = pool.spacing();
|
||||
int xOffset = HydrologyHash.between(HydrologyHash.mix(profileSeed, DEEP_FLUID_X_OFFSET_SALT), 0, spacing - 1);
|
||||
int zOffset = HydrologyHash.between(HydrologyHash.mix(profileSeed, DEEP_FLUID_Z_OFFSET_SALT), 0, spacing - 1);
|
||||
long firstCellX = ceilDiv((long) minimumX - xOffset, spacing);
|
||||
long firstCellZ = ceilDiv((long) minimumZ - zOffset, spacing);
|
||||
long lastCellX = Math.floorDiv((long) minimumX + tileSize - 1L - xOffset, spacing);
|
||||
long lastCellZ = Math.floorDiv((long) minimumZ + tileSize - 1L - zOffset, spacing);
|
||||
ArrayList<DeepSite> sites = new ArrayList<>();
|
||||
for (long cellZ = firstCellZ; cellZ <= lastCellZ; cellZ++) {
|
||||
for (long cellX = firstCellX; cellX <= lastCellX; cellX++) {
|
||||
long stable = HydrologyHash.mix(profileSeed, cellX, cellZ);
|
||||
// Jitter inside the cell keeps pools off a visible grid.
|
||||
int jitter = Math.max(1, spacing / 3);
|
||||
int x = Math.toIntExact(xOffset + cellX * spacing + HydrologyHash.between(HydrologyHash.mix(stable, 11), -jitter, jitter));
|
||||
int z = Math.toIntExact(zOffset + cellZ * spacing + HydrologyHash.between(HydrologyHash.mix(stable, 12), -jitter, jitter));
|
||||
HydrologyTerrainSample terrain = sampleLandBasis(x, z);
|
||||
sites.add(new DeepSite(x, z, terrain == null ? 0 : terrain.naturalHeight(), stable));
|
||||
}
|
||||
}
|
||||
sites.sort(Comparator.comparingLong(DeepSite::stableId));
|
||||
return sites;
|
||||
}
|
||||
|
||||
private HydrologyCandidateRejection poolAdmission(
|
||||
HydrologyPlannerSettings.SurfacePool pool,
|
||||
DeepSite site,
|
||||
List<RiverCourse> courses
|
||||
) {
|
||||
HydrologyTerrainSample terrain = sampleLandBasis(site.x(), site.z());
|
||||
if (terrain == null || terrain.ocean() || terrain.naturalHeight() <= settings.seaLevel() + 1) {
|
||||
return HydrologyCandidateRejection.SURFACE_EXPOSURE;
|
||||
}
|
||||
if (!terrain.transitAllowed() || !terrain.surfacePoolKeys().contains(pool.id())) {
|
||||
return HydrologyCandidateRejection.POLICY_EXCLUDED;
|
||||
}
|
||||
long clearance = pool.maximumRadius() + (long) StrictMath.ceil(settings.surface().shoreWidth())
|
||||
+ settings.surface().banks().maximumBlendWidth() + settings.surface().maximumWidth();
|
||||
long clearanceSquared = clearance * clearance;
|
||||
for (RiverCourse course : courses) {
|
||||
for (HydraulicSegment segment : course.segments()) {
|
||||
for (HydrologyPoint point : segment.centerline()) {
|
||||
long deltaX = (long) point.x() - site.x();
|
||||
long deltaZ = (long) point.z() - site.z();
|
||||
if (deltaX * deltaX + deltaZ * deltaZ < clearanceSquared) {
|
||||
return HydrologyCandidateRejection.SOURCE_SPACING;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private RiverCourse buildSurfacePoolCourse(HydrologyPlannerSettings.SurfacePool pool, DeepSite site) {
|
||||
int radius = HydrologyHash.between(HydrologyHash.mix(site.stableId(), 3), pool.minimumRadius(), pool.maximumRadius());
|
||||
HydrologyTerrainSample terrain = sampleLandBasis(site.x(), site.z());
|
||||
List<HydrologyPoint> points = List.of(
|
||||
new HydrologyPoint(site.x() - 1, site.head(), site.z()),
|
||||
new HydrologyPoint(site.x(), site.head(), site.z()),
|
||||
new HydrologyPoint(site.x() + 1, site.head(), site.z())
|
||||
);
|
||||
SurfaceCenterline centerline = SurfaceCenterline.densify(points);
|
||||
int count = centerline.size();
|
||||
double[] halfWidth = new double[count];
|
||||
double[] depth = new double[count];
|
||||
double[] bank = new double[count];
|
||||
Arrays.fill(halfWidth, radius);
|
||||
Arrays.fill(depth, pool.depth());
|
||||
Arrays.fill(bank, terrain == null ? 1D : terrain.bankMultiplier());
|
||||
ChannelProfile profile = new ChannelProfile(halfWidth, depth, bank);
|
||||
ValleyProfile valley = new ValleyProfileSolver(settings.surface(), this::sampleBasis, settings.seaLevel(), 0)
|
||||
.solve(centerline, profile, SurfaceTerminal.SINKHOLE, Integer.MIN_VALUE);
|
||||
if (!valley.accepted()) {
|
||||
return null;
|
||||
}
|
||||
int center = Math.min(1, valley.exposedStations() - 1);
|
||||
int head = valley.head()[center];
|
||||
// A pool belongs in a hollow or on level ground: the whole rim must sit within a few blocks of the
|
||||
// water, or the bowl becomes a scar dug into a slope.
|
||||
int rimAllowance = pool.depth() + settings.surface().banks().inset() + 2;
|
||||
for (int station = 0; station < valley.exposedStations(); station++) {
|
||||
if (valley.crossMax()[station] - valley.head()[station] > rimAllowance) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
long courseId = HydrologyHash.mix(worldSeed, SURFACE_POOL_SALT, HydrologyHash.text(pool.id()), site.stableId());
|
||||
long segmentId = HydrologyHash.mix(courseId, SEGMENT_SALT, HydrologyFeatureType.STANDING_POOL.ordinal());
|
||||
ArrayList<HydrologyPoint> centerlinePoints = new ArrayList<>(points.size());
|
||||
for (HydrologyPoint point : points) {
|
||||
centerlinePoints.add(new HydrologyPoint(point.x(), head, point.z()));
|
||||
}
|
||||
HydraulicSegment segment = new HydraulicSegment(
|
||||
segmentId,
|
||||
courseId,
|
||||
HydrologyFeatureType.STANDING_POOL,
|
||||
head,
|
||||
head,
|
||||
radius * 2,
|
||||
pool.depth(),
|
||||
false,
|
||||
false,
|
||||
centerlinePoints
|
||||
);
|
||||
return new RiverCourse(
|
||||
courseId,
|
||||
RiverCourseType.SURFACE_POOL,
|
||||
OptionalLong.empty(),
|
||||
OptionalLong.empty(),
|
||||
pool.id(),
|
||||
1,
|
||||
List.of(),
|
||||
List.of(segment)
|
||||
);
|
||||
}
|
||||
|
||||
private void addPoolDiagnostic(
|
||||
DeepSite site,
|
||||
HydrologyCandidateRejection rejection,
|
||||
List<HydrologyDiagnosticCandidate> diagnostics
|
||||
) {
|
||||
diagnostics.add(new HydrologyDiagnosticCandidate(
|
||||
HydrologyHash.mix(site.stableId(), DIAGNOSTIC_SALT, rejection.ordinal()),
|
||||
HydrologyCandidateKind.POOL,
|
||||
HydrologyFeatureType.STANDING_POOL,
|
||||
new HydrologyPoint(site.x(), site.head(), site.z()),
|
||||
rejection,
|
||||
0
|
||||
));
|
||||
}
|
||||
|
||||
private void compileDeepFluidCourses(
|
||||
HydrologyTileKey key,
|
||||
List<RiverCourse> courses,
|
||||
|
||||
@@ -10,7 +10,8 @@ public record HydrologyPlannerSettings(
|
||||
Underground underground,
|
||||
Outlets outlets,
|
||||
Geometry geometry,
|
||||
List<DeepFluid> deepFluids
|
||||
List<DeepFluid> deepFluids,
|
||||
List<SurfacePool> surfacePools
|
||||
) {
|
||||
private static final long PLAN_FORMAT_REVISION = 6L;
|
||||
private static final int MAXIMUM_CROSS_TILE_COLOR_PERIOD = 4;
|
||||
@@ -24,7 +25,8 @@ public record HydrologyPlannerSettings(
|
||||
throw new IllegalArgumentException("Hydrology planner settings cannot contain null sections.");
|
||||
}
|
||||
deepFluids = deepFluids == null ? List.of() : List.copyOf(deepFluids);
|
||||
int publicationRadius = publicationRadius(routing, surface, underground, outlets, geometry, deepFluids);
|
||||
surfacePools = surfacePools == null ? List.of() : List.copyOf(surfacePools);
|
||||
int publicationRadius = publicationRadius(routing, surface, underground, outlets, geometry, deepFluids, surfacePools);
|
||||
if (crossTileColorPeriod(publicationRadius, routing.tileSize()) > MAXIMUM_CROSS_TILE_COLOR_PERIOD) {
|
||||
throw new IllegalArgumentException("Hydrology publication envelope exceeds the bounded cross-tile admission period.");
|
||||
}
|
||||
@@ -50,6 +52,7 @@ public record HydrologyPlannerSettings(
|
||||
12
|
||||
),
|
||||
Geometry.defaults(),
|
||||
List.of(),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
@@ -68,11 +71,14 @@ public record HydrologyPlannerSettings(
|
||||
for (DeepFluid deepFluid : deepFluids) {
|
||||
hash = HydrologyHash.mix(hash, deepFluid.hashCode(), HydrologyHash.text(deepFluid.id()));
|
||||
}
|
||||
for (SurfacePool pool : surfacePools) {
|
||||
hash = HydrologyHash.mix(hash, pool.hashCode(), HydrologyHash.text(pool.id()));
|
||||
}
|
||||
return hash;
|
||||
}
|
||||
|
||||
public int publicationRadius() {
|
||||
return publicationRadius(routing, surface, underground, outlets, geometry, deepFluids);
|
||||
return publicationRadius(routing, surface, underground, outlets, geometry, deepFluids, surfacePools);
|
||||
}
|
||||
|
||||
int crossTileColorPeriod() {
|
||||
@@ -85,7 +91,8 @@ public record HydrologyPlannerSettings(
|
||||
Underground underground,
|
||||
Outlets outlets,
|
||||
Geometry geometry,
|
||||
List<DeepFluid> deepFluids
|
||||
List<DeepFluid> deepFluids,
|
||||
List<SurfacePool> surfacePools
|
||||
) {
|
||||
int alignedHalo = Math.floorDiv(
|
||||
Math.min(
|
||||
@@ -133,6 +140,13 @@ public record HydrologyPlannerSettings(
|
||||
: 0;
|
||||
radius = Math.max(radius, Math.max(poolReach, channelReach));
|
||||
}
|
||||
for (SurfacePool pool : surfacePools) {
|
||||
if (!pool.enabled()) {
|
||||
continue;
|
||||
}
|
||||
radius = Math.max(radius, pool.maximumRadius() + (int) StrictMath.ceil(surface.shoreWidth())
|
||||
+ surface.banks().maximumBlendWidth());
|
||||
}
|
||||
return radius;
|
||||
}
|
||||
|
||||
@@ -278,6 +292,31 @@ public record HydrologyPlannerSettings(
|
||||
}
|
||||
}
|
||||
|
||||
/** A standing surface pool profile: a bowl of the given radius and depth, filled with its own fluid. */
|
||||
public record SurfacePool(
|
||||
String id,
|
||||
boolean enabled,
|
||||
double density,
|
||||
int spacing,
|
||||
int minimumRadius,
|
||||
int maximumRadius,
|
||||
int depth,
|
||||
int maximumPerTile,
|
||||
String biomeKey
|
||||
) {
|
||||
public SurfacePool {
|
||||
if (id == null || id.isBlank()) {
|
||||
throw new IllegalArgumentException("Surface pool id must not be blank.");
|
||||
}
|
||||
id = id.trim();
|
||||
if (!Double.isFinite(density) || density < 0D || spacing < 1
|
||||
|| minimumRadius < 1 || maximumRadius < minimumRadius || depth < 1 || maximumPerTile < 0) {
|
||||
throw new IllegalArgumentException("Surface pool settings are invalid.");
|
||||
}
|
||||
biomeKey = biomeKey == null || biomeKey.isBlank() ? null : biomeKey.trim();
|
||||
}
|
||||
}
|
||||
|
||||
// The routing gates read one hydraulic threshold: the drop that makes a reach a waterfall.
|
||||
public record Hydraulics(int waterfallMinimumDrop) {
|
||||
public Hydraulics {
|
||||
|
||||
@@ -29,7 +29,8 @@ public record HydrologyTerrainSample(
|
||||
String shoreBiomeKey,
|
||||
String bankBiomeKey,
|
||||
String floodedCaveBiomeKey,
|
||||
List<String> preferredProfileKeys
|
||||
List<String> preferredProfileKeys,
|
||||
List<String> surfacePoolKeys
|
||||
) {
|
||||
public HydrologyTerrainSample {
|
||||
requireFiniteNonNegative(slope, "slope");
|
||||
@@ -51,6 +52,7 @@ public record HydrologyTerrainSample(
|
||||
bankBiomeKey = normalizeKey(bankBiomeKey, parentBiomeKey);
|
||||
floodedCaveBiomeKey = normalizeKey(floodedCaveBiomeKey, surfaceBiomeKey);
|
||||
preferredProfileKeys = normalizeProfiles(preferredProfileKeys);
|
||||
surfacePoolKeys = surfacePoolKeys == null ? List.of() : List.copyOf(surfacePoolKeys);
|
||||
}
|
||||
|
||||
public static HydrologyTerrainSample openLand(int naturalHeight, double slope, String parentBiomeKey) {
|
||||
@@ -81,7 +83,8 @@ public record HydrologyTerrainSample(
|
||||
parentBiomeKey,
|
||||
parentBiomeKey,
|
||||
parentBiomeKey,
|
||||
List.of("default")
|
||||
List.of("default"),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -113,7 +116,41 @@ public record HydrologyTerrainSample(
|
||||
parentBiomeKey,
|
||||
parentBiomeKey,
|
||||
parentBiomeKey,
|
||||
List.of("default")
|
||||
List.of("default"),
|
||||
List.of()
|
||||
);
|
||||
}
|
||||
|
||||
public HydrologyTerrainSample withSurfacePoolKeys(List<String> replacementPoolKeys) {
|
||||
return new HydrologyTerrainSample(
|
||||
naturalHeight,
|
||||
slope,
|
||||
ocean,
|
||||
caveAvailable,
|
||||
caveFloorY,
|
||||
caveFluidY,
|
||||
transitAllowed,
|
||||
outletAllowed,
|
||||
surfaceSourceAllowed,
|
||||
surfaceSourceRequired,
|
||||
undergroundSourceAllowed,
|
||||
undergroundSourceRequired,
|
||||
routingCost,
|
||||
surfaceSourceWeight,
|
||||
undergroundSourceWeight,
|
||||
widthMultiplier,
|
||||
depthMultiplier,
|
||||
incisionMultiplier,
|
||||
routingMultiplier,
|
||||
bankMultiplier,
|
||||
parentBiomeKey,
|
||||
surfaceBiomeKey,
|
||||
mouthBiomeKey,
|
||||
shoreBiomeKey,
|
||||
bankBiomeKey,
|
||||
floodedCaveBiomeKey,
|
||||
preferredProfileKeys,
|
||||
replacementPoolKeys
|
||||
);
|
||||
}
|
||||
|
||||
@@ -145,7 +182,8 @@ public record HydrologyTerrainSample(
|
||||
shoreBiomeKey,
|
||||
bankBiomeKey,
|
||||
floodedCaveBiomeKey,
|
||||
preferredProfileKeys
|
||||
preferredProfileKeys,
|
||||
surfacePoolKeys
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -38,10 +38,11 @@ public record RiverCourse(
|
||||
}
|
||||
}
|
||||
validateSinkholeContinuation(type, segments);
|
||||
if (type == RiverCourseType.DEEP_FLUID && (sourceNodeId.isPresent() || outletId.isPresent())) {
|
||||
throw new IllegalArgumentException("Independent deep-fluid courses cannot reference drainage nodes or river outlets.");
|
||||
boolean independent = type == RiverCourseType.DEEP_FLUID || type == RiverCourseType.SURFACE_POOL;
|
||||
if (independent && (sourceNodeId.isPresent() || outletId.isPresent())) {
|
||||
throw new IllegalArgumentException("Independent deep-fluid and pool courses cannot reference drainage nodes or river outlets.");
|
||||
}
|
||||
if (type != RiverCourseType.DEEP_FLUID && (sourceNodeId.isEmpty() || outletId.isEmpty())) {
|
||||
if (!independent && (sourceNodeId.isEmpty() || outletId.isEmpty())) {
|
||||
throw new IllegalArgumentException("Surface and underground courses require a source and outlet.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,5 +3,6 @@ package art.arcane.iris.engine.hydrology;
|
||||
public enum RiverCourseType {
|
||||
SURFACE,
|
||||
UNDERGROUND,
|
||||
DEEP_FLUID
|
||||
DEEP_FLUID,
|
||||
SURFACE_POOL
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ public record EffectiveRiverPolicy(
|
||||
List<String> shoreBiomes,
|
||||
List<String> bankBiomes,
|
||||
List<String> floodedCaveBiomes,
|
||||
List<String> surfacePools,
|
||||
double widthMultiplier,
|
||||
double depthMultiplier,
|
||||
double incisionMultiplier,
|
||||
@@ -31,6 +32,7 @@ public record EffectiveRiverPolicy(
|
||||
shoreBiomes = List.copyOf(shoreBiomes);
|
||||
bankBiomes = List.copyOf(bankBiomes);
|
||||
floodedCaveBiomes = List.copyOf(floodedCaveBiomes);
|
||||
surfacePools = List.copyOf(surfacePools);
|
||||
}
|
||||
|
||||
public boolean allowsSources() {
|
||||
|
||||
@@ -43,6 +43,7 @@ public final class RiverPolicyResolver {
|
||||
private List<String> shoreBiomes = List.of();
|
||||
private List<String> bankBiomes = List.of();
|
||||
private List<String> floodedCaveBiomes = List.of();
|
||||
private List<String> surfacePools = List.of();
|
||||
private double widthMultiplier = 1D;
|
||||
private double depthMultiplier = 1D;
|
||||
private double incisionMultiplier = 1D;
|
||||
@@ -80,6 +81,9 @@ public final class RiverPolicyResolver {
|
||||
if (policy.getFloodedCaveBiomes() != null) {
|
||||
floodedCaveBiomes = List.copyOf(policy.getFloodedCaveBiomes());
|
||||
}
|
||||
if (policy.getSurfacePools() != null) {
|
||||
surfacePools = List.copyOf(policy.getSurfacePools());
|
||||
}
|
||||
if (policy.getWidthMultiplier() != null) {
|
||||
widthMultiplier = policy.getWidthMultiplier();
|
||||
}
|
||||
@@ -108,6 +112,7 @@ public final class RiverPolicyResolver {
|
||||
shoreBiomes,
|
||||
bankBiomes,
|
||||
floodedCaveBiomes,
|
||||
surfacePools,
|
||||
widthMultiplier,
|
||||
depthMultiplier,
|
||||
incisionMultiplier,
|
||||
|
||||
+29
-5
@@ -22,6 +22,7 @@ import art.arcane.iris.engine.hydrology.policy.RiverPolicyResolver;
|
||||
import art.arcane.iris.engine.object.IrisBiome;
|
||||
import art.arcane.iris.engine.object.IrisCoastalRiverGrottoConfig;
|
||||
import art.arcane.iris.engine.object.IrisDeepFluidConfig;
|
||||
import art.arcane.iris.engine.object.IrisSurfacePoolConfig;
|
||||
import art.arcane.iris.engine.object.IrisDimension;
|
||||
import art.arcane.iris.engine.object.IrisHydrology;
|
||||
import art.arcane.iris.engine.object.IrisInlandRiverGrottoConfig;
|
||||
@@ -364,11 +365,14 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
|
||||
}
|
||||
|
||||
public List<String> featureQueryKeys() {
|
||||
ArrayList<String> deepFluidIds = new ArrayList<>(settings.deepFluids().size());
|
||||
ArrayList<String> profiledIds = new ArrayList<>(settings.deepFluids().size() + settings.surfacePools().size());
|
||||
for (HydrologyPlannerSettings.DeepFluid deepFluid : settings.deepFluids()) {
|
||||
deepFluidIds.add(deepFluid.id());
|
||||
profiledIds.add(deepFluid.id());
|
||||
}
|
||||
return HydrologyFeatureQuery.suggestions(deepFluidIds);
|
||||
for (HydrologyPlannerSettings.SurfacePool pool : settings.surfacePools()) {
|
||||
profiledIds.add(pool.id());
|
||||
}
|
||||
return HydrologyFeatureQuery.suggestions(profiledIds);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -487,7 +491,8 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
|
||||
selectKey(policy.shoreBiomes(), parentBiomeKey, biomePatchNoise, 3),
|
||||
selectKey(policy.bankBiomes(), parentBiomeKey, biomePatchNoise, 4),
|
||||
selectKey(policy.floodedCaveBiomes(), parentBiomeKey, biomePatchNoise, 5),
|
||||
profiles
|
||||
profiles,
|
||||
policy.surfacePools()
|
||||
);
|
||||
return new IrisHydrologyRoutingTerrainSampler.TerrainBasis(resolvedNaturalHeight, terrain);
|
||||
}
|
||||
@@ -764,10 +769,29 @@ public final class IrisHydrologyRuntime implements AutoCloseable {
|
||||
plannerUnderground,
|
||||
plannerOutlets,
|
||||
plannerGeometry,
|
||||
deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, plannerRouting.refinementSpacing())
|
||||
deepFluids(hydrology.getDeepFluids(), minimumWorldY, routing, plannerRouting.refinementSpacing()),
|
||||
surfacePools(hydrology.getSurfacePools())
|
||||
);
|
||||
}
|
||||
|
||||
private static List<HydrologyPlannerSettings.SurfacePool> surfacePools(List<IrisSurfacePoolConfig> configurations) {
|
||||
ArrayList<HydrologyPlannerSettings.SurfacePool> pools = new ArrayList<>();
|
||||
for (IrisSurfacePoolConfig configuration : configurations) {
|
||||
pools.add(new HydrologyPlannerSettings.SurfacePool(
|
||||
configuration.getId(),
|
||||
configuration.getDensity() > 0D,
|
||||
configuration.getDensity(),
|
||||
configuration.getSpacing(),
|
||||
configuration.getMinimumRadius(),
|
||||
configuration.getMaximumRadius(),
|
||||
configuration.getDepth(),
|
||||
Math.min(64, maximumSources(configuration.getDensity(), 0)),
|
||||
configuration.getBiome()
|
||||
));
|
||||
}
|
||||
return List.copyOf(pools);
|
||||
}
|
||||
|
||||
private static HydrologyPlannerSettings.Geometry geometry(
|
||||
IrisRiverGeometryConfig geometry,
|
||||
HydrologyPlannerSettings.ChannelShape surfaceShape
|
||||
|
||||
+65
-4
@@ -42,7 +42,8 @@ public final class SurfaceFootprintCompiler {
|
||||
}
|
||||
|
||||
public SurfaceFootprint compile(RiverCourse course) {
|
||||
if (course.type() != RiverCourseType.SURFACE) {
|
||||
boolean pool = course.type() == RiverCourseType.SURFACE_POOL;
|
||||
if (course.type() != RiverCourseType.SURFACE && !pool) {
|
||||
return SurfaceFootprint.empty();
|
||||
}
|
||||
ArrayList<HydraulicSegment> exposed = new ArrayList<>();
|
||||
@@ -59,10 +60,13 @@ public final class SurfaceFootprintCompiler {
|
||||
if (stations.count() < 1) {
|
||||
return SurfaceFootprint.empty();
|
||||
}
|
||||
SurfaceTerminal terminal = terminal(course, exposed);
|
||||
SurfaceTerminal terminal = pool ? SurfaceTerminal.SINKHOLE : terminal(course, exposed);
|
||||
SurfaceCenterline centerline = SurfaceCenterline.densify(stations.points());
|
||||
ChannelProfile channel = new ChannelProfileBuilder(settings.surface(), sampler, geometry)
|
||||
ChannelProfile channel = pool
|
||||
? poolProfile(exposed.getFirst(), centerline)
|
||||
: new ChannelProfileBuilder(settings.surface(), sampler, geometry)
|
||||
.build(centerline, course.profileKey(), terminal == SurfaceTerminal.OCEAN_MOUTH);
|
||||
String poolBiome = pool ? poolBiome(course.profileKey()) : null;
|
||||
ValleyProfile valley = ValleyProfile.fromHeads(stations.head(), stations.exposedStations());
|
||||
ErosionField field = new ErosionFieldCompiler(settings.surface(), sampler, settings.seaLevel()).compile(
|
||||
HydrologyHash.mix(course.id(), COURSE_SEED_SALT),
|
||||
@@ -90,7 +94,7 @@ public final class SurfaceFootprintCompiler {
|
||||
column.x(),
|
||||
column.z(),
|
||||
column.terrain(),
|
||||
layer(feature, column, course.profileKey()),
|
||||
pool ? poolLayer(feature, column, course.profileKey(), poolBiome) : layer(feature, column, course.profileKey()),
|
||||
column.role(),
|
||||
column.apron()
|
||||
));
|
||||
@@ -98,6 +102,63 @@ public final class SurfaceFootprintCompiler {
|
||||
return new SurfaceFootprint(columns, field.uncontainedWetCells());
|
||||
}
|
||||
|
||||
/** A pool's bowl and rim carry the pool biome when one is configured, otherwise the surrounding biome. */
|
||||
private static HydrologyColumnLayer poolLayer(
|
||||
HydrologyFeatureRef feature,
|
||||
SurfaceColumn column,
|
||||
String profileKey,
|
||||
String poolBiome
|
||||
) {
|
||||
HydrologyTerrainSample terrain = column.terrain();
|
||||
String biome = poolBiome == null ? terrain.parentBiomeKey() : poolBiome;
|
||||
boolean channel = column.role() == SurfaceRole.CHANNEL;
|
||||
boolean shore = column.role() == SurfaceRole.SHORE;
|
||||
return new HydrologyColumnLayer(
|
||||
feature,
|
||||
column.height(),
|
||||
column.headY(),
|
||||
column.headY(),
|
||||
channel,
|
||||
shore,
|
||||
!channel,
|
||||
channel,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
channel,
|
||||
false,
|
||||
profileKey,
|
||||
biome,
|
||||
biome,
|
||||
biome,
|
||||
biome,
|
||||
terrain.floodedCaveBiomeKey()
|
||||
);
|
||||
}
|
||||
|
||||
private ChannelProfile poolProfile(HydraulicSegment segment, SurfaceCenterline centerline) {
|
||||
int count = centerline.size();
|
||||
double[] halfWidth = new double[count];
|
||||
double[] depth = new double[count];
|
||||
double[] bank = new double[count];
|
||||
for (int station = 0; station < count; station++) {
|
||||
HydrologyTerrainSample terrain = sampler.sample(centerline.x()[station], centerline.z()[station]);
|
||||
halfWidth[station] = Math.max(0.5D, segment.width() / 2D);
|
||||
depth[station] = Math.max(1D, segment.depth());
|
||||
bank[station] = terrain == null ? 1D : terrain.bankMultiplier();
|
||||
}
|
||||
return new ChannelProfile(halfWidth, depth, bank);
|
||||
}
|
||||
|
||||
private String poolBiome(String poolId) {
|
||||
for (HydrologyPlannerSettings.SurfacePool pool : settings.surfacePools()) {
|
||||
if (pool.id().equals(poolId)) {
|
||||
return pool.biomeKey();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private static HydrologyColumnLayer layer(HydrologyFeatureRef feature, SurfaceColumn column, String profileKey) {
|
||||
HydrologyTerrainSample terrain = column.terrain();
|
||||
if (column.apron()) {
|
||||
|
||||
@@ -18,4 +18,8 @@ public class IrisHydrology {
|
||||
@ArrayType(type = IrisDeepFluidConfig.class)
|
||||
@Desc("Independent deep-fluid systems that do not consume the surface or underground river source budgets.")
|
||||
private KList<IrisDeepFluidConfig> deepFluids = new KList<>();
|
||||
|
||||
@ArrayType(type = IrisSurfacePoolConfig.class, min = 0)
|
||||
@Desc("Standing surface pools such as lava pools. Each region or biome opts in through riverPolicy.surfacePools.")
|
||||
private KList<IrisSurfacePoolConfig> surfacePools = new KList<>();
|
||||
}
|
||||
|
||||
@@ -54,6 +54,10 @@ public class IrisRiverPolicy {
|
||||
@Desc("Flooded cave and grotto biome selection. An empty list explicitly clears the inherited selection.")
|
||||
private KList<String> floodedCaveBiomes = null;
|
||||
|
||||
@ArrayType(type = String.class)
|
||||
@Desc("Standing surface pool ids (hydrology.surfacePools) allowed in this area. An empty list explicitly clears the inherited selection.")
|
||||
private KList<String> surfacePools = null;
|
||||
|
||||
@MinNumber(0.0001)
|
||||
@MaxNumber(16)
|
||||
@Desc("Multiplier applied to accepted river width.")
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
package art.arcane.iris.engine.object;
|
||||
|
||||
import art.arcane.iris.engine.object.annotations.ArrayType;
|
||||
import art.arcane.iris.engine.object.annotations.Desc;
|
||||
import art.arcane.iris.engine.object.annotations.MaxNumber;
|
||||
import art.arcane.iris.engine.object.annotations.MinNumber;
|
||||
import art.arcane.iris.engine.object.annotations.RegistryListResource;
|
||||
import art.arcane.iris.engine.object.annotations.Required;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.experimental.Accessors;
|
||||
|
||||
@Accessors(chain = true)
|
||||
@NoArgsConstructor
|
||||
@Desc("A standing surface pool: a bowl cut into open ground and filled with a fluid, placed where the river policy allows it.")
|
||||
@Data
|
||||
public class IrisSurfacePoolConfig {
|
||||
@Required
|
||||
@Desc("Pool identifier. Policies list it under surfacePools and locators accept it as a selector.")
|
||||
private String id = "lava_pool";
|
||||
|
||||
@Required
|
||||
@Desc("Fluid palette filling the pool.")
|
||||
private IrisMaterialPalette fluidPalette = new IrisMaterialPalette().qclear().qadd("lava");
|
||||
|
||||
@MinNumber(0)
|
||||
@MaxNumber(64)
|
||||
@Desc("Expected pools per hydrology tile where the policy allows them.")
|
||||
private double density = 0.75D;
|
||||
|
||||
@MinNumber(32)
|
||||
@MaxNumber(8192)
|
||||
@Desc("Nominal spacing in blocks between candidate pool sites.")
|
||||
private int spacing = 384;
|
||||
|
||||
@MinNumber(2)
|
||||
@MaxNumber(16)
|
||||
@Desc("Smallest pool radius in blocks.")
|
||||
private int minimumRadius = 4;
|
||||
|
||||
@MinNumber(2)
|
||||
@MaxNumber(16)
|
||||
@Desc("Largest pool radius in blocks.")
|
||||
private int maximumRadius = 7;
|
||||
|
||||
@MinNumber(1)
|
||||
@MaxNumber(8)
|
||||
@Desc("Fluid depth at the centre of the pool.")
|
||||
private int depth = 2;
|
||||
|
||||
@RegistryListResource(IrisBiome.class)
|
||||
@Desc("Biome applied to the pool bed and its rim. Leave empty to keep the surrounding biome.")
|
||||
private String biome = "";
|
||||
}
|
||||
@@ -156,6 +156,70 @@ public class PackHydrologyValidatorTest {
|
||||
assertTrue(result.errors().toString(), result.errors().stream().noneMatch((String error) -> error.contains(".bed")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfacePoolRulesRejectBadRangesAndUnknownPolicyReferences() throws Exception {
|
||||
File pack = pack("""
|
||||
{
|
||||
"regions": ["region"],
|
||||
"dimensionHeight": {"min": -256, "max": 512},
|
||||
"hydrology": {
|
||||
"rivers": {"enabled": true, "routing": {"tileSize": 1024, "sampleSpacing": 64, "oceanOutlets": true}},
|
||||
"surfacePools": [
|
||||
{
|
||||
"id": "lava_pool",
|
||||
"fluidPalette": {"palette": [{"block": "minecraft:lava"}]},
|
||||
"spacing": 16,
|
||||
"minimumRadius": 8,
|
||||
"maximumRadius": 4,
|
||||
"depth": 9,
|
||||
"biome": "no/such/biome"
|
||||
},
|
||||
{"id": "pool", "fluidPalette": {"palette": [{"block": "minecraft:water"}]}}
|
||||
]
|
||||
},
|
||||
"riverPolicy": {"surfacePools": ["lava_pool", "missing_pool"]}
|
||||
}
|
||||
""");
|
||||
|
||||
PackHydrologyValidator.Validation result = validate(pack);
|
||||
|
||||
assertContains(result.errors(), "spacing must be at least 32");
|
||||
assertContains(result.errors(), "minimumRadius must not exceed maximumRadius");
|
||||
assertContains(result.errors(), "depth must be at most 8");
|
||||
assertContains(result.errors(), "biome references unknown biome 'no/such/biome'");
|
||||
assertContains(result.errors(), "'pool' is a reserved hydrology feature selector");
|
||||
assertContains(result.errors(), "references unknown surface pool 'missing_pool'");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void surfacePoolsAcceptAConfiguredPoolAndItsPolicyReference() throws Exception {
|
||||
File pack = pack("""
|
||||
{
|
||||
"regions": ["region"],
|
||||
"dimensionHeight": {"min": -256, "max": 512},
|
||||
"hydrology": {
|
||||
"rivers": {"enabled": true, "routing": {"tileSize": 1024, "sampleSpacing": 64, "oceanOutlets": true}},
|
||||
"surfacePools": [
|
||||
{
|
||||
"id": "lava_pool",
|
||||
"fluidPalette": {"palette": [{"block": "minecraft:lava"}]},
|
||||
"density": 0.75,
|
||||
"spacing": 384,
|
||||
"minimumRadius": 4,
|
||||
"maximumRadius": 7,
|
||||
"depth": 2
|
||||
}
|
||||
]
|
||||
},
|
||||
"riverPolicy": {"surfacePools": ["lava_pool"]}
|
||||
}
|
||||
""");
|
||||
|
||||
PackHydrologyValidator.Validation result = validate(pack);
|
||||
|
||||
assertTrue(result.errors().toString(), result.errors().stream().noneMatch((String error) -> error.contains("surfacePool")));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void rejectsMouthLevelingDistanceBeyondTheRouteLength() throws Exception {
|
||||
File pack = pack(riversDimension("""
|
||||
|
||||
@@ -46,6 +46,7 @@ public class IrisRendererRiverTest {
|
||||
expected.put(HydrologyFeatureType.MOUTH, new Color(54, 164, 205).getRGB());
|
||||
expected.put(HydrologyFeatureType.DEEP_POOL, new Color(194, 50, 22).getRGB());
|
||||
expected.put(HydrologyFeatureType.DEEP_CHANNEL, new Color(194, 50, 22).getRGB());
|
||||
expected.put(HydrologyFeatureType.STANDING_POOL, new Color(232, 120, 40).getRGB());
|
||||
|
||||
assertEquals(HydrologyFeatureType.values().length, expected.size());
|
||||
for (HydrologyFeatureType type : HydrologyFeatureType.values()) {
|
||||
|
||||
+1
-1
@@ -268,7 +268,7 @@ public class HydrologyFootprintCompilerTest {
|
||||
base.underground(),
|
||||
base.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of(deepFluid)
|
||||
List.of(deepFluid), List.of()
|
||||
);
|
||||
HydraulicSegment segment = new HydraulicSegment(
|
||||
15L,
|
||||
|
||||
@@ -510,6 +510,7 @@ public class HydrologyModelTest {
|
||||
"mouth",
|
||||
"ridge_tunnel",
|
||||
"deep",
|
||||
"pool",
|
||||
"acid",
|
||||
"deep_lava"
|
||||
), suggestions);
|
||||
@@ -527,7 +528,8 @@ public class HydrologyModelTest {
|
||||
"inland_grotto",
|
||||
"mouth",
|
||||
"ridge_tunnel",
|
||||
"deep"
|
||||
"deep",
|
||||
"pool"
|
||||
);
|
||||
|
||||
assertEquals(builtInKeywords, HydrologyFeatureQuery.suggestions(List.of()));
|
||||
|
||||
+5
-5
@@ -288,7 +288,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
|
||||
8
|
||||
),
|
||||
stableGeometry(),
|
||||
List.of(deepFluid)
|
||||
List.of(deepFluid), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -362,7 +362,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
|
||||
base.outlets().maximumPerTile()
|
||||
),
|
||||
base.geometry(),
|
||||
base.deepFluids()
|
||||
base.deepFluids(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -404,7 +404,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -445,7 +445,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
|
||||
sample.shoreBiomeKey(),
|
||||
sample.bankBiomeKey(),
|
||||
sample.floodedCaveBiomeKey(),
|
||||
sample.preferredProfileKeys()
|
||||
sample.preferredProfileKeys(), List.of()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -478,7 +478,7 @@ public class HydrologyPlannerCrossTileCaveAdmissionIntegrationTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+5
-5
@@ -704,7 +704,7 @@ public class HydrologyPlannerGlobalInvariantTest {
|
||||
8
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
deepFluids
|
||||
deepFluids, List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -752,7 +752,7 @@ public class HydrologyPlannerGlobalInvariantTest {
|
||||
),
|
||||
base.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
base.deepFluids()
|
||||
base.deepFluids(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -945,7 +945,7 @@ public class HydrologyPlannerGlobalInvariantTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("default")
|
||||
List.of("default"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -996,7 +996,7 @@ public class HydrologyPlannerGlobalInvariantTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("default")
|
||||
List.of("default"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1045,7 +1045,7 @@ public class HydrologyPlannerGlobalInvariantTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("default")
|
||||
List.of("default"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+1
-1
@@ -62,7 +62,7 @@ public class HydrologyPlannerSettingsTest {
|
||||
base.underground(),
|
||||
base.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of(overlongChannel)
|
||||
List.of(overlongChannel), List.of()
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
+119
@@ -0,0 +1,119 @@
|
||||
package art.arcane.iris.engine.hydrology;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class HydrologyPlannerSurfacePoolTest {
|
||||
private static final HydrologyTileKey TILE = new HydrologyTileKey(0, 0);
|
||||
|
||||
@Test
|
||||
public void poolsAreCarvedWhereThePolicyAllowsThemAndNowhereElse() {
|
||||
HydrologyPlannerSettings settings = settings(2D, 128, 4, 6, 2);
|
||||
HydrologyTerrainSampler terrain = (int x, int z) -> HydrologyTerrainSample.openLand(80, 0D, "land")
|
||||
.withSurfacePoolKeys(x < 512 ? List.of("lava_pool") : List.of());
|
||||
|
||||
HydrologyTile tile = new HydrologyPlanner(91L, settings, terrain).plan(TILE);
|
||||
HydrologyTile replay = new HydrologyPlanner(91L, settings, terrain).plan(TILE);
|
||||
|
||||
assertEquals(tile, replay);
|
||||
List<RiverCourse> pools = pools(tile);
|
||||
assertFalse(tile.diagnosticCandidates().toString(), pools.isEmpty());
|
||||
for (RiverCourse pool : pools) {
|
||||
assertEquals("lava_pool", pool.profileKey());
|
||||
assertEquals(1, pool.segments().size());
|
||||
HydraulicSegment segment = pool.segments().getFirst();
|
||||
assertEquals(HydrologyFeatureType.STANDING_POOL, segment.type());
|
||||
assertTrue(segment.width() >= 8 && segment.width() <= 12);
|
||||
assertTrue(segment.start().x() < 512);
|
||||
assertEquals(79, segment.upstreamHeadY());
|
||||
assertEquals(segment.upstreamHeadY(), segment.downstreamHeadY());
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void aPoolIsABowlWithALipInsideTheFootprint() {
|
||||
HydrologyPlannerSettings settings = settings(2D, 256, 5, 5, 2);
|
||||
HydrologyTerrainSampler terrain = (int x, int z) -> HydrologyTerrainSample.openLand(80, 0D, "land")
|
||||
.withSurfacePoolKeys(List.of("lava_pool"));
|
||||
|
||||
HydrologyTile tile = new HydrologyPlanner(7L, settings, terrain).plan(TILE);
|
||||
RiverCourse pool = pools(tile).getFirst();
|
||||
HydrologyPoint center = pool.segments().getFirst().centerline().get(1);
|
||||
|
||||
int wet = 0;
|
||||
int shore = 0;
|
||||
for (HydrologyColumnSample column : tile.footprint().columns().values()) {
|
||||
HydrologyColumnLayer layer = column.primarySurfaceLayer().orElse(null);
|
||||
if (layer == null || layer.feature().courseId() != pool.id()) {
|
||||
continue;
|
||||
}
|
||||
assertEquals(HydrologyFeatureType.STANDING_POOL, layer.feature().type());
|
||||
assertTrue(column.terrainHeight() <= column.naturalHeight());
|
||||
if (layer.channel()) {
|
||||
wet++;
|
||||
assertEquals("lava_pool", layer.profileKey());
|
||||
assertEquals(79, layer.fluidHeadY());
|
||||
assertTrue(layer.bedY() < layer.fluidHeadY());
|
||||
assertTrue(center.distanceSquared2D(new HydrologyPoint(column.x(), 0, column.z())) <= 8 * 8);
|
||||
} else if (layer.shore()) {
|
||||
shore++;
|
||||
assertTrue(column.terrainHeight() >= 80);
|
||||
}
|
||||
}
|
||||
assertTrue("wet=" + wet, wet >= 60 && wet <= 140);
|
||||
assertTrue("shore=" + shore, shore > 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void poolsKeepClearOfEachOther() {
|
||||
HydrologyPlannerSettings settings = settings(8D, 64, 6, 6, 2);
|
||||
HydrologyTerrainSampler terrain = (int x, int z) -> HydrologyTerrainSample.openLand(80, 0D, "land")
|
||||
.withSurfacePoolKeys(List.of("lava_pool"));
|
||||
|
||||
HydrologyTile tile = new HydrologyPlanner(3L, settings, terrain).plan(TILE);
|
||||
List<RiverCourse> pools = pools(tile);
|
||||
|
||||
assertTrue(pools.size() >= 2);
|
||||
for (int first = 0; first < pools.size(); first++) {
|
||||
for (int second = first + 1; second < pools.size(); second++) {
|
||||
HydrologyPoint a = pools.get(first).segments().getFirst().centerline().get(1);
|
||||
HydrologyPoint b = pools.get(second).segments().getFirst().centerline().get(1);
|
||||
assertTrue(a.distanceSquared2D(b) >= 40L * 40L);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<RiverCourse> pools(HydrologyTile tile) {
|
||||
ArrayList<RiverCourse> pools = new ArrayList<>();
|
||||
for (RiverCourse course : tile.courses()) {
|
||||
if (course.type() == RiverCourseType.SURFACE_POOL) {
|
||||
pools.add(course);
|
||||
}
|
||||
}
|
||||
return pools;
|
||||
}
|
||||
|
||||
private static HydrologyPlannerSettings settings(double density, int spacing, int minimumRadius, int maximumRadius, int depth) {
|
||||
HydrologyPlannerSettings base = HydrologyPlannerSettings.defaults();
|
||||
HydrologyPlannerSettings.Routing routing = new HydrologyPlannerSettings.Routing(
|
||||
1024, 64, base.routing().maximumRouteNodes(), 4096, 384, 192, 1.5D, 24D, 2D, 0.2D, 1D);
|
||||
return new HydrologyPlannerSettings(
|
||||
base.seaLevel(),
|
||||
routing,
|
||||
base.surface(),
|
||||
base.hydraulics(),
|
||||
base.underground(),
|
||||
base.outlets(),
|
||||
base.geometry(),
|
||||
List.of(),
|
||||
List.of(new HydrologyPlannerSettings.SurfacePool(
|
||||
"lava_pool", true, density, spacing, minimumRadius, maximumRadius, depth, 64, null))
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -514,7 +514,7 @@ public class HydrologyPlannerTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("alpha")
|
||||
List.of("alpha"), List.of()
|
||||
);
|
||||
};
|
||||
HydrologyTile tile = new HydrologyPlanner(812L, settings, terrain, solidCaveView()).plan(TILE);
|
||||
@@ -588,7 +588,7 @@ public class HydrologyPlannerTest {
|
||||
base.underground(),
|
||||
base.outlets(),
|
||||
base.geometry(),
|
||||
base.deepFluids()
|
||||
base.deepFluids(), List.of()
|
||||
);
|
||||
HydrologyTerrainSampler repeatedRidges = (int x, int z) -> {
|
||||
if (x >= 112) {
|
||||
@@ -1122,7 +1122,7 @@ public class HydrologyPlannerTest {
|
||||
base.underground(),
|
||||
base.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
base.deepFluids()
|
||||
base.deepFluids(), List.of()
|
||||
);
|
||||
HydrologyPlannerSettings settings = withOutlets(shaped, new HydrologyPlannerSettings.Outlets(
|
||||
true,
|
||||
@@ -1408,7 +1408,7 @@ public class HydrologyPlannerTest {
|
||||
base.underground(),
|
||||
base.outlets(),
|
||||
base.geometry(),
|
||||
base.deepFluids()
|
||||
base.deepFluids(), List.of()
|
||||
);
|
||||
HydrologyTerrainSampler terrain = (int x, int z) -> {
|
||||
if (x >= 112) {
|
||||
@@ -1769,7 +1769,7 @@ public class HydrologyPlannerTest {
|
||||
4
|
||||
),
|
||||
stableGeometry(),
|
||||
deepFluids
|
||||
deepFluids, List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1824,7 +1824,7 @@ public class HydrologyPlannerTest {
|
||||
1
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of()
|
||||
List.of(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1893,7 +1893,7 @@ public class HydrologyPlannerTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("alpha")
|
||||
List.of("alpha"), List.of()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -1958,7 +1958,7 @@ public class HydrologyPlannerTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("alpha", "beta")
|
||||
List.of("alpha", "beta"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1990,7 +1990,7 @@ public class HydrologyPlannerTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("alpha")
|
||||
List.of("alpha"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2031,7 +2031,7 @@ public class HydrologyPlannerTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("beta", "alpha")
|
||||
List.of("beta", "alpha"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2063,7 +2063,7 @@ public class HydrologyPlannerTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("alpha")
|
||||
List.of("alpha"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2095,7 +2095,7 @@ public class HydrologyPlannerTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("alpha", "beta")
|
||||
List.of("alpha", "beta"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2121,7 +2121,7 @@ public class HydrologyPlannerTest {
|
||||
settings.underground(),
|
||||
settings.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
settings.deepFluids()
|
||||
settings.deepFluids(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2150,7 +2150,7 @@ public class HydrologyPlannerTest {
|
||||
),
|
||||
settings.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
settings.deepFluids()
|
||||
settings.deepFluids(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2180,7 +2180,7 @@ public class HydrologyPlannerTest {
|
||||
),
|
||||
settings.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
settings.deepFluids()
|
||||
settings.deepFluids(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2209,7 +2209,7 @@ public class HydrologyPlannerTest {
|
||||
),
|
||||
settings.outlets(),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
settings.deepFluids()
|
||||
settings.deepFluids(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -2269,7 +2269,7 @@ public class HydrologyPlannerTest {
|
||||
settings.underground(),
|
||||
outlets,
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
settings.deepFluids()
|
||||
settings.deepFluids(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
+3
-3
@@ -231,7 +231,7 @@ public class HydrologyStyledGeometryTest {
|
||||
4
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
deepFluids
|
||||
deepFluids, List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -264,7 +264,7 @@ public class HydrologyStyledGeometryTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
}
|
||||
int height = 118 - Math.floorDiv(x, 12) + (int) StrictMath.round(StrictMath.sin(z / 18D) * 2D);
|
||||
@@ -296,7 +296,7 @@ public class HydrologyStyledGeometryTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -77,7 +77,7 @@ public final class HydrologyTerrainSampleTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
profiles
|
||||
profiles, List.of()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ public class HydrologyTileCacheTest {
|
||||
2
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of()
|
||||
List.of(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -297,7 +297,7 @@ public class HydrologyTileCacheTest {
|
||||
2
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of()
|
||||
List.of(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -338,7 +338,7 @@ public class HydrologyTileCacheTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("default")
|
||||
List.of("default"), List.of()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ public class ChannelProfileBuilderTest {
|
||||
"parent",
|
||||
"parent",
|
||||
"parent",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
return new ChannelProfileBuilder(HydrologyPlannerSettings.defaults().surface(), sampler, CONSTANT_GEOMETRY);
|
||||
}
|
||||
|
||||
+2
-2
@@ -1566,7 +1566,7 @@ public class MantleHydrologyComponentTest {
|
||||
8
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of()
|
||||
List.of(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1603,7 +1603,7 @@ public class MantleHydrologyComponentTest {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("default")
|
||||
List.of("default"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -215,7 +215,8 @@ public final class HydrologyPackProbe {
|
||||
enum CoverageFamily {
|
||||
SURFACE,
|
||||
UNDERGROUND,
|
||||
DEEP
|
||||
DEEP,
|
||||
POOL
|
||||
}
|
||||
|
||||
enum VerificationFamily {
|
||||
@@ -4080,6 +4081,7 @@ public final class HydrologyPackProbe {
|
||||
case SURFACE -> CoverageFamily.SURFACE;
|
||||
case UNDERGROUND -> CoverageFamily.UNDERGROUND;
|
||||
case DEEP_FLUID -> CoverageFamily.DEEP;
|
||||
case SURFACE_POOL -> CoverageFamily.POOL;
|
||||
};
|
||||
return new ConfiguredCoverage(family, course.profileKey());
|
||||
}
|
||||
|
||||
@@ -265,7 +265,7 @@ public final class HydrologyPlannerProbe {
|
||||
4
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of()
|
||||
List.of(), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -304,7 +304,7 @@ public final class HydrologyPlannerProbe {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -355,7 +355,7 @@ public final class HydrologyPlannerProbe {
|
||||
"shore",
|
||||
"bank",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -476,7 +476,7 @@ public final class HydrologyPlannerProbe {
|
||||
8
|
||||
),
|
||||
HydrologyPlannerSettings.Geometry.defaults(),
|
||||
List.of(deepFluid)
|
||||
List.of(deepFluid), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
@@ -521,7 +521,7 @@ public final class HydrologyPlannerProbe {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
};
|
||||
}
|
||||
@@ -554,7 +554,7 @@ public final class HydrologyPlannerProbe {
|
||||
"shore",
|
||||
"dry",
|
||||
"flooded",
|
||||
List.of("water")
|
||||
List.of("water"), List.of()
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -186,7 +186,7 @@ public final class RiverTransectProbe {
|
||||
writeTileDiagnostics(new File(configuration.output(), "tile-diagnostics.png"), tile, complex, runtime, seaLevel);
|
||||
List<CourseSummary> summaries = new ArrayList<>();
|
||||
for (RiverCourse course : tile.courses()) {
|
||||
if (course.type() != RiverCourseType.SURFACE) {
|
||||
if (course.type() != RiverCourseType.SURFACE && course.type() != RiverCourseType.SURFACE_POOL) {
|
||||
continue;
|
||||
}
|
||||
List<HydrologyPoint> exposedPath = exposedPath(course);
|
||||
|
||||
Reference in New Issue
Block a user