45#include <unordered_map>
51using Vec3 = CaveHydrologyVec3;
52using PassagePoint = CaveHydrologyPoint;
101struct QuantizedPoint {
107 bool operator==(
const QuantizedPoint& other)
const {
108 return x == other.x &&
y == other.y &&
z == other.z &&
group == other.group;
116 bool operator==(
const EdgeKey& other)
const {
117 return ax == other.ax &&
ay == other.ay &&
az == other.az &&
bx == other.bx &&
by == other.by &&
bz == other.bz;
121struct EdgeProjection {
129 size_t operator()(
const EdgeKey&
key)
const {
140float dot(Vec3
a, Vec3
b) {
return a.x *
b.x +
a.y *
b.y +
a.z *
b.z; }
141Vec3 sub(Vec3
a, Vec3
b) {
return {
a.x -
b.x,
a.y -
b.y,
a.z -
b.z}; }
142Vec3 add(Vec3
a, Vec3
b) {
return {
a.x +
b.x,
a.y +
b.y,
a.z +
b.z}; }
145EdgeKey makeEdgeKey(Vec3
a, Vec3
b) {
147 return QuantizedPoint{int(std::lround(
p.x * 1000000.f)), int(std::lround(
p.y * 1000000.f)),
148 int(std::lround(
p.z * 1000000.f)), 0};
151 const bool swap = qa.x > qb.x || (qa.x == qb.x && qa.y > qb.y) || (qa.x == qb.x && qa.y == qb.y && qa.z > qb.z);
152 if (swap) std::swap(qa, qb);
153 return {qa.x, qa.y, qa.z, qb.x, qb.y, qb.z};
156float smoothstep(
float edge0,
float edge1,
float value) {
157 const float t = std::clamp((
value - edge0) / (edge1 - edge0), 0.f, 1.f);
158 return t *
t * (3.f - 2.f *
t);
161float smoothMaximum(
float a,
float b,
float blendRadius) {
162 if (blendRadius <= 0.f)
return std::max(
a,
b);
163 const float h = std::clamp(0.5f + 0.5f * (
a -
b) / blendRadius, 0.f, 1.f);
164 return b + (
a -
b) *
h + blendRadius *
h * (1.f -
h);
168 const Vec3 segment = sub(
b,
a);
170 const float t =
length2 > 1e-8f ? std::clamp(
dot(sub(
point,
a), segment) / length2, 0.f, 1.f) : 0.f;
171 const Vec3 delta = sub(
point, add(
a, mul(segment,
t)));
172 return std::sqrt(
dot(delta, delta));
175float ellipsoidDistance(Vec3
point,
const Chamber&
shape) {
177 const float normalizedX =
q.x /
shape.radii.x;
178 const float normalizedY =
q.y /
shape.radii.y;
179 const float normalizedZ =
q.z /
shape.radii.z;
181 (std::sqrt(normalizedX * normalizedX + normalizedY * normalizedY + normalizedZ * normalizedZ) - 1.f) *
183 if (
shape.irregularity <= 0.f)
return base;
184 const float azimuth = std::atan2(normalizedZ, normalizedX);
185 const float lobes = std::sin(azimuth * 3.f +
shape.phase) * 0.58f +
186 std::sin(azimuth * 2.f - normalizedY * 2.4f +
shape.phase * 0.7f) * 0.42f;
187 return base - lobes *
shape.irregularity * std::min({
shape.radii.x,
shape.radii.y,
shape.radii.z}) * 0.13f;
190float taperedSegmentDistance(Vec3
point,
const Dripstone&
shape,
float horizontalX = 1.f,
float horizontalZ = 1.f) {
193 const float t =
length2 > 1e-8f ? std::clamp(
dot(sub(
point,
shape.start), axis) / length2, 0.f, 1.f) : 0.f;
195 delta.x *= horizontalX;
196 delta.z *= horizontalZ;
198 const float envelope = std::sin(
t * 3.1415926535f);
199 radius *= 1.f +
shape.profileAmplitude * envelope *
200 std::sin(
t *
shape.profileFrequency * 6.283185307f +
shape.profilePhase);
201 return std::sqrt(
dot(delta, delta)) -
radius;
204float flowstoneDistance(Vec3
point,
const Flowstone&
shape) {
208 const float ripple = std::sin((
v /
shape.halfHeight) *
shape.rippleFrequency +
shape.phase) *
shape.rippleAmplitude;
209 const float n =
dot(
q,
shape.normal) - ripple;
210 const float ellipsoid =
214 return ellipsoid * std::min({
shape.halfWidth,
shape.halfHeight,
shape.thickness});
217float curtainDistance(Vec3
point,
const Curtain&
shape) {
220 const float downward = -
q.y;
221 const float wave = std::sin((
u /
shape.halfWidth) *
shape.waveFrequency +
shape.phase) *
shape.waveAmplitude;
223 const float side = std::fabs(
u) -
shape.halfWidth;
224 const float vertical = std::max(-downward, downward -
shape.length);
225 const float sheet = std::fabs(
n) -
shape.thickness;
226 return std::max({
side, vertical, sheet});
231 value *= 0x7feb352du;
233 value *= 0x846ca68bu;
237float latticeNoise(
float x,
float y,
float z, uint32_t
seed) {
238 const int ix = int(std::floor(
x));
239 const int iy = int(std::floor(
y));
240 const int iz = int(std::floor(
z));
241 const float fx =
x - float(ix);
242 const float fy =
y - float(iy);
243 const float fz =
z - float(iz);
244 auto smooth = [](
float t) {
return t *
t * (3.f - 2.f *
t); };
246 uint32_t
h =
hash(uint32_t(
sx) * 73856093u ^ uint32_t(
sy) * 19349663u ^ uint32_t(
sz) * 83492791u ^
seed);
247 return float(
h & 0xffffu) / 32767.5f - 1.f;
249 const float ux = smooth(fx), uy = smooth(fy), uz = smooth(fz);
250 auto lerp = [](
float a,
float b,
float t) {
return a + (
b -
a) *
t; };
252 const float x10 =
lerp(
sample(ix, iy + 1, iz),
sample(ix + 1, iy + 1, iz), ux);
253 const float x01 =
lerp(
sample(ix, iy, iz + 1),
sample(ix + 1, iy, iz + 1), ux);
254 const float x11 =
lerp(
sample(ix, iy + 1, iz + 1),
sample(ix + 1, iy + 1, iz + 1), ux);
255 return lerp(
lerp(x00, x10, uy),
lerp(x01, x11, uy), uz);
258Vec3 projectToDensitySurface(Vec3 meshPoint,
const std::vector<float>& density,
int nx,
int ny,
int nz) {
259 const CaveFieldPoint
point =
264void addFacetedTriangle(MeshBuild&
mesh, Vec3
a, Vec3
b, Vec3
c, Vec3 referenceNormal) {
267 if (
dot(
normal, referenceNormal) < 0.f) {
273 const uint32_t base = uint32_t(
mesh.getVertexCount());
277 mesh.addTriangle(base, base + 1, base + 2);
280bool validStyle(
const std::string& style) {
281 return style ==
"cavern" || style ==
"tunnels" || style ==
"vertical" || style ==
"labyrinth" || style ==
"mixed";
284bool validGenesis(
const std::string& genesis) {
285 return genesis ==
"epigene" || genesis ==
"hypogene" || genesis ==
"mixed";
288bool generateCaveMesh(
const Params&
params, MeshBuild& out, std::string&
error) {
289 const std::string style =
params.getString(
"style",
"mixed");
290 const std::string genesis =
params.getString(
"genesis",
"epigene");
291 const int resolution =
params.getInt(
"resolution", 40);
292 const int nx =
params.getInt(
"nx", resolution);
293 const int ny =
params.getInt(
"ny", std::max(12, resolution * 3 / 5));
294 const int nz =
params.getInt(
"nz", resolution);
295 const int chamberCount =
params.getInt(
"chambers", 7);
296 const int branchCount =
params.getInt(
"branches", 4);
297 const float tunnelRadius =
params.getFloat(
"tunnelRadius", 0.16f);
298 const float chamberScale =
params.getFloat(
"chamberScale", 1.f);
299 const float chamberHierarchy =
params.getFloat(
"chamberHierarchy", 0.f);
300 const float passageVariation =
params.getFloat(
"passageVariation", 0.f);
301 const float chamberIrregularity =
params.getFloat(
"chamberIrregularity", 0.f);
303 const float multiscaleRoughness =
params.getFloat(
"multiscaleRoughness", 0.f);
304 const float roughnessFlowCoupling =
params.getFloat(
"roughnessFlowCoupling", 0.f);
305 const float erosion =
params.getFloat(
"erosion", 0.55f);
306 const float bedding =
params.getFloat(
"bedding", 0.6f);
307 const float fractureDissolution =
params.getFloat(
"fractureDissolution", 0.55f);
308 const float fractureApertureVariability =
params.getFloat(
"fractureApertureVariability", 0.f);
309 const float fractureStressControl =
params.getFloat(
"fractureStressControl", 0.f);
310 const float fractureFlowFeedback =
params.getFloat(
"fractureFlowFeedback", 0.f);
311 const float vadoseIncision =
params.getFloat(
"vadoseIncision", 0.35f);
312 const float waterTableCorrosion =
params.getFloat(
"waterTableCorrosion", 0.f);
313 const float waterTableLevel =
params.getFloat(
"waterTableLevel", 0.18f);
314 const int waterTableStages =
params.getInt(
"waterTableStages", 1);
315 const float waterTableDrop =
params.getFloat(
"waterTableDrop", 0.18f);
316 const float waterTableFluctuation =
params.getFloat(
"waterTableFluctuation", 0.35f);
317 const float scallopErosion =
params.getFloat(
"scallopErosion", 0.45f);
318 const float scallopScale =
params.getFloat(
"scallopScale", 0.12f);
319 const float scallopHydraulicScaling =
params.getFloat(
"scallopHydraulicScaling", 0.f);
320 const float scallopMaturity =
params.getFloat(
"scallopMaturity", 0.f);
321 const float scallopScaleVariability =
params.getFloat(
"scallopScaleVariability", 0.f);
322 const float scallopFlowSeparation =
params.getFloat(
"scallopFlowSeparation", 0.f);
323 const float scallopFlowHistory =
params.getFloat(
"scallopFlowHistory", 0.f);
324 const float bendUndercut =
params.getFloat(
"bendUndercut", 0.f);
325 const float fragmentDetachment =
params.getFloat(
"fragmentDetachment", 0.f);
326 const float curvatureDissolution =
params.getFloat(
"curvatureDissolution", 0.f);
327 const float reactiveSurfaceCoupling =
params.getFloat(
"reactiveSurfaceCoupling", 0.f);
328 const float surfaceSlopeReactivity =
params.getFloat(
"surfaceSlopeReactivity", 0.f);
329 const float reactivePatchiness =
params.getFloat(
"reactivePatchiness", 0.f);
330 const float hydraulicErosion =
params.getFloat(
"hydraulicErosion", 0.f);
331 const float mixingCorrosion =
params.getFloat(
"mixingCorrosion", 0.f);
332 const float lithologicHeterogeneity =
params.getFloat(
"lithologicHeterogeneity", 0.f);
333 const float floodAbrasion =
params.getFloat(
"floodAbrasion", 0.f);
334 const float sedimentLoad =
params.getFloat(
"sedimentLoad", 0.55f);
335 const float floodPlucking =
params.getFloat(
"floodPlucking", 0.f);
336 const float pluckingBlockScale =
params.getFloat(
"pluckingBlockScale", 0.1f);
337 const float constrictionScour =
params.getFloat(
"constrictionScour", 0.f);
338 const float knickpointErosion =
params.getFloat(
"knickpointErosion", 0.f);
339 const float streamBedKarren =
params.getFloat(
"streamBedKarren", 0.f);
340 const float eddyPotholes =
params.getFloat(
"eddyPotholes", 0.f);
341 const float potholeGravelSize =
params.getFloat(
"potholeGravelSize", 0.5f);
342 const float breakdownScour =
params.getFloat(
"breakdownScour", 0.f);
343 const float hydraulicGradient =
params.getFloat(
"hydraulicGradient", 0.35f);
344 const float recharge =
params.getFloat(
"recharge", 0.65f);
345 const float flowFocusing =
params.getFloat(
"flowFocusing", 0.7f);
346 const float damkohler =
params.getFloat(
"damkohler", 0.002f);
347 const float transportG =
params.getFloat(
"transportG", 1.f);
348 const float microstructure =
params.getFloat(
"microstructure", 0.f);
349 const float microporosityAccess =
params.getFloat(
"microporosityAccess", 0.55f);
350 const float permeabilityContrast =
params.getFloat(
"permeabilityContrast", 0.65f);
351 const int fractureCount =
params.getInt(
"fractureCount", 5);
352 const int cupolaCount =
params.getInt(
"cupolas", 6);
353 const int feederCount =
params.getInt(
"feeders", 4);
354 const int dripstoneCount =
params.getInt(
"dripstones", 12);
355 const float dripstoneScale =
params.getFloat(
"dripstoneScale", 0.7f);
356 const std::string stalagmiteShape =
params.getString(
"stalagmiteShape",
"mixed");
357 const float normalSmoothing =
params.getFloat(
"normalSmoothing", 0.78f);
358 const std::string surfaceNormalMode =
params.getString(
"surfaceNormalMode",
"faceAverage");
359 const int wetnessRefinement =
params.getInt(
"wetnessRefinement", 0);
360 const float boundaryClosure =
params.getFloat(
"boundaryClosure", 0.f);
361 const float condensationCorrosion =
params.getFloat(
"condensationCorrosion", 0.f);
362 const float biogenicCorrosion =
params.getFloat(
"biogenicCorrosion", 0.f);
363 const float mineralArmoring =
params.getFloat(
"mineralArmoring", 0.f);
364 const float condensationFaceting =
params.getFloat(
"condensationFaceting", 0.f);
365 const float differentialVeinErosion =
params.getFloat(
"differentialVeinErosion", 0.f);
366 const float breakdown =
params.getFloat(
"breakdown", 0.f);
367 const int breakdownEvents =
params.getInt(
"breakdownEvents", 4);
368 const float sedimentDeposition =
params.getFloat(
"sedimentDeposition", 0.f);
369 const float paragenesis =
params.getFloat(
"paragenesis", 0.f);
370 const int sedimentBars =
params.getInt(
"sedimentBars", 4);
371 const int flowstoneCount =
params.getInt(
"flowstones", 7);
372 const int curtainCount =
params.getInt(
"curtains", 5);
373 const float flowstoneScale =
params.getFloat(
"flowstoneScale", 0.75f);
374 const int surfaceRefinement =
params.getInt(
"surfaceRefinement", 0);
375 const int isosurfaceSampling =
params.getInt(
"isosurfaceSampling", 1);
376 const float refinementThreshold =
params.getFloat(
"refinementThreshold", 0.0015f);
377 const float width =
params.getFloat(
"width", 30.f);
379 const float depth =
params.getFloat(
"depth", 24.f);
380 if (!validStyle(style)) {
381 error =
"mesh.cave: unknown style '" + style +
"' (use cavern|tunnels|vertical|labyrinth|mixed)";
384 if (!validGenesis(genesis)) {
385 error =
"mesh.cave: unknown genesis '" + genesis +
"' (use epigene|hypogene|mixed)";
388 if (stalagmiteShape !=
"conical" && stalagmiteShape !=
"columnar" && stalagmiteShape !=
"flatTop" &&
389 stalagmiteShape !=
"mixed") {
390 error =
"mesh.cave: unknown stalagmiteShape '" + stalagmiteShape +
"' (use conical|columnar|flatTop|mixed)";
393 if (surfaceNormalMode !=
"faceAverage" && surfaceNormalMode !=
"densityGradient") {
394 error =
"mesh.cave: unknown surfaceNormalMode '" + surfaceNormalMode +
"' (use faceAverage|densityGradient)";
397 if (
nx < 8 ||
ny < 8 || nz < 8 || nx > 128 ||
ny > 128 ||
nz > 128) {
398 error =
"mesh.cave: each resolution axis must be in [8, 128]";
401 if (chamberCount < 1 || chamberCount > 64 || branchCount < 0 || branchCount > 32) {
402 error =
"mesh.cave: chambers must be in [1, 64] and branches in [0, 32]";
405 if (!(tunnelRadius >= 0.04f && tunnelRadius <= 0.4f) || chamberScale < 0.35f || chamberScale > 2.5f ||
406 chamberHierarchy < 0.f || chamberHierarchy > 1.f || passageVariation < 0.f || passageVariation > 1.f ||
407 chamberIrregularity < 0.f || chamberIrregularity > 1.f || roughness < 0.f || roughness > 0.45f ||
408 multiscaleRoughness < 0.f || multiscaleRoughness > 1.f || roughnessFlowCoupling < 0.f ||
409 roughnessFlowCoupling > 1.f || erosion < 0.f || erosion > 1.f || bedding < 0.f || bedding > 1.f ||
410 fractureDissolution < 0.f || fractureDissolution > 1.f || fractureApertureVariability < 0.f ||
411 fractureApertureVariability > 1.f || fractureStressControl < 0.f || fractureStressControl > 1.f ||
412 fractureFlowFeedback < 0.f || fractureFlowFeedback > 1.f || vadoseIncision < 0.f || vadoseIncision > 1.f ||
413 waterTableCorrosion < 0.f || waterTableCorrosion > 1.f || waterTableLevel < -0.8f || waterTableLevel > 0.8f ||
414 waterTableStages < 1 || waterTableStages > 4 || waterTableDrop < 0.05f || waterTableDrop > 0.5f ||
415 waterTableFluctuation < 0.f || waterTableFluctuation > 1.f || scallopErosion < 0.f || scallopErosion > 1.f ||
416 scallopScale < 0.04f || scallopScale > 0.32f || scallopHydraulicScaling < 0.f ||
417 scallopHydraulicScaling > 1.f || scallopMaturity < 0.f || scallopMaturity > 1.f ||
418 scallopScaleVariability < 0.f || scallopScaleVariability > 1.f || scallopFlowSeparation < 0.f ||
419 scallopFlowSeparation > 1.f || scallopFlowHistory < 0.f || scallopFlowHistory > 1.f || bendUndercut < 0.f ||
420 bendUndercut > 1.f || fragmentDetachment < 0.f || fragmentDetachment > 1.f || curvatureDissolution < 0.f ||
421 curvatureDissolution > 1.f || reactiveSurfaceCoupling < 0.f || reactiveSurfaceCoupling > 1.f ||
422 surfaceSlopeReactivity < 0.f || surfaceSlopeReactivity > 1.f || reactivePatchiness < 0.f ||
423 reactivePatchiness > 1.f || hydraulicErosion < 0.f || hydraulicErosion > 1.f || mixingCorrosion < 0.f ||
424 mixingCorrosion > 1.f || lithologicHeterogeneity < 0.f || lithologicHeterogeneity > 1.f ||
425 floodAbrasion < 0.f || floodAbrasion > 1.f || sedimentLoad < 0.f || sedimentLoad > 1.f || floodPlucking < 0.f ||
426 floodPlucking > 1.f || pluckingBlockScale < 0.04f || pluckingBlockScale > 0.2f || constrictionScour < 0.f ||
427 constrictionScour > 1.f || knickpointErosion < 0.f || knickpointErosion > 1.f || streamBedKarren < 0.f ||
428 streamBedKarren > 1.f || eddyPotholes < 0.f || eddyPotholes > 1.f || potholeGravelSize < 0.f ||
429 potholeGravelSize > 1.f || breakdownScour < 0.f || breakdownScour > 1.f || hydraulicGradient < 0.01f ||
430 hydraulicGradient > 2.f || recharge < 0.f || recharge > 1.f || flowFocusing < 0.f || flowFocusing > 1.f ||
431 damkohler < 0.00005f || damkohler > 0.05f || transportG < 0.1f || transportG > 5.f || cupolaCount < 0 ||
432 cupolaCount > 32 || microstructure < 0.f || microstructure > 1.f || microporosityAccess < 0.f ||
433 microporosityAccess > 1.f || permeabilityContrast < 0.f || permeabilityContrast > 1.f || feederCount < 0 ||
434 feederCount > 24 || fractureCount < 0 || fractureCount > 24 || dripstoneCount < 0 || dripstoneCount > 64 ||
435 dripstoneScale < 0.25f || dripstoneScale > 1.5f || normalSmoothing < 0.f || normalSmoothing > 1.f ||
436 flowstoneCount < 0 || flowstoneCount > 32 || curtainCount < 0 || curtainCount > 32 || flowstoneScale < 0.25f ||
437 flowstoneScale > 1.5f || surfaceRefinement < 0 || surfaceRefinement > 2 || isosurfaceSampling < 1 ||
438 isosurfaceSampling > 2 || wetnessRefinement < 0 || wetnessRefinement > 1 || boundaryClosure < 0.f ||
439 boundaryClosure > 1.f || condensationCorrosion < 0.f || condensationCorrosion > 1.f ||
440 biogenicCorrosion < 0.f || biogenicCorrosion > 1.f || mineralArmoring < 0.f || mineralArmoring > 1.f ||
441 condensationFaceting < 0.f || condensationFaceting > 1.f || differentialVeinErosion < 0.f ||
442 differentialVeinErosion > 1.f || breakdown < 0.f || breakdown > 1.f || breakdownEvents < 0 ||
443 breakdownEvents > 16 || refinementThreshold < 0.0001f || refinementThreshold > 0.02f ||
444 sedimentDeposition < 0.f || sedimentDeposition > 1.f || paragenesis < 0.f || paragenesis > 1.f ||
445 sedimentBars < 0 || sedimentBars > 16 ||
width <= 0.f ||
height <= 0.f ||
depth <= 0.f) {
446 error =
"mesh.cave: invalid radius, chamber scale, roughness, or world dimensions";
450 std::mt19937 rng(
params.getSeed());
451 std::uniform_real_distribution<float>
unit(-1.f, 1.f);
452 std::uniform_real_distribution<float> positive(0.f, 1.f);
453 std::vector<PassagePoint> spine;
454 const int spinePoints = std::max(8, chamberCount * 2);
455 spine.reserve(
size_t(spinePoints));
456 for (
int i = 0; i < spinePoints; ++i) {
457 const float t = float(i) / float(spinePoints - 1);
458 float y =
unit(rng) * 0.16f;
459 float z = std::sin(
t * 9.f +
unit(rng)) * 0.22f +
unit(rng) * 0.1f;
460 if (style ==
"vertical")
y = (
t - 0.5f) * 1.25f +
unit(rng) * 0.12f;
461 if (style ==
"labyrinth")
z += std::sin(
t * 22.f) * 0.24f;
462 const float coherentRadius = 1.f + passageVariation * (std::sin(
t * 4.f * 3.1415926535f + 0.7f) * 0.24f +
463 std::sin(
t * 9.f * 3.1415926535f + 1.9f) * 0.10f);
464 const float randomRadius = 0.78f + positive(rng) * 0.55f;
465 spine.push_back({{-0.72f +
t * 1.44f, std::clamp(
y, -0.72f, 0.72f), std::clamp(
z, -0.68f, 0.68f)},
466 tunnelRadius * randomRadius * std::clamp(coherentRadius, 0.68f, 1.38f)});
469 std::vector<CaveHydrologyBranch> branches;
470 branches.reserve(
size_t(branchCount));
471 for (
int branch = 0; branch < branchCount; ++branch) {
472 const int anchor = 1 + int(rng() % uint32_t(std::max(1, spinePoints - 2)));
473 const int points = 3 + int(rng() % 4u);
474 std::vector<PassagePoint>
path;
479 (
unit(rng) < 0.f ? -1.f : 1.f) * (0.15f + positive(rng) * 0.2f)};
488 path.push_back({
cursor, tunnelRadius * (0.58f + positive(rng) * 0.45f)});
490 branches.push_back({
anchor, std::move(
path)});
493 const CaveHydrologyWeights hydrology =
buildCaveHydrology(spine, branches, hydraulicErosion, hydraulicGradient,
494 recharge, flowFocusing, damkohler, transportG);
496 const std::vector<CaveConstrictionScourSite> constrictionScourSites =
498 const std::vector<CaveKnickpointSite> knickpointSites =
501 std::vector<Chamber> chambers;
502 chambers.reserve(
size_t(chamberCount));
503 const int primaryChamber = chamberCount / 2;
504 float primaryChamberVerticalRadius = 0.f;
505 for (
int i = 0; i < chamberCount; ++i) {
506 const float evenT = float(i) / float(std::max(1, chamberCount - 1));
507 const float jitter = chamberHierarchy > 0.f && i > 0 && i + 1 < chamberCount
508 ?
unit(rng) * chamberHierarchy * 0.32f / float(std::max(1, chamberCount - 1))
510 const float anchoredT =
511 i == primaryChamber && chamberHierarchy > 0.f ? 0.52f : std::clamp(evenT + jitter, 0.f, 1.f);
512 const PassagePoint&
anchor = spine[size_t(std::lround(anchoredT *
float(spinePoints - 1)))];
513 const float styleScale = style ==
"cavern" ? 1.35f : (style ==
"tunnels" ? 0.72f : 1.f);
514 const bool primary = i == primaryChamber;
515 const float hierarchyScale =
516 chamberHierarchy <= 0.f
518 : (primary ? 1.f + 0.95f * chamberHierarchy : 1.f - chamberHierarchy * (0.12f + positive(rng) * 0.20f));
519 const float radius = tunnelRadius * chamberScale * styleScale * (1.5f + positive(rng) * 1.25f) * hierarchyScale;
520 const float verticalScale = primary ? 1.f + chamberHierarchy * 0.58f : 1.f;
521 chambers.push_back({add(
anchor.position, {unit(rng) * 0.08f, unit(rng) * 0.08f, unit(rng) * 0.08f}),
522 {std::min(0.82f,
radius * (1.1f + positive(rng) * 0.8f)),
523 std::min(0.84f,
radius * (0.75f + positive(rng) * 0.65f) * verticalScale),
524 std::min(0.82f,
radius * (1.0f + positive(rng) * 0.9f))},
525 chamberIrregularity * (primary ? 1.f : 0.65f),
526 positive(rng) * 6.283185307f});
527 if (primary) primaryChamberVerticalRadius = chambers.back().radii.y;
529 std::vector<CaveBreakdownChamber> breakdownChambers;
530 breakdownChambers.reserve(chambers.size());
531 for (
const Chamber& chamber : chambers)
532 breakdownChambers.push_back(
533 {chamber.center.x, chamber.center.y, chamber.center.z, chamber.radii.x, chamber.radii.y, chamber.radii.z});
534 const CaveBreakdownSet breakdownSet =
536 const std::vector<CaveObstacleScourSite> obstacleScourSites =
538 std::vector<CaveSedimentPathPoint> sedimentPath;
539 sedimentPath.reserve(spine.size());
540 for (
const PassagePoint&
point : spine)
542 const CaveSedimentSet sedimentSet =
548 std::vector<CaveFracture> fractures;
549 fractures.reserve(
size_t(fractureCount));
550 for (
int i = 0; i < fractureCount; ++i) {
551 const float angle = positive(rng) * 3.1415926535f;
552 fractures.push_back({std::cos(
angle), std::sin(
angle),
unit(rng) * 0.58f, 0.012f + positive(rng) * 0.025f});
554 const std::vector<CavePotholeSite> potholeSites =
555 createCavePotholeSites(spine, hydrology.trunk, fractures, fractureApertureVariability, fractureStressControl,
556 sedimentLoad, potholeGravelSize,
params.getSeed());
558 std::vector<Chamber> cupolas;
559 std::vector<RisingConduit> feeders;
560 std::vector<RisingConduit> ceilingChannels;
561 if (genesis !=
"epigene") {
562 cupolas.reserve(
size_t(cupolaCount));
563 feeders.reserve(
size_t(feederCount));
564 ceilingChannels.reserve(
size_t(feederCount));
565 for (
int i = 0; i < cupolaCount; ++i) {
566 const Chamber&
host = chambers[size_t(rng() % uint32_t(chambers.size()))];
567 const float rx = 0.045f + positive(rng) * 0.065f;
568 const float rz = 0.045f + positive(rng) * 0.065f;
569 const float ry = 0.08f + positive(rng) * 0.15f;
571 {{
host.center.x +
unit(rng) *
host.radii.x * 0.45f,
host.center.y +
host.radii.y * 0.82f + ry * 0.45f,
575 for (
int i = 0; i < feederCount; ++i) {
576 const Chamber&
host = chambers[size_t(rng() % uint32_t(chambers.size()))];
579 const Vec3 bottom{
top.x +
unit(rng) * 0.06f, std::max(-0.96f,
top.y - (0.20f + positive(rng) * 0.35f)),
581 const float radius = 0.035f + positive(rng) * 0.035f;
583 if (!cupolas.empty()) {
584 const Chamber& outlet = cupolas[size_t(i) % cupolas.size()];
586 ceilingChannels.push_back({ceilingStart, outlet.center,
radius * 0.62f});
594 std::vector<Dripstone> dripstones;
595 dripstones.reserve(
size_t(dripstoneCount) * 2u);
597 for (
int i = 0; i < dripstoneCount; ++i) {
598 const Chamber& chamber = chambers[size_t(rng() % uint32_t(chambers.size()))];
599 const float dripAngle = positive(rng) * 2.f * 3.1415926535f;
600 const float dripRadius = 0.34f + positive(rng) * 0.30f;
601 const float dx = std::cos(dripAngle) * chamber.radii.x * dripRadius;
602 const float dz = std::sin(dripAngle) * chamber.radii.z * dripRadius;
603 const float radial = std::min(
604 0.86f, (
dx *
dx) / (chamber.radii.x * chamber.radii.x) + (
dz *
dz) / (chamber.radii.z * chamber.radii.z));
605 const float yExtent = chamber.radii.y * std::sqrt(1.f - radial);
606 const Vec3 ceiling{chamber.center.x +
dx, chamber.center.y + yExtent, chamber.center.z +
dz};
607 const Vec3 floor{ceiling.x, chamber.center.y - yExtent, ceiling.z};
608 const float gap = std::max(0.08f, ceiling.y - floor.y);
609 const bool column = positive(rng) < 0.12f * dripstoneScale;
610 const float ceilingLength =
gap * (
column ? 0.54f : (0.18f + positive(rng) * 0.28f)) * dripstoneScale;
611 const float floorLength =
gap * (
column ? 0.54f : (0.14f + positive(rng) * 0.23f)) * dripstoneScale;
612 const float ceilingRadius = std::clamp(ceilingLength * (0.14f + positive(rng) * 0.08f), 0.025f, 0.075f);
613 dripstones.push_back({add(ceiling, {0.f, 0.018f, 0.f}), add(ceiling, {0.f, -ceilingLength, 0.f}), ceilingRadius,
614 column ? ceilingRadius * 0.72f : ceilingRadius * 0.13f});
616 std::string
shape = stalagmiteShape;
617 if (
shape ==
"mixed") {
618 const float selector = positive(rng);
619 shape = selector < 0.42f ?
"conical" : (selector < 0.78f ?
"columnar" :
"flatTop");
621 const float floorRadius = std::clamp(floorLength * (
shape ==
"conical" ? 0.38f
622 :
shape ==
"columnar" ? 0.28f
625 const float tipRatio =
column ? 0.72f : (
shape ==
"conical" ? 0.12f :
shape ==
"columnar" ? 0.68f : 0.88f);
626 dripstones.push_back({add(floor, {0.f, -0.018f, 0.f}), add(floor, {0.f, floorLength, 0.f}), floorRadius,
627 floorRadius * tipRatio});
628 if (
column) ++columnCount;
631 std::vector<Flowstone> flowstones;
632 flowstones.reserve(
size_t(flowstoneCount));
633 for (
int i = 0; i < flowstoneCount; ++i) {
634 const Chamber& chamber = chambers[size_t(rng() % uint32_t(chambers.size()))];
635 const float angle = positive(rng) * 2.f * 3.1415926535f;
640 const Vec3 wall{chamber.center.x +
normal.x * chamber.radii.x * 0.98f,
641 chamber.center.y +
unit(rng) * chamber.radii.y * 0.22f,
642 chamber.center.z +
normal.z * chamber.radii.z * 0.98f};
643 flowstones.push_back({wall,
normal,
tangent, (0.055f + positive(rng) * 0.065f) * flowstoneScale,
644 (0.09f + positive(rng) * 0.13f) * flowstoneScale,
645 (0.026f + positive(rng) * 0.018f) * flowstoneScale,
646 (0.005f + positive(rng) * 0.009f) * flowstoneScale, 4.f + positive(rng) * 4.f,
647 positive(rng) * 2.f * 3.1415926535f});
650 std::vector<Curtain> curtains;
651 curtains.reserve(
size_t(curtainCount));
652 for (
int i = 0; i < curtainCount; ++i) {
653 const Chamber& chamber = chambers[size_t(rng() % uint32_t(chambers.size()))];
654 const float angle = positive(rng) * 2.f * 3.1415926535f;
655 const float radial = 0.38f + positive(rng) * 0.32f;
656 const float dx = std::cos(
angle) * chamber.radii.x * radial;
657 const float dz = std::sin(
angle) * chamber.radii.z * radial;
658 const float radial2 = std::min(
659 0.9f, (
dx *
dx) / (chamber.radii.x * chamber.radii.x) + (
dz *
dz) / (chamber.radii.z * chamber.radii.z));
660 const Vec3 anchor{chamber.center.x +
dx, chamber.center.y + chamber.radii.y * std::sqrt(1.f - radial2) + 0.018f,
661 chamber.center.z +
dz};
664 curtains.push_back({
anchor,
normal,
tangent, (0.045f + positive(rng) * 0.07f) * flowstoneScale,
665 (0.10f + positive(rng) * 0.18f) * flowstoneScale,
666 (0.024f + positive(rng) * 0.016f) * flowstoneScale,
667 (0.008f + positive(rng) * 0.014f) * flowstoneScale, 2.5f + positive(rng) * 2.5f,
668 positive(rng) * 2.f * 3.1415926535f});
671 std::vector<float> density(
size_t(
nx) *
size_t(
ny) *
size_t(
nz), 1.f);
672 std::vector<float> reactiveRate(density.size(), 1.f);
673 std::vector<float> hydraulicExposure(density.size(), 1.f);
674 std::vector<CaveHydrologyVec3> flowDirection(density.size(), {1.f, 0.f, 0.f});
675 int biogenicAffectedVoxels = 0;
676 float minimumScallopRetention = 1.f;
677 float maximumBiogenicErosion = 0.f;
678 float totalBiogenicErosion = 0.f;
679 int facetAffectedVoxels = 0;
680 float maximumFacetRetreat = 0.f;
681 int differentialVeinAffectedVoxels = 0;
682 float maximumDifferentialVeinRetreat = 0.f;
683 float maximumVeinProtection = 0.f;
684 float minimumFractureAperture = 1.f;
685 float maximumFractureAperture = 1.f;
686 float minimumFractureBranchOpenness = 1.f;
687 int fractureChannelAffectedVoxels = 0;
688 float maximumFractureChannelRetreat = 0.f;
689 float maximumFractureFlowConcentration = 0.f;
690 float maximumFractureIntersectionAmplification = 0.f;
691 float minimumFractureReactantAccess = 1.4f;
692 float minimumWallRelief = 1.f;
693 float maximumWallRelief = -1.f;
694 int roughnessTransferAffectedVoxels = 0;
695 float minimumRoughnessTransfer = 1.f;
696 float maximumRoughnessTransfer = 1.f;
697 float maximumRidgeExposure = 0.f;
698 float maximumRecessShelter = 0.f;
699 int waterTableAffectedVoxels = 0;
700 float maximumWaterTableRetreat = 0.f;
701 int mixingCorrosionAffectedVoxels = 0;
702 float maximumMixingCorrosionRetreat = 0.f;
703 int lithologyAffectedVoxels = 0;
704 int styloliteAffectedVoxels = 0;
705 float minimumBedResistance = 1.f;
706 float maximumLithologyRetreat = 0.f;
707 int abrasionAffectedVoxels = 0;
708 float maximumAbrasionRetreat = 0.f;
709 float maximumAbrasionVortex = 0.f;
710 int pluckingAffectedVoxels = 0;
711 float maximumPluckingRetreat = 0.f;
712 float maximumPluckingPredisposition = 0.f;
713 int constrictionScourAffectedVoxels = 0;
714 float maximumConstrictionScourRetreat = 0.f;
715 float maximumConstrictionRatio = 0.f;
716 float maximumPlungingEfficiency = 0.f;
717 int knickpointAffectedVoxels = 0;
718 float maximumKnickpointRetreat = 0.f;
719 float maximumKnickpointSlopeBreak = 0.f;
720 float maximumKnickpointDrop = 0.f;
721 int streamBedKarrenAffectedVoxels = 0;
722 float maximumStreamBedKarrenRetreat = 0.f;
723 float maximumKarrenFractureGuidance = 0.f;
724 float maximumKarrenIntersectionPocket = 0.f;
725 int potholeAffectedVoxels = 0;
726 float maximumPotholeRetreat = 0.f;
727 float maximumPotholeSecondaryErosion = 0.f;
728 float maximumPotholeFractureIntersection = 0.f;
729 int obstacleScourAffectedVoxels = 0;
730 float maximumObstacleScourRetreat = 0.f;
731 float maximumHorseshoeScour = 0.f;
732 float maximumWakeScour = 0.f;
733 float minimumObstacleRoughnessRetention = 1.f;
734 int mineralArmoringAffectedVoxels = 0;
735 float maximumMineralCoatingCoverage = 0.f;
736 float maximumMineralHydraulicRetention = 0.f;
737 float minimumArmoredDissolutionRetention = 1.f;
738 int scallopHistoryAffectedVoxels = 0;
739 float maximumYoungerScallopErosion = 0.f;
740 float maximumYoungerScallopCoverage = 0.f;
741 float maximumScallopReversalMask = 0.f;
742 float minimumSecondaryScallopScaleRatio = 1.f;
743 auto carvePath = [](
Vec3 p,
const std::vector<PassagePoint>&
path,
float current) {
744 for (
size_t i = 1; i <
path.size(); ++i) {
745 const float radius = 0.5f * (
path[i - 1].radius +
path[i].radius);
750 for (
int z = 0;
z <
nz; ++
z) {
751 for (
int y = 0;
y <
ny; ++
y) {
752 for (
int x = 0;
x <
nx; ++
x) {
753 const Vec3 p{float(
x) / float(
nx - 1) * 2.f - 1.f, float(
y) / float(
ny - 1) * 2.f - 1.f,
754 float(
z) / float(
nz - 1) * 2.f - 1.f};
755 float d = carvePath(
p, spine, 1.f);
756 for (
const auto& branch : branches)
d = carvePath(
p, branch.
points,
d);
758 for (
size_t branch = 0; branch < branches.size(); ++branch) {
759 const CavePassageFrame candidate =
761 if (candidate.distance < passage.distance) passage = candidate;
766 const float effectiveVadose = genesis ==
"hypogene" ? 0.f : vadoseIncision;
767 if (effectiveVadose > 0.f) {
768 const Vec3 incisedPoint{
p.x,
p.y + tunnelRadius * (0.55f + 0.55f * vadoseIncision),
p.z};
769 float incision = 1.f;
770 for (
size_t i = 1; i < spine.size(); ++i) {
771 const float radius = 0.5f * (spine[i - 1].radius + spine[i].radius) *
772 (0.38f + 0.22f * vadoseIncision) * std::sqrt(hydrology.trunk[i - 1]);
777 for (
size_t branch = 0; branch < branches.size(); ++branch) {
778 const auto&
path = branches[branch].points;
779 for (
size_t i = 1; i <
path.size(); ++i) {
780 const float radius = 0.5f * (
path[i - 1].radius +
path[i].radius) *
781 (0.30f + 0.18f * vadoseIncision) *
782 std::sqrt(hydrology.branches[branch][i - 1]);
788 d = std::min(
d, incision);
790 for (
const Chamber& chamber : chambers) {
791 d = std::min(
d, ellipsoidDistance(
p, chamber));
793 for (
const Chamber& cupola : cupolas)
d =
std::
min(
d, ellipsoidDistance(
p, cupola));
794 for (
const RisingConduit& feeder : feeders)
796 for (
const RisingConduit& channel : ceilingChannels)
798 const float legacyStrata =
799 latticeNoise((
p.x + 2.f) * 5.5f, (
p.y + 3.f) * 3.2f, (
p.z + 5.f) * 5.5f,
params.getSeed());
800 float strata = legacyStrata;
801 if (multiscaleRoughness > 0.f) {
803 strata = legacyStrata * (1.f - multiscaleRoughness) + spectrum.
relief * multiscaleRoughness;
805 minimumWallRelief = std::min(minimumWallRelief, strata);
806 maximumWallRelief = std::max(maximumWallRelief, strata);
814 const float shell = 1.f - smoothstep(0.025f, 0.22f, std::fabs(
d));
815 const float beddingWarp =
816 latticeNoise(
p.x * 2.3f,
p.y * 1.1f,
p.z * 2.3f,
params.getSeed() ^ 0x51a7u) * 0.12f;
817 const float beddingWave = std::fabs(std::sin((
p.y + beddingWarp) * 8.f * 3.1415926535f));
818 const float beddingMask = 1.f - smoothstep(0.05f, 0.42f, beddingWave);
819 float fractureMask = 0.f;
820 float secondaryFractureMask = 0.f;
821 float fractureAperture = 1.f;
822 float secondaryFractureAperture = 1.f;
823 for (
const CaveFracture& fracture : fractures) {
824 const CaveFractureSample
sample =
827 if (
sample.mask > fractureMask) {
828 secondaryFractureMask = fractureMask;
829 secondaryFractureAperture = fractureAperture;
831 fractureAperture =
sample.apertureMultiplier;
833 if (
sample.mask > secondaryFractureMask) {
834 secondaryFractureMask =
sample.mask;
835 secondaryFractureAperture =
sample.apertureMultiplier;
838 minimumFractureAperture = std::min(minimumFractureAperture,
sample.apertureMultiplier);
839 maximumFractureAperture = std::max(maximumFractureAperture,
sample.apertureMultiplier);
840 minimumFractureBranchOpenness = std::min(minimumFractureBranchOpenness,
sample.branchOpenness);
842 const float pockets =
843 std::max(0.f, latticeNoise(
p.x * 15.f,
p.y * 11.f,
p.z * 15.f,
params.getSeed() ^ 0xb5297a4du));
847 {{passage.along, passage.angle, passage.distance, passage.radius, passage.hydraulicIntensity,
848 scallopScale, scallopHydraulicScaling, scallopMaturity, bendUndercut, passage.bendStrength,
849 passage.outerBankAngle, scallopScaleVariability, scallopFlowSeparation,
params.getSeed()},
850 scallopFlowHistory});
851 if (scallops.youngerErosion > 1e-6f) {
852 ++scallopHistoryAffectedVoxels;
853 maximumYoungerScallopErosion = std::max(maximumYoungerScallopErosion, scallops.youngerErosion);
854 maximumYoungerScallopCoverage = std::max(maximumYoungerScallopCoverage, scallops.youngerCoverage);
855 maximumScallopReversalMask = std::max(maximumScallopReversalMask, scallops.reversalMask);
856 minimumSecondaryScallopScaleRatio =
857 std::min(minimumSecondaryScallopScaleRatio, scallops.secondaryScaleRatio);
859 const CaveBiogenicCorrosionSample biogenic =
861 passage.hydraulicIntensity, biogenicCorrosion,
params.getSeed()});
862 const CaveFacetSample facets =
864 condensationFaceting,
params.getSeed()});
866 {
p.x,
p.y,
p.z, passage.distance, passage.radius, differentialVeinErosion,
params.getSeed()});
867 const float mineralSupply = genesis ==
"hypogene" ? 1.f : (genesis ==
"mixed" ? 0.72f : 0.28f);
868 CaveMineralArmoringSample armoring;
869 const bool chemicalArmoringPotential =
870 erosion > 0.f || biogenicCorrosion > 0.f || waterTableCorrosion > 0.f || mixingCorrosion > 0.f ||
871 lithologicHeterogeneity > 0.f || condensationFaceting > 0.f || differentialVeinErosion > 0.f;
872 if (mineralArmoring > 0.f && chemicalArmoringPotential) {
874 {passage.along, passage.angle, passage.hydraulicIntensity, mineralSupply,
params.getSeed()});
876 const float chemicalRetention = 1.f - mineralArmoring * (1.f - armoring.dissolutionRetention);
877 CaveRoughnessTransferSample roughnessTransfer;
878 if (roughnessFlowCoupling > 0.f && multiscaleRoughness > 0.f &&
roughness > 0.f &&
879 chemicalArmoringPotential) {
884 if (shell > 1e-4f && std::fabs(chemicalMassTransfer - 1.f) > 1e-6f) {
885 ++roughnessTransferAffectedVoxels;
886 minimumRoughnessTransfer = std::min(minimumRoughnessTransfer, chemicalMassTransfer);
887 maximumRoughnessTransfer = std::max(maximumRoughnessTransfer, chemicalMassTransfer);
888 maximumRidgeExposure = std::max(maximumRidgeExposure, roughnessTransfer.ridgeExposure);
889 maximumRecessShelter = std::max(maximumRecessShelter, roughnessTransfer.recessShelter);
891 CaveWaterTableSample waterTable;
892 if (waterTableCorrosion > 0.f && genesis !=
"hypogene") {
894 waterTableDrop, waterTableFluctuation, waterTableStages,
897 CaveMixingCorrosionSample mixing;
899 const CaveLithologySample lithology =
901 lithologicHeterogeneity,
params.getSeed()});
902 CaveAbrasionSample abrasion;
903 if (floodAbrasion > 0.f && genesis !=
"hypogene") {
905 passage.bendStrength, passage.outerBankAngle, sedimentLoad,
908 CavePluckingSample plucking;
909 if (floodPlucking > 0.f && genesis !=
"hypogene" && fractures.size() >= 2) {
912 secondaryFractureMask, pluckingBlockScale,
params.getSeed()});
914 CaveConstrictionScourSample constriction;
915 if (constrictionScour > 0.f && genesis !=
"hypogene") {
918 CaveKnickpointSample knickpoint;
919 if (knickpointErosion > 0.f && genesis !=
"hypogene") {
922 CaveKarrenSample karren;
923 if (streamBedKarren > 0.f && genesis !=
"hypogene" && fractures.size() >= 2) {
925 {passage.angle, passage.hydraulicIntensity, fractureMask, secondaryFractureMask});
927 CavePotholeSample pothole;
928 if (eddyPotholes > 0.f && genesis !=
"hypogene") {
931 CaveObstacleScourSample obstacleScour;
932 if (breakdownScour > 0.f && genesis !=
"hypogene") {
935 CaveFractureChannelizationSample fractureChannel;
936 if (fractureFlowFeedback > 0.f && fractureApertureVariability > 0.f) {
938 {fractureMask, secondaryFractureMask, fractureAperture, secondaryFractureAperture,
939 passage.hydraulicIntensity, passage.along, hydrology.reactantPenetration});
941 if (facets.retreat > 1e-6f) {
942 ++facetAffectedVoxels;
943 maximumFacetRetreat = std::max(maximumFacetRetreat, facets.retreat);
945 if (biogenic.erosion > 1e-6f) {
946 ++biogenicAffectedVoxels;
947 minimumScallopRetention = std::min(minimumScallopRetention, biogenic.fluvialScallopRetention);
948 maximumBiogenicErosion = std::max(maximumBiogenicErosion, biogenic.erosion);
949 totalBiogenicErosion += biogenic.erosion;
951 if (differentialVeins.hostRetreat > 1e-6f) {
952 ++differentialVeinAffectedVoxels;
953 maximumDifferentialVeinRetreat =
954 std::max(maximumDifferentialVeinRetreat, differentialVeins.hostRetreat);
955 maximumVeinProtection = std::max(maximumVeinProtection, differentialVeins.veinProtection);
957 const float dissolution = bedding * beddingMask * 0.055f + fractureDissolution * fractureMask * 0.075f +
959 scallopErosion * scallops.erosion * biogenic.fluvialScallopRetention *
960 0.045f * (genesis ==
"hypogene" ? 0.35f : 1.f);
962 p.x,
p.y,
p.z,
params.getSeed(), microstructure, microporosityAccess, permeabilityContrast);
966 const float flowWeightedDissolution =
967 dissolution * rock.reactiveSurface *
968 std::clamp(passage.hydraulicIntensity * rock.permeability, 0.2f, 1.7f);
969 const float biogenicDissolution = biogenic.erosion * rock.reactiveSurface * 0.052f;
970 d -= shell * chemicalRetention * chemicalMassTransfer *
971 (erosion * flowWeightedDissolution + biogenicDissolution);
972 if (shell > 1e-4f && chemicalRetention < 0.999999f) {
973 ++mineralArmoringAffectedVoxels;
974 maximumMineralCoatingCoverage = std::max(maximumMineralCoatingCoverage, armoring.coatingCoverage);
975 maximumMineralHydraulicRetention =
976 std::max(maximumMineralHydraulicRetention, armoring.hydraulicRetention);
977 minimumArmoredDissolutionRetention =
978 std::min(minimumArmoredDissolutionRetention, chemicalRetention);
980 const float fractureChannelRetreat = shell * chemicalMassTransfer * erosion * fractureDissolution *
981 fractureFlowFeedback * fractureChannel.erosion * 0.062f;
982 d -= fractureChannelRetreat;
983 if (fractureChannelRetreat > 1e-6f) {
984 ++fractureChannelAffectedVoxels;
985 maximumFractureChannelRetreat = std::max(maximumFractureChannelRetreat, fractureChannelRetreat);
986 maximumFractureFlowConcentration =
987 std::max(maximumFractureFlowConcentration, fractureChannel.flowConcentration);
988 maximumFractureIntersectionAmplification =
989 std::max(maximumFractureIntersectionAmplification, fractureChannel.intersectionAmplification);
990 minimumFractureReactantAccess =
991 std::min(minimumFractureReactantAccess, fractureChannel.reactantAccess);
993 const float waterTableRetreat = shell * chemicalRetention * chemicalMassTransfer * waterTableCorrosion *
994 waterTable.erosion * 0.065f;
995 d -= waterTableRetreat;
996 if (waterTableRetreat > 1e-6f) {
997 ++waterTableAffectedVoxels;
998 maximumWaterTableRetreat = std::max(maximumWaterTableRetreat, waterTableRetreat);
1000 const float mixingRetreat =
1001 shell * chemicalRetention * chemicalMassTransfer * mixingCorrosion * mixing.erosion * 0.07f;
1003 if (mixingRetreat > 1e-6f) {
1004 ++mixingCorrosionAffectedVoxels;
1005 maximumMixingCorrosionRetreat = std::max(maximumMixingCorrosionRetreat, mixingRetreat);
1007 const float lithologyRetreat =
1008 shell * chemicalRetention * chemicalMassTransfer * bedding * lithology.retreat * 0.052f;
1009 d -= lithologyRetreat;
1010 if (lithologyRetreat > 1e-6f) {
1011 ++lithologyAffectedVoxels;
1012 if (lithology.styloliteMask > 0.05f) ++styloliteAffectedVoxels;
1013 minimumBedResistance = std::min(minimumBedResistance, lithology.bedResistance);
1014 maximumLithologyRetreat = std::max(maximumLithologyRetreat, lithologyRetreat);
1016 const float abrasionRetreat = shell * floodAbrasion * abrasion.erosion * 0.058f;
1017 d -= abrasionRetreat;
1018 if (abrasionRetreat > 1e-6f) {
1019 ++abrasionAffectedVoxels;
1020 maximumAbrasionRetreat = std::max(maximumAbrasionRetreat, abrasionRetreat);
1021 maximumAbrasionVortex = std::max(maximumAbrasionVortex, abrasion.vortexMask);
1023 const float pluckingRetreat = shell * floodPlucking * plucking.erosion * 0.082f;
1024 d -= pluckingRetreat;
1025 if (pluckingRetreat > 1e-6f) {
1026 ++pluckingAffectedVoxels;
1027 maximumPluckingRetreat = std::max(maximumPluckingRetreat, pluckingRetreat);
1028 maximumPluckingPredisposition =
1029 std::max(maximumPluckingPredisposition, plucking.fracturePredisposition);
1031 const float constrictionRetreat = shell * constrictionScour * constriction.erosion * 0.115f;
1032 d -= constrictionRetreat;
1033 if (constrictionRetreat > 1e-6f) {
1034 ++constrictionScourAffectedVoxels;
1035 maximumConstrictionScourRetreat = std::max(maximumConstrictionScourRetreat, constrictionRetreat);
1036 maximumConstrictionRatio = std::max(maximumConstrictionRatio, constriction.constrictionRatio);
1037 maximumPlungingEfficiency = std::max(maximumPlungingEfficiency, constriction.plungingEfficiency);
1039 const float knickpointRetreat = shell * knickpointErosion * knickpoint.erosion * 0.12f;
1040 d -= knickpointRetreat;
1041 if (knickpointRetreat > 1e-6f) {
1042 ++knickpointAffectedVoxels;
1043 maximumKnickpointRetreat = std::max(maximumKnickpointRetreat, knickpointRetreat);
1044 maximumKnickpointSlopeBreak = std::max(maximumKnickpointSlopeBreak, knickpoint.slopeBreak);
1045 maximumKnickpointDrop =
1046 std::max(maximumKnickpointDrop, knickpointSites[
size_t(knickpoint.siteIndex)].drop);
1048 const float karrenRetreat = shell * streamBedKarren * karren.erosion * 0.052f;
1050 if (karrenRetreat > 1e-6f) {
1051 ++streamBedKarrenAffectedVoxels;
1052 maximumStreamBedKarrenRetreat = std::max(maximumStreamBedKarrenRetreat, karrenRetreat);
1053 maximumKarrenFractureGuidance = std::max(maximumKarrenFractureGuidance, karren.fractureGuidance);
1054 maximumKarrenIntersectionPocket =
1055 std::max(maximumKarrenIntersectionPocket, karren.intersectionPocket);
1057 const float potholeRetreat = shell * eddyPotholes * pothole.erosion * 0.09f;
1058 d -= potholeRetreat;
1059 if (potholeRetreat > 1e-6f) {
1060 ++potholeAffectedVoxels;
1061 maximumPotholeRetreat = std::max(maximumPotholeRetreat, potholeRetreat);
1062 maximumPotholeSecondaryErosion = std::max(maximumPotholeSecondaryErosion, pothole.secondaryPothole);
1063 maximumPotholeFractureIntersection =
1064 std::max(maximumPotholeFractureIntersection,
1065 potholeSites[
size_t(pothole.siteIndex)].fractureIntersection);
1067 const float obstacleScourRetreat = shell * breakdownScour * obstacleScour.erosion * 0.082f;
1068 d -= obstacleScourRetreat;
1069 if (obstacleScourRetreat > 1e-6f) {
1070 ++obstacleScourAffectedVoxels;
1071 maximumObstacleScourRetreat = std::max(maximumObstacleScourRetreat, obstacleScourRetreat);
1072 maximumHorseshoeScour = std::max(maximumHorseshoeScour, obstacleScour.horseshoeScour);
1073 maximumWakeScour = std::max(maximumWakeScour, obstacleScour.wakeScour);
1074 minimumObstacleRoughnessRetention =
1075 std::min(minimumObstacleRoughnessRetention, obstacleScour.roughnessRetention);
1077 d -= shell * chemicalRetention * chemicalMassTransfer * facets.retreat;
1078 d -= shell * chemicalRetention * chemicalMassTransfer * differentialVeins.hostRetreat;
1079 const size_t voxel = size_t(
x) + size_t(
y) * size_t(
nx) + size_t(
z) * size_t(
nx) * size_t(
ny);
1081 const float exposedRate =
1082 rock.reactiveSurface * std::clamp(passage.hydraulicIntensity * rock.permeability, 0.2f, 1.7f);
1083 reactiveRate[voxel] = std::clamp(1.f + reactiveSurfaceCoupling * (exposedRate - 1.f), 0.25f, 2.5f);
1084 hydraulicExposure[voxel] = passage.hydraulicIntensity;
1085 flowDirection[voxel] = passage.tangent;
1092 const CaveSurfaceReactivityResult surfaceReactivity =
1099 density, reactiveRate, flowDirection,
nx,
ny,
nz, reactivePatchiness,
params.getSeed());
1103 const CaveCondensationResult condensation =
1108 const CaveSurfaceEvolutionResult surfaceEvolution =
1114 const float voxelSize = 2.f / float(std::min({
nx - 1,
ny - 1,
nz - 1}));
1115 const float embedDepth = voxelSize * 1.15f;
1116 const float minimumFeatureRadius = voxelSize * 0.82f;
1117 int rejectedDripstonePairs = 0;
1118 int anchoredDripstonePairs = 0;
1119 std::vector<Dripstone> anchoredDripstones;
1120 anchoredDripstones.reserve(dripstones.size());
1121 for (
size_t i = 0; i + 1 < dripstones.size(); i += 2) {
1122 const Dripstone& ceilingCandidate = dripstones[i];
1123 const Dripstone& floorCandidate = dripstones[i + 1];
1124 const float preferredY = (ceilingCandidate.start.y + floorCandidate.start.y) * 0.5f;
1128 ++rejectedDripstonePairs;
1131 const float gap = span->ceiling.position.y - span->floor.position.y;
1132 bool column = ceilingCandidate.endRadius > ceilingCandidate.startRadius * 0.5f &&
1133 floorCandidate.endRadius > floorCandidate.startRadius * 0.5f;
1135 const float clearanceRadius = std::max(ceilingCandidate.startRadius, floorCandidate.startRadius) * 1.18f;
1136 for (
int sampleIndex = 1; sampleIndex < 12 &&
column; ++sampleIndex) {
1137 const float sampleY = span->floor.position.y +
gap * float(sampleIndex) / 12.f;
1138 for (
int angleIndex = 0; angleIndex < 8; ++angleIndex) {
1139 const float angle = float(angleIndex) * 0.7853981634f;
1140 const CaveFieldPoint clearancePoint{ceilingCandidate.start.x + std::cos(
angle) * clearanceRadius,
1142 ceilingCandidate.start.z + std::sin(
angle) * clearanceRadius};
1150 const float maximumLength =
gap * (
column ? 0.515f : 0.42f);
1151 const float ceilingLength =
1153 : std::min(std::abs(ceilingCandidate.end.y - ceilingCandidate.start.y), maximumLength);
1154 const float floorLength =
1155 column ? maximumLength : std::min(std::abs(floorCandidate.end.y - floorCandidate.start.y), maximumLength);
1156 if (ceilingLength < embedDepth * 1.5f || floorLength < embedDepth * 1.5f) {
1157 ++rejectedDripstonePairs;
1160 const Vec3 ceiling{span->ceiling.position.x, span->ceiling.position.y, span->ceiling.position.z};
1161 const Vec3 floor{span->floor.position.x, span->floor.position.y, span->floor.position.z};
1166 anchoredDripstones.push_back({add(ceiling, {0.f, embedDepth, 0.f}), add(floor, {0.f, -embedDepth, 0.f}),
1167 std::max(ceilingCandidate.startRadius, minimumFeatureRadius),
1168 std::max(floorCandidate.startRadius, minimumFeatureRadius)});
1169 anchoredDripstones.back().profileAmplitude = 0.055f;
1170 anchoredDripstones.back().profileFrequency = 2.5f;
1171 anchoredDripstones.back().profilePhase =
1172 std::fmod(std::abs(ceiling.x * 19.7f + ceiling.z * 31.1f), 6.283185307f);
1173 ++anchoredDripstonePairs;
1176 anchoredDripstones.push_back({add(ceiling, {0.f, embedDepth, 0.f}), add(ceiling, {0.f, -ceilingLength, 0.f}),
1177 std::max(ceilingCandidate.startRadius, minimumFeatureRadius),
1178 std::max(ceilingCandidate.endRadius, minimumFeatureRadius * 0.24f)});
1179 anchoredDripstones.back().profileAmplitude = 0.08f;
1180 anchoredDripstones.back().profileFrequency = 1.5f;
1181 anchoredDripstones.back().profilePhase =
1182 std::fmod(std::abs(ceiling.x * 17.3f + ceiling.z * 23.9f), 6.283185307f);
1183 anchoredDripstones.push_back({add(floor, {0.f, -embedDepth, 0.f}), add(floor, {0.f, floorLength, 0.f}),
1184 std::max(floorCandidate.startRadius, minimumFeatureRadius),
1185 std::max(floorCandidate.endRadius, minimumFeatureRadius * 0.30f)});
1186 anchoredDripstones.back().profileAmplitude = 0.11f;
1187 anchoredDripstones.back().profileFrequency = 2.f;
1188 anchoredDripstones.back().profilePhase = std::fmod(std::abs(floor.x * 29.3f + floor.z * 13.7f), 6.283185307f);
1189 ++anchoredDripstonePairs;
1191 dripstones = std::move(anchoredDripstones);
1193 int rejectedFlowstones = 0;
1194 std::vector<Flowstone> anchoredFlowstones;
1195 anchoredFlowstones.reserve(flowstones.size());
1196 for (Flowstone flowstone : flowstones) {
1198 density,
nx,
ny,
nz, {flowstone.center.x, flowstone.center.y, flowstone.center.z}, 0.24f);
1199 if (!
anchor || std::abs(
anchor->rockNormal.y) > 0.82f) {
1200 ++rejectedFlowstones;
1203 flowstone.normal = {
anchor->rockNormal.x,
anchor->rockNormal.y,
anchor->rockNormal.z};
1204 Vec3 tangent{-flowstone.normal.z, 0.f, flowstone.normal.x};
1206 if (tangentLength < 1e-4f) {
1207 ++rejectedFlowstones;
1210 flowstone.tangent = mul(
tangent, 1.f / tangentLength);
1212 add({
anchor->position.x,
anchor->position.y,
anchor->position.z}, mul(flowstone.normal, embedDepth));
1213 flowstone.thickness = std::max(flowstone.thickness, minimumFeatureRadius);
1214 anchoredFlowstones.push_back(flowstone);
1216 flowstones = std::move(anchoredFlowstones);
1218 int rejectedCurtains = 0;
1219 std::vector<Curtain> anchoredCurtains;
1220 anchoredCurtains.reserve(curtains.size());
1221 for (Curtain curtain : curtains) {
1224 if (!span || span->ceiling.rockNormal.y < 0.35f) {
1228 curtain.anchor = {span->ceiling.position.x, span->ceiling.position.y + embedDepth, span->ceiling.position.z};
1229 curtain.length = std::min(curtain.length, (span->ceiling.position.y - span->floor.position.y) * 0.36f);
1230 curtain.thickness = std::max(curtain.thickness, minimumFeatureRadius);
1231 if (curtain.length < embedDepth * 2.f) {
1235 anchoredCurtains.push_back(curtain);
1237 curtains = std::move(anchoredCurtains);
1238 std::vector<float> depositDelta(density.size(), 0.f);
1239 for (
int z = 0;
z <
nz; ++
z) {
1240 for (
int y = 0;
y <
ny; ++
y) {
1241 for (
int x = 0;
x <
nx; ++
x) {
1242 const Vec3 p{float(
x) / float(
nx - 1) * 2.f - 1.f, float(
y) / float(
ny - 1) * 2.f - 1.f,
1243 float(
z) / float(
nz - 1) * 2.f - 1.f};
1244 float&
d = density[size_t(
x) + size_t(
y) * size_t(
nx) + size_t(
z) * size_t(
nx) * size_t(
ny)];
1245 const float densityBeforeDeposits =
d;
1246 for (
const Dripstone& dripstone : dripstones)
1248 for (
const Flowstone& flowstone : flowstones)
1249 d = smoothMaximum(
d, -flowstoneDistance(
p, flowstone), 0.008f);
1250 for (
const Curtain& curtain : curtains)
d = smoothMaximum(
d, -curtainDistance(
p, curtain), 0.006f);
1251 depositDelta[size_t(
x) + size_t(
y) * size_t(
nx) + size_t(
z) * size_t(
nx) * size_t(
ny)] =
1252 std::max(0.f,
d - densityBeforeDeposits);
1260 const CaveBoundaryClosure
boundary =
1263 CaveResampledField resampled;
1264 const std::vector<float>* extractionDensity = &density;
1265 int extractionNx =
nx, extractionNy =
ny, extractionNz =
nz;
1266 if (isosurfaceSampling > 1) {
1268 extractionDensity = &resampled.density;
1269 extractionNx = resampled.nx;
1270 extractionNy = resampled.ny;
1271 extractionNz = resampled.nz;
1273 if (!
marchingCubes(extractionDensity->data(), extractionNx, extractionNy, extractionNz, 0.f, out, &
error) ||
1275 if (
error.empty())
error =
"mesh.cave: generated an empty cave";
1278 std::vector<int> caveWallGroups(
size_t(out.getIndexCount() / 3), 0);
1279 const float groupTolerance = 1.5f / float(std::min({extractionNx - 1, extractionNy - 1, extractionNz - 1}));
1282 for (
int corner = 0; corner < 3; ++corner) {
1283 const int index = out.indices()[size_t(
triangle * 3 + corner)];
1290 const bool createdByDeposition =
1292 if (createdByDeposition) {
1293 for (
const Dripstone& dripstone : dripstones) {
1296 caveWallGroups[size_t(
triangle)] = 1;
1300 if (caveWallGroups[
size_t(
triangle)] == 0) {
1301 for (
const Flowstone& flowstone : flowstones) {
1302 if (std::fabs(flowstoneDistance(
center, flowstone)) <= groupTolerance) {
1303 caveWallGroups[size_t(
triangle)] = 1;
1308 if (caveWallGroups[
size_t(
triangle)] == 0) {
1309 for (
const Curtain& curtain : curtains) {
1310 if (std::fabs(curtainDistance(
center, curtain)) <= groupTolerance) {
1311 caveWallGroups[size_t(
triangle)] = 1;
1318 caveWallGroups[size_t(
triangle)] = 3;
1320 caveWallGroups[size_t(
triangle)] = 4;
1322 auto groupResult = out.restoreGroupData({
"caveWalls",
"speleothems",
"wetWalls",
"breakdown",
"sediment"},
1323 std::move(caveWallGroups), 0);
1325 const auto* diagnostic = groupResult.error();
1326 error =
"mesh.cave: failed to assign cave wall group";
1327 if (diagnostic)
error +=
": " + diagnostic->message();
1330 int refinedSourceTriangles = 0;
1331 int adaptiveSplitEdges = 0;
1332 if (surfaceRefinement > 0) {
1334 refined.reserve(out.getVertexCount() * 4, out.getIndexCount() * 4);
1335 std::unordered_map<EdgeKey, EdgeProjection, EdgeKeyHash> edgeProjections;
1336 edgeProjections.reserve(
size_t(out.getIndexCount()));
1337 auto edgeProjection = [&](
Vec3 a,
Vec3 b) -> EdgeProjection {
1338 const EdgeKey
key = makeEdgeKey(
a,
b);
1339 auto found = edgeProjections.find(
key);
1340 if (
found != edgeProjections.end())
return found->second;
1341 const Vec3 midpoint = mul(add(
a,
b), 0.5f);
1343 projectToDensitySurface(midpoint, *extractionDensity, extractionNx, extractionNy, extractionNz);
1346 const float edgeLength = std::sqrt(
dot(sub(
b,
a), sub(
b,
a)));
1347 const float maximumProjection = edgeLength * (surfaceRefinement == 1 ? 0.35f : 0.10f);
1348 if (errorLength > maximumProjection && errorLength > 1e-8f) {
1349 error = mul(
error, maximumProjection / errorLength);
1352 const bool split = surfaceRefinement == 1 || std::sqrt(
dot(
error,
error)) >= refinementThreshold;
1353 if (
split) ++adaptiveSplitEdges;
1358 refined.setActiveGroup(out.getGroupName(
group));
1360 for (
int corner = 0; corner < 3; ++corner) {
1362 corners[corner] = {out.getPositionX(
index), out.getPositionY(
index), out.getPositionZ(
index)};
1364 const EdgeProjection e01 = edgeProjection(corners[0], corners[1]);
1365 const EdgeProjection e12 = edgeProjection(corners[1], corners[2]);
1366 const EdgeProjection e20 = edgeProjection(corners[2], corners[0]);
1367 const Vec3 ab = sub(corners[1], corners[0]);
1368 const Vec3 ac = sub(corners[2], corners[0]);
1370 if (surfaceRefinement == 1) {
1371 ++refinedSourceTriangles;
1372 addFacetedTriangle(refined, corners[0], e01.projected, e20.projected, referenceNormal);
1373 addFacetedTriangle(refined, e01.projected, corners[1], e12.projected, referenceNormal);
1374 addFacetedTriangle(refined, e20.projected, e12.projected, corners[2], referenceNormal);
1375 addFacetedTriangle(refined, e01.projected, e12.projected, e20.projected, referenceNormal);
1378 const int splitMask = (e01.split ? 1 : 0) | (e12.split ? 2 : 0) | (e20.
split ? 4 : 0);
1379 if (splitMask != 0) ++refinedSourceTriangles;
1380 auto emit = [&](
Vec3 a,
Vec3 b,
Vec3 c) { addFacetedTriangle(refined,
a,
b,
c, referenceNormal); };
1381 switch (splitMask) {
1382 case 0: emit(corners[0], corners[1], corners[2]);
break;
1384 emit(corners[0], e01.projected, corners[2]);
1385 emit(e01.projected, corners[1], corners[2]);
1388 emit(corners[1], e12.projected, corners[0]);
1389 emit(e12.projected, corners[2], corners[0]);
1392 emit(corners[1], e12.projected, e01.projected);
1393 emit(corners[0], e01.projected, corners[2]);
1394 emit(e01.projected, e12.projected, corners[2]);
1397 emit(corners[2], e20.projected, corners[1]);
1398 emit(e20.projected, corners[0], corners[1]);
1401 emit(corners[0], e01.projected, e20.projected);
1402 emit(corners[2], e20.projected, corners[1]);
1403 emit(e20.projected, e01.projected, corners[1]);
1406 emit(corners[2], e20.projected, e12.projected);
1407 emit(corners[1], e12.projected, corners[0]);
1408 emit(e12.projected, e20.projected, corners[0]);
1411 emit(corners[0], e01.projected, e20.projected);
1412 emit(e01.projected, corners[1], e12.projected);
1413 emit(e20.projected, e12.projected, corners[2]);
1414 emit(e01.projected, e12.projected, e20.projected);
1418 out = std::move(refined);
1420 const CaveWetnessRefinement
wetness =
1432 for (
size_t i = 0; i <
positions.size(); i += 3) {
1437 out.setMeta(
"algorithm",
"mesh.cave");
1438 out.setMeta(
"style", style);
1439 out.setMeta(
"genesis", genesis);
1440 out.setMeta(
"seed", std::to_string(
params.getSeed()));
1441 out.setMeta(
"chambers", std::to_string(chamberCount));
1442 out.setMeta(
"branches", std::to_string(branchCount));
1443 out.setMeta(
"chamberHierarchy", std::to_string(chamberHierarchy));
1444 out.setMeta(
"passageVariation", std::to_string(passageVariation));
1445 out.setMeta(
"chamberIrregularity", std::to_string(chamberIrregularity));
1446 out.setMeta(
"macroMorphologyModel",
1447 chamberHierarchy > 0.f ?
"hierarchical-primary-hall-v2" :
"uniform-chambers-v1");
1448 out.setMeta(
"primaryChamberVerticalRadius", std::to_string(primaryChamberVerticalRadius));
1449 out.setMeta(
"cupolas", std::to_string(cupolas.size()));
1450 out.setMeta(
"feeders", std::to_string(feeders.size()));
1451 out.setMeta(
"risingFlowModel", genesis ==
"epigene" ?
"none" :
"feeder-half-tube-cupola-v1");
1454 biogenicCorrosion > 0.f ?
"karst-biogenic-aero-overprint-v10"
1455 : scallopMaturity > 0.f ?
"karst-reactive-coarsening-v9"
1456 : bendUndercut > 0.f ?
"karst-reactive-curvature-v8"
1457 : microstructure > 0.f && scallopHydraulicScaling > 0.f
1458 ?
"karst-reactive-microstructure-scallop-v7"
1459 : (microstructure > 0.f ?
"karst-reactive-microstructure-v6"
1460 : (scallopHydraulicScaling > 0.f
1461 ?
"karst-hydraulic-scallop-v7"
1462 : (hydraulicErosion > 0.f ?
"karst-reactive-network-da-v5"
1463 :
"karst-fracture-bedding-vadose-scallop-v2"))));
1464 out.setMeta(
"erosion", std::to_string(erosion));
1465 out.setMeta(
"waterTableCorrosion", std::to_string(waterTableCorrosion));
1466 out.setMeta(
"waterTableCorrosionModel", waterTableCorrosion <= 0.f ?
"disabled"
1467 : genesis ==
"hypogene" ?
"inactive-hypogene"
1468 :
"descending-epiphreatic-belts-v1");
1469 out.setMeta(
"waterTableLevel", std::to_string(waterTableLevel));
1470 out.setMeta(
"waterTableStages", std::to_string(waterTableStages));
1471 out.setMeta(
"waterTableDrop", std::to_string(waterTableDrop));
1472 out.setMeta(
"waterTableAffectedVoxels", std::to_string(waterTableAffectedVoxels));
1473 out.setMeta(
"maximumWaterTableRetreat", std::to_string(maximumWaterTableRetreat));
1474 out.setMeta(
"mixingCorrosion", std::to_string(mixingCorrosion));
1475 out.setMeta(
"mixingCorrosionModel", mixingCorrosion <= 0.f ?
"disabled"
1476 : mixingSites.empty() ?
"inactive-no-confluence"
1477 :
"chemistry-weighted-confluence-v1");
1478 out.setMeta(
"mixingCorrosionSites", std::to_string(mixingSites.size()));
1479 out.setMeta(
"mixingCorrosionAffectedVoxels", std::to_string(mixingCorrosionAffectedVoxels));
1480 out.setMeta(
"maximumMixingCorrosionRetreat", std::to_string(maximumMixingCorrosionRetreat));
1481 out.setMeta(
"lithologicHeterogeneity", std::to_string(lithologicHeterogeneity));
1482 out.setMeta(
"lithologyErosionModel",
1483 lithologicHeterogeneity > 0.f && bedding > 0.f ?
"flow-accessible-stylolite-beds-v1" :
"disabled");
1484 out.setMeta(
"lithologyAffectedVoxels", std::to_string(lithologyAffectedVoxels));
1485 out.setMeta(
"styloliteAffectedVoxels", std::to_string(styloliteAffectedVoxels));
1486 out.setMeta(
"minimumBedResistance", std::to_string(minimumBedResistance));
1487 out.setMeta(
"maximumLithologyRetreat", std::to_string(maximumLithologyRetreat));
1488 out.setMeta(
"floodAbrasion", std::to_string(floodAbrasion));
1489 out.setMeta(
"sedimentLoad", std::to_string(sedimentLoad));
1490 out.setMeta(
"floodAbrasionModel", floodAbrasion <= 0.f ?
"disabled"
1491 : genesis ==
"hypogene" ?
"inactive-hypogene"
1492 : sedimentLoad <= 0.f ?
"inactive-no-tools"
1493 :
"near-bed-tools-cover-vortex-v1");
1494 out.setMeta(
"abrasionAffectedVoxels", std::to_string(abrasionAffectedVoxels));
1495 out.setMeta(
"maximumAbrasionRetreat", std::to_string(maximumAbrasionRetreat));
1496 out.setMeta(
"maximumAbrasionVortex", std::to_string(maximumAbrasionVortex));
1497 out.setMeta(
"floodPlucking", std::to_string(floodPlucking));
1498 out.setMeta(
"pluckingBlockScale", std::to_string(pluckingBlockScale));
1499 out.setMeta(
"floodPluckingModel", floodPlucking <= 0.f ?
"disabled"
1500 : genesis ==
"hypogene" ?
"inactive-hypogene"
1501 : fractures.
size() < 2 ?
"inactive-no-fracture-network"
1502 :
"thresholded-fracture-block-release-v1");
1503 out.setMeta(
"pluckingAffectedVoxels", std::to_string(pluckingAffectedVoxels));
1504 out.setMeta(
"maximumPluckingRetreat", std::to_string(maximumPluckingRetreat));
1505 out.setMeta(
"maximumPluckingPredisposition", std::to_string(maximumPluckingPredisposition));
1506 out.setMeta(
"constrictionScour", std::to_string(constrictionScour));
1507 out.setMeta(
"constrictionScourModel", constrictionScour <= 0.f ?
"disabled"
1508 : genesis ==
"hypogene" ?
"inactive-hypogene"
1509 : constrictionScourSites.empty() ?
"inactive-no-constriction"
1510 :
"discharge-optimal-plunging-flow-cpw-v2");
1511 out.setMeta(
"constrictionScourSites", std::to_string(constrictionScourSites.size()));
1512 out.setMeta(
"constrictionScourAffectedVoxels", std::to_string(constrictionScourAffectedVoxels));
1513 out.setMeta(
"maximumConstrictionScourRetreat", std::to_string(maximumConstrictionScourRetreat));
1514 out.setMeta(
"maximumConstrictionRatio", std::to_string(maximumConstrictionRatio));
1515 out.setMeta(
"maximumPlungingEfficiency", std::to_string(maximumPlungingEfficiency));
1516 out.setMeta(
"knickpointErosion", std::to_string(knickpointErosion));
1517 out.setMeta(
"knickpointErosionModel", knickpointErosion <= 0.f ?
"disabled"
1518 : genesis ==
"hypogene" ?
"inactive-hypogene"
1519 : sedimentLoad <= 0.f ?
"inactive-no-tools"
1520 : knickpointSites.empty() ?
"inactive-no-slope-break"
1521 :
"sediment-driven-headward-plunge-pool-v1");
1522 out.setMeta(
"knickpointSites", std::to_string(knickpointSites.size()));
1523 out.setMeta(
"knickpointAffectedVoxels", std::to_string(knickpointAffectedVoxels));
1524 out.setMeta(
"maximumKnickpointRetreat", std::to_string(maximumKnickpointRetreat));
1525 out.setMeta(
"maximumKnickpointSlopeBreak", std::to_string(maximumKnickpointSlopeBreak));
1526 out.setMeta(
"maximumKnickpointDrop", std::to_string(maximumKnickpointDrop));
1527 out.setMeta(
"streamBedKarren", std::to_string(streamBedKarren));
1528 out.setMeta(
"streamBedKarrenModel", streamBedKarren <= 0.f ?
"disabled"
1529 : genesis ==
"hypogene" ?
"inactive-hypogene"
1530 : fractures.
size() < 2 ?
"inactive-no-crossing-fractures"
1531 :
"lidar-constrained-fracture-guided-bed-karren-v1");
1532 out.setMeta(
"streamBedKarrenAffectedVoxels", std::to_string(streamBedKarrenAffectedVoxels));
1533 out.setMeta(
"maximumStreamBedKarrenRetreat", std::to_string(maximumStreamBedKarrenRetreat));
1534 out.setMeta(
"maximumKarrenFractureGuidance", std::to_string(maximumKarrenFractureGuidance));
1535 out.setMeta(
"maximumKarrenIntersectionPocket", std::to_string(maximumKarrenIntersectionPocket));
1536 out.setMeta(
"eddyPotholes", std::to_string(eddyPotholes));
1537 out.setMeta(
"potholeGravelSize", std::to_string(potholeGravelSize));
1538 out.setMeta(
"eddyPotholeModel", eddyPotholes <= 0.f ?
"disabled"
1539 : genesis ==
"hypogene" ?
"inactive-hypogene"
1540 : sedimentLoad <= 0.f ?
"inactive-no-tools"
1541 : fractures.
size() < 2 ?
"inactive-no-crossing-fractures"
1542 : potholeSites.empty() ?
"inactive-no-fracture-seeded-vortex"
1543 :
"gravel-size-dependent-compound-eddy-pothole-v1");
1544 out.setMeta(
"eddyPotholeSites", std::to_string(potholeSites.size()));
1545 out.setMeta(
"eddyPotholeAffectedVoxels", std::to_string(potholeAffectedVoxels));
1546 out.setMeta(
"maximumPotholeRetreat", std::to_string(maximumPotholeRetreat));
1547 out.setMeta(
"maximumPotholeSecondaryErosion", std::to_string(maximumPotholeSecondaryErosion));
1548 out.setMeta(
"maximumPotholeFractureIntersection", std::to_string(maximumPotholeFractureIntersection));
1549 out.setMeta(
"breakdownScour", std::to_string(breakdownScour));
1550 out.setMeta(
"breakdownScourModel", breakdownScour <= 0.f ?
"disabled"
1551 : genesis ==
"hypogene" ?
"inactive-hypogene"
1552 : sedimentLoad <= 0.f ?
"inactive-no-tools"
1553 : breakdownSet.blockCount <= 0 ?
"inactive-no-breakdown-blocks"
1554 : obstacleScourSites.empty() ?
"inactive-no-stream-obstacle"
1555 :
"roughness-damped-horseshoe-wake-scour-v1");
1556 out.setMeta(
"breakdownScourSites", std::to_string(obstacleScourSites.size()));
1557 out.setMeta(
"breakdownScourAffectedVoxels", std::to_string(obstacleScourAffectedVoxels));
1558 out.setMeta(
"maximumBreakdownScourRetreat", std::to_string(maximumObstacleScourRetreat));
1559 out.setMeta(
"maximumHorseshoeScour", std::to_string(maximumHorseshoeScour));
1560 out.setMeta(
"maximumWakeScour", std::to_string(maximumWakeScour));
1561 out.setMeta(
"minimumObstacleRoughnessRetention", std::to_string(minimumObstacleRoughnessRetention));
1562 out.setMeta(
"mineralArmoring", std::to_string(mineralArmoring));
1563 out.setMeta(
"mineralArmoringModel", mineralArmoring <= 0.f ?
"disabled"
1564 : erosion <= 0.f && biogenicCorrosion <= 0.f && waterTableCorrosion <= 0.f &&
1565 mixingCorrosion <= 0.f && lithologicHeterogeneity <= 0.f &&
1566 condensationFaceting <= 0.f && differentialVeinErosion <= 0.f
1567 ?
"inactive-no-chemical-retreat"
1568 :
"genesis-supplied-hydraulic-stripping-shield-v1");
1569 out.setMeta(
"mineralArmoringAffectedVoxels", std::to_string(mineralArmoringAffectedVoxels));
1570 out.setMeta(
"maximumMineralCoatingCoverage", std::to_string(maximumMineralCoatingCoverage));
1571 out.setMeta(
"maximumMineralHydraulicRetention", std::to_string(maximumMineralHydraulicRetention));
1572 out.setMeta(
"minimumArmoredDissolutionRetention",
1573 std::to_string(mineralArmoringAffectedVoxels > 0 ? minimumArmoredDissolutionRetention : 1.f));
1574 out.setMeta(
"multiscaleRoughness", std::to_string(multiscaleRoughness));
1575 out.setMeta(
"wallRoughnessSpectrum",
1576 multiscaleRoughness > 0.f ?
"band-limited-three-scale-v1" :
"legacy-single-band");
1577 out.setMeta(
"minimumWallRelief", std::to_string(minimumWallRelief));
1578 out.setMeta(
"maximumWallRelief", std::to_string(maximumWallRelief));
1579 out.setMeta(
"roughnessFlowCoupling", std::to_string(roughnessFlowCoupling));
1580 out.setMeta(
"roughnessMassTransferModel",
1581 roughnessFlowCoupling <= 0.f ?
"disabled"
1582 : multiscaleRoughness <= 0.f ||
roughness <= 0.f ?
"inactive-no-resolved-relief"
1583 : erosion <= 0.f && biogenicCorrosion <= 0.f && waterTableCorrosion <= 0.f && mixingCorrosion <= 0.f &&
1584 lithologicHeterogeneity <= 0.f && condensationFaceting <= 0.f && differentialVeinErosion <= 0.f
1585 ?
"inactive-no-chemical-retreat"
1586 :
"ridge-exposure-recess-shelter-v1");
1587 out.setMeta(
"roughnessTransferAffectedVoxels", std::to_string(roughnessTransferAffectedVoxels));
1588 out.setMeta(
"minimumRoughnessTransferMultiplier", std::to_string(minimumRoughnessTransfer));
1589 out.setMeta(
"maximumRoughnessTransferMultiplier", std::to_string(maximumRoughnessTransfer));
1590 out.setMeta(
"maximumRidgeExposure", std::to_string(maximumRidgeExposure));
1591 out.setMeta(
"maximumRecessShelter", std::to_string(maximumRecessShelter));
1592 out.setMeta(
"fractureApertureVariability", std::to_string(fractureApertureVariability));
1593 out.setMeta(
"fractureApertureDistribution",
1594 fractureApertureVariability > 0.f ?
"correlated-lognormal-proxy-v1" :
"uniform");
1595 out.setMeta(
"fractureStressControl", std::to_string(fractureStressControl));
1596 out.setMeta(
"fractureDissolutionFront",
1597 fractureStressControl > 0.f ?
"stress-split-branching-v1" :
"uniform-plane");
1598 out.setMeta(
"fractureApertureGeometricStdDev", std::to_string(std::exp(0.55f * fractureApertureVariability)));
1599 out.setMeta(
"minimumFractureApertureMultiplier", std::to_string(minimumFractureAperture));
1600 out.setMeta(
"maximumFractureApertureMultiplier", std::to_string(maximumFractureAperture));
1601 out.setMeta(
"minimumFractureBranchOpenness", std::to_string(minimumFractureBranchOpenness));
1602 out.setMeta(
"fractureFlowFeedback", std::to_string(fractureFlowFeedback));
1603 out.setMeta(
"fractureFlowFeedbackModel",
1604 fractureFlowFeedback <= 0.f ?
"disabled"
1605 : fractureDissolution <= 0.f || erosion <= 0.f ?
"inactive-no-chemical-retreat"
1606 : fractures.empty() ?
"inactive-no-fracture-network"
1607 : fractureApertureVariability <= 0.f ?
"inactive-no-aperture-contrast"
1608 :
"cubic-aperture-reactive-channelization-v1");
1609 out.setMeta(
"fractureChannelAffectedVoxels", std::to_string(fractureChannelAffectedVoxels));
1610 out.setMeta(
"maximumFractureChannelRetreat", std::to_string(maximumFractureChannelRetreat));
1611 out.setMeta(
"maximumFractureFlowConcentration", std::to_string(maximumFractureFlowConcentration));
1612 out.setMeta(
"maximumFractureIntersectionAmplification", std::to_string(maximumFractureIntersectionAmplification));
1613 out.setMeta(
"minimumFractureReactantAccess",
1614 std::to_string(fractureChannelAffectedVoxels > 0 ? minimumFractureReactantAccess : 0.f));
1615 out.setMeta(
"scallopErosion", std::to_string(scallopErosion));
1616 out.setMeta(
"scallopScale", std::to_string(scallopScale));
1617 out.setMeta(
"scallopHydraulicScaling", std::to_string(scallopHydraulicScaling));
1618 out.setMeta(
"scallopMaturity", std::to_string(scallopMaturity));
1619 out.setMeta(
"scallopScaleVariability", std::to_string(scallopScaleVariability));
1620 out.setMeta(
"scallopScaleDistribution",
1621 scallopScaleVariability > 0.f ?
"correlated-lognormal-proxy-v1" :
"uniform");
1622 out.setMeta(
"scallopFlowSeparation", std::to_string(scallopFlowSeparation));
1623 out.setMeta(
"scallopFlowProfile", scallopFlowSeparation > 0.f ?
"slope-separated-travelling-wave-v2" :
"legacy");
1624 out.setMeta(
"scallopFlowHistory", std::to_string(scallopFlowHistory));
1625 out.setMeta(
"scallopFlowHistoryModel",
1626 scallopFlowHistory <= 0.f ?
"disabled" :
"partitioned-base-flood-reversal-overprint-v1");
1627 out.setMeta(
"scallopHistoryAffectedVoxels", std::to_string(scallopHistoryAffectedVoxels));
1628 out.setMeta(
"maximumYoungerScallopErosion", std::to_string(maximumYoungerScallopErosion));
1629 out.setMeta(
"maximumYoungerScallopCoverage", std::to_string(maximumYoungerScallopCoverage));
1630 out.setMeta(
"maximumScallopReversalMask", std::to_string(maximumScallopReversalMask));
1631 out.setMeta(
"minimumSecondaryScallopScaleRatio",
1632 std::to_string(scallopHistoryAffectedVoxels > 0 ? minimumSecondaryScallopScaleRatio : 1.f));
1633 out.setMeta(
"scallopEvolutionModel", scallopMaturity > 0.f ?
"normal-ablation-coarsening-v1" :
"stationary-wave");
1634 out.setMeta(
"bendUndercut", std::to_string(bendUndercut));
1635 out.setMeta(
"bendErosionModel", bendUndercut > 0.f ?
"curvature-outer-bank-v1" :
"disabled");
1636 out.setMeta(
"fragmentDetachment", std::to_string(fragmentDetachment));
1637 out.setMeta(
"detachmentModel", fragmentDetachment > 0.f ?
"host-rock-connectivity-v1" :
"disabled");
1638 out.setMeta(
"unsupportedVoxels", std::to_string(detachment.unsupportedVoxels));
1639 out.setMeta(
"detachedVoxels", std::to_string(detachment.detachedVoxels));
1640 out.setMeta(
"curvatureDissolution", std::to_string(curvatureDissolution));
1641 out.setMeta(
"surfaceEvolutionModel", curvatureDissolution > 0.f ?
"convex-normal-retreat-v1" :
"disabled");
1642 out.setMeta(
"depositionAnchoringModel",
1643 dripstoneCount + flowstoneCount + curtainCount > 0 ?
"post-erosion-zero-crossing-v1" :
"disabled");
1644 out.setMeta(
"anchoredDripstonePairs", std::to_string(anchoredDripstonePairs));
1645 out.setMeta(
"rejectedDripstonePairs", std::to_string(rejectedDripstonePairs));
1646 out.setMeta(
"anchoredFlowstones", std::to_string(flowstones.size()));
1647 out.setMeta(
"rejectedFlowstones", std::to_string(rejectedFlowstones));
1648 out.setMeta(
"anchoredCurtains", std::to_string(curtains.size()));
1649 out.setMeta(
"rejectedCurtains", std::to_string(rejectedCurtains));
1650 out.setMeta(
"curvatureAffectedVoxels", std::to_string(surfaceEvolution.affectedVoxels));
1651 out.setMeta(
"maximumCurvatureRetreat", std::to_string(surfaceEvolution.maximumRetreat));
1652 out.setMeta(
"totalCurvatureRetreat", std::to_string(surfaceEvolution.totalRetreat));
1653 out.setMeta(
"reactiveSurfaceCoupling", std::to_string(reactiveSurfaceCoupling));
1654 out.setMeta(
"surfaceSlopeReactivity", std::to_string(surfaceSlopeReactivity));
1655 out.setMeta(
"surfaceReactivityModel",
1656 surfaceSlopeReactivity > 0.f ?
"rotation-invariant-normal-dispersion-v1" :
"disabled");
1657 out.setMeta(
"surfaceReactivityAffectedVoxels", std::to_string(surfaceReactivity.affectedVoxels));
1658 out.setMeta(
"maximumSurfaceReactivityRetreat", std::to_string(surfaceReactivity.maximumRetreat));
1659 out.setMeta(
"totalSurfaceReactivityRetreat", std::to_string(surfaceReactivity.totalRetreat));
1660 out.setMeta(
"maximumSurfaceNormalDispersion", std::to_string(surfaceReactivity.maximumNormalDispersion));
1661 out.setMeta(
"reactivePatchiness", std::to_string(reactivePatchiness));
1662 out.setMeta(
"reactivePatchModel", reactivePatchiness > 0.f ?
"flow-aligned-correlated-psd-v2" :
"disabled");
1663 out.setMeta(
"reactivePatchAffectedVoxels", std::to_string(reactivePatchEvolution.affectedVoxels));
1664 out.setMeta(
"maximumReactivePatchRetreat", std::to_string(reactivePatchEvolution.maximumRetreat));
1665 out.setMeta(
"totalReactivePatchRetreat", std::to_string(reactivePatchEvolution.totalRetreat));
1666 out.setMeta(
"minimumReactivePatchRate", std::to_string(reactivePatchEvolution.minimumPatchRate));
1667 out.setMeta(
"maximumReactivePatchRate", std::to_string(reactivePatchEvolution.maximumPatchRate));
1668 out.setMeta(
"reactivePatchNeighborCoherence", std::to_string(reactivePatchEvolution.meanNeighborCoherence));
1669 out.setMeta(
"reactivePatchFlowCoherence", std::to_string(reactivePatchEvolution.meanFlowCoherence));
1670 out.setMeta(
"reactivePatchTransverseCoherence", std::to_string(reactivePatchEvolution.meanTransverseCoherence));
1671 out.setMeta(
"reactivePatchChannelAnisotropy", std::to_string(reactivePatchEvolution.channelAnisotropy));
1672 out.setMeta(
"condensationCorrosion", std::to_string(condensationCorrosion));
1673 out.setMeta(
"condensationCorrosionModel",
1674 condensationCorrosion > 0.f ?
"cool-wall-co2-film-pitting-v1" :
"disabled");
1675 out.setMeta(
"condensationAffectedVoxels", std::to_string(condensation.affectedVoxels));
1676 out.setMeta(
"maximumCondensationRetreat", std::to_string(condensation.maximumRetreat));
1677 out.setMeta(
"totalCondensationRetreat", std::to_string(condensation.totalRetreat));
1678 out.setMeta(
"biogenicCorrosion", std::to_string(biogenicCorrosion));
1679 out.setMeta(
"biogenicCorrosionModel",
1680 biogenicCorrosion > 0.f ?
"ammonia-nitrification-aero-speleogen-v1" :
"disabled");
1681 out.setMeta(
"biogenicTransportModel",
1682 biogenicCorrosion > 0.f ?
"passage-airflow-wet-film-protection-v1" :
"disabled");
1683 out.setMeta(
"biogenicAffectedVoxels", std::to_string(biogenicAffectedVoxels));
1684 out.setMeta(
"minimumFluvialScallopRetention", std::to_string(minimumScallopRetention));
1685 out.setMeta(
"maximumBiogenicErosion", std::to_string(maximumBiogenicErosion));
1686 out.setMeta(
"totalBiogenicErosion", std::to_string(totalBiogenicErosion));
1687 out.setMeta(
"condensationFaceting", std::to_string(condensationFaceting));
1688 out.setMeta(
"condensationFacetModel",
1689 condensationFaceting > 0.f ?
"local-convection-planar-envelope-v1" :
"disabled");
1690 out.setMeta(
"condensationFacetCount",
1691 condensationFaceting > 0.f ? std::to_string(5 +
int(
params.getSeed() % 3u)) :
"0");
1692 out.setMeta(
"facetAffectedVoxels", std::to_string(facetAffectedVoxels));
1693 out.setMeta(
"maximumFacetRetreat", std::to_string(maximumFacetRetreat));
1694 out.setMeta(
"differentialVeinErosion", std::to_string(differentialVeinErosion));
1695 out.setMeta(
"differentialVeinModel", differentialVeinErosion > 0.f ?
"resistant-vein-host-retreat-v1" :
"disabled");
1696 out.setMeta(
"differentialVeinAffectedVoxels", std::to_string(differentialVeinAffectedVoxels));
1697 out.setMeta(
"maximumDifferentialVeinRetreat", std::to_string(maximumDifferentialVeinRetreat));
1698 out.setMeta(
"maximumVeinProtection", std::to_string(maximumVeinProtection));
1699 out.setMeta(
"breakdown", std::to_string(breakdown));
1700 out.setMeta(
"breakdownModel", breakdown > 0.f ?
"paired-ceiling-spall-talus-v1" :
"disabled");
1701 out.setMeta(
"breakdownEvents", std::to_string(breakdownSet.events.size()));
1702 out.setMeta(
"breakdownBlocks", std::to_string(breakdownSet.blockCount));
1703 out.setMeta(
"breakdownDetachedVolume", std::to_string(breakdownSet.detachedVolume));
1704 out.setMeta(
"breakdownDepositedVolume", std::to_string(breakdownSet.depositedVolume));
1705 out.setMeta(
"sedimentDeposition", std::to_string(sedimentDeposition));
1706 out.setMeta(
"sedimentModel", sedimentDeposition > 0.f ?
"longitudinal-bar-imbrication-v1" :
"disabled");
1707 out.setMeta(
"sedimentBars", std::to_string(sedimentSet.bars.size()));
1708 out.setMeta(
"sedimentClasts", std::to_string(sedimentSet.clastCount));
1709 out.setMeta(
"sedimentVolume", std::to_string(sedimentSet.depositedVolume));
1710 out.setMeta(
"meanImbricationDegrees", std::to_string(sedimentSet.meanImbricationDegrees));
1711 out.setMeta(
"paragenesis", std::to_string(paragenesis));
1712 out.setMeta(
"paragenesisModel", paragenesis > 0.f ?
"alluvial-notch-ceiling-half-tube-v2" :
"disabled");
1713 out.setMeta(
"paragenesisStatus",
1714 paragenesis <= 0.f ?
"disabled" : (sedimentSet.bars.empty() ?
"inactive-no-sediment" :
"applied"));
1715 out.setMeta(
"parageneticChannels", std::to_string(sedimentSet.parageneticChannels));
1716 out.setMeta(
"maximumParageneticLift", std::to_string(sedimentSet.maximumCeilingLift));
1717 out.setMeta(
"meanParageneticWidth", std::to_string(sedimentSet.meanParageneticWidth));
1718 out.setMeta(
"meanPalaeofillRatio", std::to_string(sedimentSet.meanPalaeofillRatio));
1719 out.setMeta(
"maximumAlluvialNotchRetreat", std::to_string(sedimentSet.maximumNotchRetreat));
1720 out.setMeta(
"meanAlluvialNotchThickness", std::to_string(sedimentSet.meanNotchThickness));
1721 out.setMeta(
"surfaceRateModel", reactiveSurfaceCoupling > 0.f ?
"microstructure-hydraulic-access-v1" :
"uniform");
1722 out.setMeta(
"minimumSurfaceRate", std::to_string(surfaceEvolution.minimumRateMultiplier));
1723 out.setMeta(
"maximumSurfaceRate", std::to_string(surfaceEvolution.maximumRateMultiplier));
1724 const float minimumScallopScale = scallopScale *
1725 (1.f + scallopHydraulicScaling * (1.f / std::sqrt(hydrology.maximum) - 1.f)) *
1726 std::exp(-0.38f * scallopScaleVariability);
1727 const float maximumScallopScale = scallopScale *
1728 (1.f + scallopHydraulicScaling * (1.f / std::sqrt(hydrology.minimum) - 1.f)) *
1729 std::exp(0.38f * scallopScaleVariability);
1730 out.setMeta(
"scallopGeometricStdDev", std::to_string(std::exp(0.38f * scallopScaleVariability)));
1731 out.setMeta(
"minimumScallopScale", std::to_string(minimumScallopScale));
1732 out.setMeta(
"maximumScallopScale", std::to_string(maximumScallopScale));
1733 out.setMeta(
"hydraulicErosion", std::to_string(hydraulicErosion));
1734 out.setMeta(
"hydraulicGradient", std::to_string(hydraulicGradient));
1735 out.setMeta(
"recharge", std::to_string(recharge));
1736 out.setMeta(
"flowFocusing", std::to_string(flowFocusing));
1737 out.setMeta(
"effectiveDamkohler", std::to_string(damkohler));
1738 out.setMeta(
"transportG", std::to_string(transportG));
1739 out.setMeta(
"dissolutionRegime", hydrology.dissolutionRegime);
1740 out.setMeta(
"reactantPenetration", std::to_string(hydrology.reactantPenetration));
1741 out.setMeta(
"microstructure", std::to_string(microstructure));
1742 out.setMeta(
"microporosityAccess", std::to_string(microporosityAccess));
1743 out.setMeta(
"permeabilityContrast", std::to_string(permeabilityContrast));
1744 out.setMeta(
"microstructureModel", microstructure > 0.f ?
"dual-scale-accessibility-permeability-v1" :
"disabled");
1745 out.setMeta(
"hydraulicNetwork", hydraulicErosion > 0.f ?
"tributary-confluence-feedback-v1" :
"disabled");
1746 out.setMeta(
"hydraulicConfluences", std::to_string(branches.size()));
1747 out.setMeta(
"minimumFlowWeight", std::to_string(hydrology.minimum));
1748 out.setMeta(
"maximumFlowWeight", std::to_string(hydrology.maximum));
1749 out.setMeta(
"surfaceRefinement", std::to_string(surfaceRefinement));
1750 out.setMeta(
"isosurfaceSampling", std::to_string(isosurfaceSampling));
1751 out.setMeta(
"isosurfaceReconstruction", isosurfaceSampling > 1 ?
"trilinear-supersample-v1" :
"native-grid");
1752 out.setMeta(
"extractionResolution",
1753 std::to_string(extractionNx) +
"x" + std::to_string(extractionNy) +
"x" + std::to_string(extractionNz));
1754 out.setMeta(
"extractionVoxels", std::to_string(extractionDensity->size()));
1755 out.setMeta(
"surfaceProjection", surfaceRefinement > 0 ?
"trilinear-newton-v1" :
"none");
1756 out.setMeta(
"refinementTriangulation", surfaceRefinement == 2
1757 ?
"conforming-edge-mask-v2"
1758 : (surfaceRefinement == 1 ?
"symmetric-four-way-v1" :
"none"));
1759 out.setMeta(
"refinementThreshold", std::to_string(refinementThreshold));
1760 out.setMeta(
"refinedSourceTriangles", std::to_string(refinedSourceTriangles));
1761 out.setMeta(
"adaptiveSplitEdges", std::to_string(adaptiveSplitEdges));
1762 out.setMeta(
"dripstones", std::to_string(dripstoneCount));
1763 out.setMeta(
"columns", std::to_string(columnCount));
1764 out.setMeta(
"flowstones", std::to_string(flowstoneCount));
1765 out.setMeta(
"curtains", std::to_string(curtainCount));
1766 out.setMeta(
"depositionModel",
"damkohler-thin-film-ripple-v2");
1767 out.setMeta(
"normalSmoothing", std::to_string(normalSmoothing));
1768 out.setMeta(
"surfaceNormalMode", surfaceNormalMode);
1769 out.setMeta(
"wetnessRefinement", std::to_string(wetnessRefinement));
1770 out.setMeta(
"wetnessModel", wetnessRefinement > 0 ?
"gravity-drainage-contour-v2" :
"drainage-proximity-v1");
1771 out.setMeta(
"wetnessBoundaryTriangles", std::to_string(
wetness.boundaryTriangles));
1772 out.setMeta(
"wetnessAddedTriangles", std::to_string(
wetness.addedTriangles));
1773 out.setMeta(
"boundaryClosure", std::to_string(boundaryClosure));
1774 out.setMeta(
"boundaryClosureModel", boundaryClosure > 0.f ?
"rough-host-envelope-v1" :
"open-domain");
1775 out.setMeta(
"boundaryAirSamplesBefore", std::to_string(
boundary.airSamplesBefore));
1776 out.setMeta(
"boundaryAirSamplesAfter", std::to_string(
boundary.airSamplesAfter));
1777 out.setMeta(
"boundaryClosureChangedVoxels", std::to_string(
boundary.changedVoxels));
1778 out.setMeta(
"determinism",
"bit-exact-cpu");
building::EdgeCurveGroup group
std::array< std::uint8_t, 32 > hash
std::array< double, 10 > q
std::vector< float > positions
std::array< float, 3 > position
std::array< float, 3 > scale
std::shared_ptr< const std::vector< glm::vec2 > > points
RoadLaneDirection direction
CommandLogBoundary boundary
Anchor rule, see above.
std::vector< ParamSpec > params
float length2(float x, float z)
Length 2.
constexpr HexVec3 lerp(HexVec3 a, HexVec3 b, float t) noexcept
Linear interpolation between two positions.
double sample(const Heightmap &map, double u, double v)
Sample.
double distanceToSegment(const Vec2 &p, const Vec2 &a, const Vec2 &b)
Raster helper: does the pixel-center fall within eps of segment a-b?
double dot(const Vec2 &a, const Vec2 &b)
Dot.
CaveBoundaryClosure closeCaveDensityBoundary(std::vector< float > &density, int nx, int ny, int nz, float strength, uint32_t seed)
Apply a deterministic host-rock envelope at the finite density domain boundary.
CaveRoughnessTransferSample sampleCaveRoughnessTransfer(const CaveRoughnessTransferInput &input)
Estimate local reactive transport over a rough carbonate wall.
CaveRoughnessSample sampleCaveWallRoughness(const CaveRoughnessInput &input)
Sample a three-band, spatially correlated cave-wall relief field.
CaveFractureChannelizationSample sampleCaveFractureChannelization(const CaveFractureChannelizationInput &input)
Approximate fracture aperture-flow-dissolution feedback at one cave-wall sample.
std::optional< CaveSurfaceAnchor > projectToFinalCaveSurface(const std::vector< float > &density, int nx, int ny, int nz, CaveFieldPoint point, float maximumDistance)
Projects a nearby field-space point to the final cave zero isosurface.
CaveMixingCorrosionSample sampleCaveMixingCorrosion(CaveHydrologyVec3 point, const std::vector< CaveMixingSite > &sites)
Sample localized shell retreat around branch-to-trunk mixing sites.
std::optional< CaveVerticalSpan > findCaveVerticalSpan(const std::vector< float > &density, int nx, int ny, int nz, float x, float z, float preferredY)
Finds the first connected cave-air interval intersected by a vertical ray.
float addCaveBreakdownBlocks(float x, float y, float z, float current, const CaveBreakdownSet &breakdown)
Union the event's landed breakdown blocks into a cave SDF.
MeshRecipeFn caveMeshGenerator()
Cave mesh generator.
CaveNormalStatus applyCaveSurfaceNormals(MeshBuild &mesh, const std::vector< float > &density, int nx, int ny, int nz, float width, float height, float depth, const std::string &mode, float blend, std::string &error)
Applies cave surface normals.
CaveSedimentSet createCaveSediment(const std::vector< CaveSedimentPathPoint > &path, int barCount, float strength, float paragenesis, uint32_t seed)
Create flow-aligned cave sediment bars with upstream-imbricated gravel.
CaveReactivePatchinessResult evolveCaveSurfaceByCorrelatedReactivity(std::vector< float > &density, const std::vector< float > &rateField, const std::vector< CaveHydrologyVec3 > &flowField, int nx, int ny, int nz, float strength, uint64_t seed, int iterations)
Retreat a cave surface through a deterministic, multiscale correlated reactivity field.
std::function< bool(const Params ¶ms, MeshBuild &out, std::string &error)> MeshRecipeFn
bool isCaveBreakdownBlockSurface(float x, float y, float z, float tolerance, const CaveBreakdownSet &breakdown)
Test whether a point lies on a generated breakdown block surface.
CaveCondensationResult erodeCaveByCondensation(std::vector< float > &density, const std::vector< float > &hydraulicExposure, int nx, int ny, int nz, float strength, uint32_t seed)
Upscale long-term condensation corrosion into shallow ceiling-wall pitting.
CaveFacetSample sampleCaveCondensationFacets(const CaveFacetInput &input)
Sample planar condensation-corrosion facets in passage-local coordinates.
CaveObstacleScourSample sampleCaveObstacleScour(CaveHydrologyVec3 point, const std::vector< CaveObstacleScourSite > &sites)
Sample deep upstream horseshoe scour and shallower elongated wake erosion.
CaveScallopHistorySample sampleCaveScallopHistory(const CaveScallopHistoryInput &input)
Sample cumulative erosion from two spatially partitioned cave-flow stages.
CaveAbrasionSample sampleCaveFloodAbrasion(const CaveAbrasionInput &input)
Sample mechanical erosion by sediment-laden floods near the passage bed.
float sampleCaveDensity(const std::vector< float > &density, int nx, int ny, int nz, CaveFieldPoint point)
Sample cave density.
std::vector< CaveMixingSite > createCaveMixingSites(const std::vector< CaveHydrologyPoint > &trunk, const std::vector< CaveHydrologyBranch > &branches, uint32_t seed)
Derive mixing sites from authoritative branch anchors in a cave hydrology network.
CaveFieldPoint projectToCaveDensitySurface(CaveFieldPoint meshPoint, const std::vector< float > &density, int nx, int ny, int nz)
Project to cave density surface.
CaveHydrologyWeights buildCaveHydrology(const std::vector< CaveHydrologyPoint > &trunk, const std::vector< CaveHydrologyBranch > &branches, float erosion, float gradient, float recharge, float focusing, float damkohler, float transportG)
Builds cave hydrology.
std::vector< CaveConstrictionScourSite > createCaveConstrictionScourSites(const std::vector< CaveHydrologyPoint > &trunk, const std::vector< CaveHydrologyBranch > &branches, const CaveHydrologyWeights &hydrology)
Derive scour sites from local radius minima in the authoritative hydrology paths.
CaveDetachmentResult detachUnsupportedCaveFragments(std::vector< float > &density, int nx, int ny, int nz, float strength)
Detaches unsupported cave fragments.
std::vector< CavePotholeSite > createCavePotholeSites(const std::vector< CaveHydrologyPoint > &trunk, const std::vector< float > &hydraulicWeights, const std::vector< CaveFracture > &fractures, float apertureVariability, float stressControl, float sedimentLoad, float gravelSize, uint32_t seed)
Derive eddy-pothole sites from intersections of existing fracture planes along the trunk bed.
bool marchingCubes(const float *density, int nx, int ny, int nz, float isolevel, MeshBuild &out, std::string *error)
Classic Marching Cubes (Lorensen & Cline) over a regular scalar volume. Density >= isolevel is treate...
CaveSurfaceReactivityResult evolveCaveSurfaceByReactivity(std::vector< float > &density, const std::vector< float > &rateField, int nx, int ny, int nz, float strength, int iterations)
Retreat rough, highly reactive surface sites using rotation-invariant normal dispersion.
CaveWaterTableSample sampleCaveWaterTableCorrosion(const CaveWaterTableInput &input)
Sample laterally continuous corrosion belts left by one or more water-table stages.
CaveDifferentialErosionSample sampleCaveDifferentialVeinErosion(const CaveDifferentialErosionInput &input)
Sample host-rock retreat around resistant intersecting mineral veins.
float carveCaveParagenesis(float x, float y, float z, float current, const CaveSedimentSet &sediment)
Carve flow-aligned antigravitative ceiling channels above generated sediment bars.
CaveKnickpointSample sampleCaveKnickpointErosion(CaveHydrologyVec3 point, const std::vector< CaveKnickpointSite > &sites)
Sample plunge-pool drilling and lower-headwall erosion around derived knickpoints.
CaveFractureSample sampleCaveFracture(const CaveFractureInput &input)
Sample a spatially correlated fracture aperture and stress-split dissolution front.
CaveSurfaceEvolutionResult evolveCaveSurfaceByCurvature(std::vector< float > &density, int nx, int ny, int nz, float strength, int iterations)
Evolve cave surface by curvature.
CaveWetnessRefinement refineCaveWetnessBoundary(MeshBuild &mesh, const std::vector< CaveHydrologyPoint > &drainageSpine, float fallbackRadius, uint32_t seed, bool splitBoundary)
Refine cave wetness boundary.
CavePassageFrame nearestCavePassageFrame(const CaveHydrologyVec3 &point, const std::vector< CaveHydrologyPoint > &path, const std::vector< float > &hydraulicIntensity)
Nearest cave passage frame.
std::vector< CaveObstacleScourSite > createCaveObstacleScourSites(const CaveBreakdownSet &breakdown, const std::vector< CaveHydrologyPoint > &trunk, const std::vector< float > &hydraulicWeights, float sedimentLoad, float wallRoughness)
Couple landed breakdown blocks to the nearest authoritative cave-stream segment.
CaveLithologySample sampleCaveLithology(const CaveLithologyInput &input)
Sample flow-accessible dissolution caused by contrasting beds and clustered stylolites.
CaveMineralArmoringSample sampleCaveMineralArmoring(const CaveMineralArmoringInput &input)
Sample patchy secondary-mineral coating and its protection of carbonate wall rock.
CaveKarrenSample sampleCaveStreamKarren(const CaveKarrenInput &input)
Sample stream-bed solution grooves guided by the two strongest local fracture sets.
CaveResampledField resampleCaveDensity(const std::vector< float > &density, int nx, int ny, int nz, int factor)
Resample cave density.
std::vector< CaveKnickpointSite > createCaveKnickpointSites(const std::vector< CaveHydrologyPoint > &trunk, const std::vector< float > &hydraulicWeights, float sedimentLoad)
Derive knickpoints from steepening downstream segments of the authoritative trunk path.
CavePluckingSample sampleCaveFloodPlucking(const CavePluckingInput &input)
Sample thresholded removal of blocks bounded by one or more exposed fractures.
CaveMicrostructureSample sampleCaveMicrostructure(float x, float y, float z, uint32_t seed, float heterogeneity, float microporosityAccess, float permeabilityContrast)
Sample cave microstructure.
CaveBreakdownSet createCaveBreakdown(const std::vector< CaveBreakdownChamber > &chambers, int eventCount, float strength, uint32_t seed)
Create paired ceiling-spall scars and floor blocks from host chambers.
CavePotholeSample sampleCavePotholeErosion(CaveHydrologyVec3 point, const std::vector< CavePotholeSite > &sites)
Sample gravel-size-dependent pothole abrasion around derived sites.
CaveConstrictionScourSample sampleCaveConstrictionScour(CaveHydrologyVec3 point, const std::vector< CaveConstrictionScourSite > &sites)
Sample bed scour downstream of a constriction and lateral erosion near the pool exit.
bool isCaveSedimentSurface(float x, float y, float z, float tolerance, const CaveSedimentSet &sediment)
Test whether a point lies on a generated sediment surface.
float carveCaveBreakdownScars(float x, float y, float z, float current, const CaveBreakdownSet &breakdown)
Carve the event's shallow ceiling detachment scars from a cave SDF.
float addCaveSediment(float x, float y, float z, float current, const CaveSedimentSet &sediment)
Union sediment bars and imbricated clasts into a cave SDF.
CaveBiogenicCorrosionSample sampleCaveBiogenicCorrosion(const CaveBiogenicCorrosionInput &input)
Sample an airflow-aligned, guano-ammonia biogenic corrosion overprint.
Result< std::vector< uint8_t > > quantize(std::span< const float > input, float scale, int zero, bool sign)
Affine quantization to int8/uint8 bytes, saturating and rounding ties to even.
int axis(int64_t a, size_t rank)
Axis.
float massTransferMultiplier