载入中...
搜索中...
未找到
CaveMesh.cpp
浏览该文件的文档.
2
39
40#include <algorithm>
41#include <cmath>
42#include <cstdint>
43#include <random>
44#include <string>
45#include <unordered_map>
46#include <vector>
47
48namespace eve::procgen {
49namespace {
50
51using Vec3 = CaveHydrologyVec3;
52using PassagePoint = CaveHydrologyPoint;
53
54struct Chamber {
55 Vec3 center;
56 Vec3 radii;
57 float irregularity = 0.f;
58 float phase = 0.f;
59};
60
61struct RisingConduit {
62 Vec3 start;
63 Vec3 end;
64 float radius = 0.05f;
65};
66
67struct Dripstone {
68 Vec3 start;
69 Vec3 end;
70 float startRadius = 0.05f;
71 float endRadius = 0.01f;
72 float profileAmplitude = 0.f;
73 float profileFrequency = 2.f;
74 float profilePhase = 0.f;
75};
76
77struct Flowstone {
79 Vec3 normal;
80 Vec3 tangent;
81 float halfWidth = 0.08f;
82 float halfHeight = 0.12f;
83 float thickness = 0.02f;
84 float rippleAmplitude = 0.01f;
85 float rippleFrequency = 5.f;
86 float phase = 0.f;
87};
88
89struct Curtain {
90 Vec3 anchor;
91 Vec3 normal;
92 Vec3 tangent;
93 float halfWidth = 0.08f;
94 float length = 0.18f;
95 float thickness = 0.018f;
96 float waveAmplitude = 0.018f;
97 float waveFrequency = 3.f;
98 float phase = 0.f;
99};
100
101struct QuantizedPoint {
102 int x = 0;
103 int y = 0;
104 int z = 0;
105 int group = 0;
106
107 bool operator==(const QuantizedPoint& other) const {
108 return x == other.x && y == other.y && z == other.z && group == other.group;
109 }
110};
111
112struct EdgeKey {
113 int ax = 0, ay = 0, az = 0;
114 int bx = 0, by = 0, bz = 0;
115
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;
118 }
119};
120
121struct EdgeProjection {
123 bool split = false;
124};
125
126uint32_t hash(uint32_t value);
127
128struct EdgeKeyHash {
129 size_t operator()(const EdgeKey& key) const {
130 size_t value = size_t(hash(uint32_t(key.ax)));
131 value ^= size_t(hash(uint32_t(key.ay))) << 1u;
132 value ^= size_t(hash(uint32_t(key.az))) << 2u;
133 value ^= size_t(hash(uint32_t(key.bx))) << 3u;
134 value ^= size_t(hash(uint32_t(key.by))) << 4u;
135 value ^= size_t(hash(uint32_t(key.bz))) << 5u;
136 return value;
137 }
138};
139
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}; }
143Vec3 mul(Vec3 a, float scale) { return {a.x * scale, a.y * scale, a.z * scale}; }
144
145EdgeKey makeEdgeKey(Vec3 a, Vec3 b) {
146 auto quantize = [](Vec3 p) {
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};
149 };
150 QuantizedPoint qa = quantize(a), qb = quantize(b);
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};
154}
155
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);
159}
160
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);
165}
166
167float distanceToSegment(Vec3 point, Vec3 a, Vec3 b) {
168 const Vec3 segment = sub(b, a);
169 const float length2 = dot(segment, segment);
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));
173}
174
175float ellipsoidDistance(Vec3 point, const Chamber& shape) {
176 const Vec3 q = sub(point, shape.center);
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;
180 const float base =
181 (std::sqrt(normalizedX * normalizedX + normalizedY * normalizedY + normalizedZ * normalizedZ) - 1.f) *
182 std::min({shape.radii.x, shape.radii.y, shape.radii.z});
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;
188}
189
190float taperedSegmentDistance(Vec3 point, const Dripstone& shape, float horizontalX = 1.f, float horizontalZ = 1.f) {
191 const Vec3 axis = sub(shape.end, shape.start);
192 const float length2 = dot(axis, axis);
193 const float t = length2 > 1e-8f ? std::clamp(dot(sub(point, shape.start), axis) / length2, 0.f, 1.f) : 0.f;
194 Vec3 delta = sub(point, add(shape.start, mul(axis, t)));
195 delta.x *= horizontalX;
196 delta.z *= horizontalZ;
197 float radius = shape.startRadius + (shape.endRadius - shape.startRadius) * t;
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;
202}
203
204float flowstoneDistance(Vec3 point, const Flowstone& shape) {
205 const Vec3 q = sub(point, shape.center);
206 const float u = dot(q, shape.tangent);
207 const float v = q.y;
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 =
211 std::sqrt((u * u) / (shape.halfWidth * shape.halfWidth) + (v * v) / (shape.halfHeight * shape.halfHeight) +
212 (n * n) / (shape.thickness * shape.thickness)) -
213 1.f;
214 return ellipsoid * std::min({shape.halfWidth, shape.halfHeight, shape.thickness});
215}
216
217float curtainDistance(Vec3 point, const Curtain& shape) {
218 const Vec3 q = sub(point, shape.anchor);
219 const float u = dot(q, shape.tangent);
220 const float downward = -q.y;
221 const float wave = std::sin((u / shape.halfWidth) * shape.waveFrequency + shape.phase) * shape.waveAmplitude;
222 const float n = dot(q, shape.normal) - wave;
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});
227}
228
229uint32_t hash(uint32_t value) {
230 value ^= value >> 16u;
231 value *= 0x7feb352du;
232 value ^= value >> 15u;
233 value *= 0x846ca68bu;
234 return value ^ (value >> 16u);
235}
236
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); };
245 auto sample = [seed](int sx, int sy, int sz) {
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;
248 };
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; };
251 const float x00 = lerp(sample(ix, iy, iz), sample(ix + 1, iy, iz), ux);
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);
256}
257
258Vec3 projectToDensitySurface(Vec3 meshPoint, const std::vector<float>& density, int nx, int ny, int nz) {
259 const CaveFieldPoint point =
260 projectToCaveDensitySurface({meshPoint.x, meshPoint.y, meshPoint.z}, density, nx, ny, nz);
261 return {point.x, point.y, point.z};
262}
263
264void addFacetedTriangle(MeshBuild& mesh, Vec3 a, Vec3 b, Vec3 c, Vec3 referenceNormal) {
265 const Vec3 ab = sub(b, a), ac = sub(c, a);
266 Vec3 normal{ab.y * ac.z - ab.z * ac.y, ab.z * ac.x - ab.x * ac.z, ab.x * ac.y - ab.y * ac.x};
267 if (dot(normal, referenceNormal) < 0.f) {
268 std::swap(b, c);
269 normal = mul(normal, -1.f);
270 }
271 const float length = std::sqrt(dot(normal, normal));
272 if (length > 1e-8f) normal = mul(normal, 1.f / length);
273 const uint32_t base = uint32_t(mesh.getVertexCount());
274 mesh.addVertex(a.x, a.y, a.z, normal.x, normal.y, normal.z, a.x + 0.5f, a.y + 0.5f);
275 mesh.addVertex(b.x, b.y, b.z, normal.x, normal.y, normal.z, b.x + 0.5f, b.y + 0.5f);
276 mesh.addVertex(c.x, c.y, c.z, normal.x, normal.y, normal.z, c.x + 0.5f, c.y + 0.5f);
277 mesh.addTriangle(base, base + 1, base + 2);
278}
279
280bool validStyle(const std::string& style) {
281 return style == "cavern" || style == "tunnels" || style == "vertical" || style == "labyrinth" || style == "mixed";
282}
283
284bool validGenesis(const std::string& genesis) {
285 return genesis == "epigene" || genesis == "hypogene" || genesis == "mixed";
286}
287
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);
302 const float roughness = params.getFloat("roughness", 0.12f);
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);
378 const float height = params.getFloat("height", 12.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)";
382 return false;
383 }
384 if (!validGenesis(genesis)) {
385 error = "mesh.cave: unknown genesis '" + genesis + "' (use epigene|hypogene|mixed)";
386 return false;
387 }
388 if (stalagmiteShape != "conical" && stalagmiteShape != "columnar" && stalagmiteShape != "flatTop" &&
389 stalagmiteShape != "mixed") {
390 error = "mesh.cave: unknown stalagmiteShape '" + stalagmiteShape + "' (use conical|columnar|flatTop|mixed)";
391 return false;
392 }
393 if (surfaceNormalMode != "faceAverage" && surfaceNormalMode != "densityGradient") {
394 error = "mesh.cave: unknown surfaceNormalMode '" + surfaceNormalMode + "' (use faceAverage|densityGradient)";
395 return false;
396 }
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]";
399 return false;
400 }
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]";
403 return false;
404 }
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";
447 return false;
448 }
449
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)});
467 }
468
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;
475 path.reserve(size_t(points + 1));
476 path.push_back(spine[size_t(anchor)]);
477 Vec3 cursor = path.front().position;
478 Vec3 direction{unit(rng) * 0.4f, unit(rng) * (style == "vertical" ? 0.5f : 0.22f),
479 (unit(rng) < 0.f ? -1.f : 1.f) * (0.15f + positive(rng) * 0.2f)};
480 for (int step = 0; step < points; ++step) {
481 direction.x = std::clamp(direction.x + unit(rng) * 0.13f, -0.35f, 0.35f);
482 direction.y = std::clamp(direction.y + unit(rng) * 0.1f, -0.32f, 0.32f);
483 direction.z = std::clamp(direction.z + unit(rng) * 0.13f, -0.4f, 0.4f);
484 cursor = add(cursor, direction);
485 cursor.x = std::clamp(cursor.x, -0.76f, 0.76f);
486 cursor.y = std::clamp(cursor.y, -0.72f, 0.72f);
487 cursor.z = std::clamp(cursor.z, -0.74f, 0.74f);
488 path.push_back({cursor, tunnelRadius * (0.58f + positive(rng) * 0.45f)});
489 }
490 branches.push_back({anchor, std::move(path)});
491 }
492
493 const CaveHydrologyWeights hydrology = buildCaveHydrology(spine, branches, hydraulicErosion, hydraulicGradient,
494 recharge, flowFocusing, damkohler, transportG);
495 const std::vector<CaveMixingSite> mixingSites = createCaveMixingSites(spine, branches, params.getSeed());
496 const std::vector<CaveConstrictionScourSite> constrictionScourSites =
497 createCaveConstrictionScourSites(spine, branches, hydrology);
498 const std::vector<CaveKnickpointSite> knickpointSites =
499 createCaveKnickpointSites(spine, hydrology.trunk, sedimentLoad);
500
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))
509 : 0.f;
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
517 ? 1.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;
528 }
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 =
535 createCaveBreakdown(breakdownChambers, breakdownEvents, breakdown, params.getSeed());
536 const std::vector<CaveObstacleScourSite> obstacleScourSites =
537 createCaveObstacleScourSites(breakdownSet, spine, hydrology.trunk, sedimentLoad, multiscaleRoughness);
538 std::vector<CaveSedimentPathPoint> sedimentPath;
539 sedimentPath.reserve(spine.size());
540 for (const PassagePoint& point : spine)
541 sedimentPath.push_back({point.position.x, point.position.y, point.position.z, point.radius});
542 const CaveSedimentSet sedimentSet =
543 createCaveSediment(sedimentPath, sedimentBars, sedimentDeposition, paragenesis, params.getSeed());
544
545 // Natural carbonate caves preferentially enlarge pre-existing joints. A sparse set of
546 // deterministic vertical fracture planes approximates that structural control without
547 // requiring a full reactive-transport solve at recipe-build time.
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});
553 }
554 const std::vector<CavePotholeSite> potholeSites =
555 createCavePotholeSites(spine, hydrology.trunk, fractures, fractureApertureVariability, fractureStressControl,
556 sedimentLoad, potholeGravelSize, params.getSeed());
557
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;
570 cupolas.push_back(
571 {{host.center.x + unit(rng) * host.radii.x * 0.45f, host.center.y + host.radii.y * 0.82f + ry * 0.45f,
572 host.center.z + unit(rng) * host.radii.z * 0.45f},
573 {rx, ry, rz}});
574 }
575 for (int i = 0; i < feederCount; ++i) {
576 const Chamber& host = chambers[size_t(rng() % uint32_t(chambers.size()))];
577 const Vec3 top{host.center.x + unit(rng) * host.radii.x * 0.32f, host.center.y - host.radii.y * 0.35f,
578 host.center.z + unit(rng) * host.radii.z * 0.32f};
579 const Vec3 bottom{top.x + unit(rng) * 0.06f, std::max(-0.96f, top.y - (0.20f + positive(rng) * 0.35f)),
580 top.z + unit(rng) * 0.06f};
581 const float radius = 0.035f + positive(rng) * 0.035f;
582 feeders.push_back({bottom, top, radius});
583 if (!cupolas.empty()) {
584 const Chamber& outlet = cupolas[size_t(i) % cupolas.size()];
585 const Vec3 ceilingStart{top.x, host.center.y + host.radii.y * 0.72f, top.z};
586 ceilingChannels.push_back({ceilingStart, outlet.center, radius * 0.62f});
587 }
588 }
589 }
590
591 // Each drip site creates a slender ceiling deposit and a broader floor deposit. The
592 // latter samples the conical, columnar and flat-topped steady forms described by the
593 // reaction/advection (Damkohler) family, rather than mirroring the stalactite.
594 std::vector<Dripstone> dripstones;
595 dripstones.reserve(size_t(dripstoneCount) * 2u);
596 int columnCount = 0;
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});
615
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");
620 }
621 const float floorRadius = std::clamp(floorLength * (shape == "conical" ? 0.38f
622 : shape == "columnar" ? 0.28f
623 : 0.45f),
624 0.035f, 0.12f);
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;
629 }
630
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;
636 const Vec3 normal{std::cos(angle), 0.f, std::sin(angle)};
637 const Vec3 tangent{-normal.z, 0.f, normal.x};
638 // Keep the deposit centred on the parent chamber wall. Moving it farther
639 // inward creates a detached calcite lens after voxelisation.
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});
648 }
649
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};
662 const Vec3 normal{std::cos(angle), 0.f, std::sin(angle)};
663 const Vec3 tangent{-normal.z, 0.f, normal.x};
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});
669 }
670
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);
746 current = std::min(current, distanceToSegment(p, path[i - 1].position, path[i].position) - radius);
747 }
748 return current;
749 };
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);
757 CavePassageFrame passage = nearestCavePassageFrame(p, spine, hydrology.trunk);
758 for (size_t branch = 0; branch < branches.size(); ++branch) {
759 const CavePassageFrame candidate =
760 nearestCavePassageFrame(p, branches[branch].points, hydrology.branches[branch]);
761 if (candidate.distance < passage.distance) passage = candidate;
762 }
763
764 // After a phreatic tube drains, gravity-driven streams incise a narrower
765 // canyon into its floor. This second connected carve preserves traversability.
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]);
773 incision = std::min(
774 incision,
775 distanceToSegment(incisedPoint, spine[i - 1].position, spine[i].position) - radius);
776 }
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]);
783 incision = std::min(
784 incision,
785 distanceToSegment(incisedPoint, path[i - 1].position, path[i].position) - radius);
786 }
787 }
788 d = std::min(d, incision);
789 }
790 for (const Chamber& chamber : chambers) {
791 d = std::min(d, ellipsoidDistance(p, chamber));
792 }
793 for (const Chamber& cupola : cupolas) d = std::min(d, ellipsoidDistance(p, cupola));
794 for (const RisingConduit& feeder : feeders)
795 d = std::min(d, distanceToSegment(p, feeder.start, feeder.end) - feeder.radius);
796 for (const RisingConduit& channel : ceilingChannels)
797 d = std::min(d, distanceToSegment(p, channel.start, channel.end) - channel.radius);
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) {
802 const CaveRoughnessSample spectrum = sampleCaveWallRoughness({p.x, p.y, p.z, params.getSeed()});
803 strata = legacyStrata * (1.f - multiscaleRoughness) + spectrum.relief * multiscaleRoughness;
804 }
805 minimumWallRelief = std::min(minimumWallRelief, strata);
806 maximumWallRelief = std::max(maximumWallRelief, strata);
807 d += strata * roughness * 0.32f;
808 d = carveCaveBreakdownScars(p.x, p.y, p.z, d, breakdownSet);
809 d = carveCaveParagenesis(p.x, p.y, p.z, d, sedimentSet);
810
811 // Apply dissolution only in a shell around the connected void. Bedding bands
812 // produce horizontal notches, joints produce tall slots, and positive fine
813 // noise creates solution pockets/scallops instead of uniformly inflating rock.
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 =
825 sampleCaveFracture({p.x, p.y, p.z, fracture, fractureApertureVariability, fractureStressControl,
826 params.getSeed()});
827 if (sample.mask > fractureMask) {
828 secondaryFractureMask = fractureMask;
829 secondaryFractureAperture = fractureAperture;
830 fractureMask = sample.mask;
831 fractureAperture = sample.apertureMultiplier;
832 } else {
833 if (sample.mask > secondaryFractureMask) {
834 secondaryFractureMask = sample.mask;
835 secondaryFractureAperture = sample.apertureMultiplier;
836 }
837 }
838 minimumFractureAperture = std::min(minimumFractureAperture, sample.apertureMultiplier);
839 maximumFractureAperture = std::max(maximumFractureAperture, sample.apertureMultiplier);
840 minimumFractureBranchOpenness = std::min(minimumFractureBranchOpenness, sample.branchOpenness);
841 }
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));
844 // Fowler's boundary-layer model motivates travelling, cusp-edged scallops.
845 // Local flow and passage curvature add hydraulic scale and outer-bank bias.
846 const CaveScallopHistorySample scallops = sampleCaveScallopHistory(
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);
858 }
859 const CaveBiogenicCorrosionSample biogenic =
860 sampleCaveBiogenicCorrosion({passage.along, passage.angle, passage.distance, passage.radius,
861 passage.hydraulicIntensity, biogenicCorrosion, params.getSeed()});
862 const CaveFacetSample facets =
863 sampleCaveCondensationFacets({passage.along, passage.angle, passage.distance, passage.radius,
864 condensationFaceting, params.getSeed()});
865 const CaveDifferentialErosionSample differentialVeins = sampleCaveDifferentialVeinErosion(
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) {
873 armoring = sampleCaveMineralArmoring(
874 {passage.along, passage.angle, passage.hydraulicIntensity, mineralSupply, params.getSeed()});
875 }
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) {
880 roughnessTransfer =
881 sampleCaveRoughnessTransfer({strata, passage.hydraulicIntensity, roughnessFlowCoupling});
882 }
883 const float chemicalMassTransfer = roughnessTransfer.massTransferMultiplier;
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);
890 }
891 CaveWaterTableSample waterTable;
892 if (waterTableCorrosion > 0.f && genesis != "hypogene") {
893 waterTable = sampleCaveWaterTableCorrosion({p.y, passage.angle, passage.along, waterTableLevel,
894 waterTableDrop, waterTableFluctuation, waterTableStages,
895 params.getSeed()});
896 }
897 CaveMixingCorrosionSample mixing;
898 if (mixingCorrosion > 0.f) mixing = sampleCaveMixingCorrosion(p, mixingSites);
899 const CaveLithologySample lithology =
900 sampleCaveLithology({p.x, p.y, p.z, passage.along, passage.hydraulicIntensity,
901 lithologicHeterogeneity, params.getSeed()});
902 CaveAbrasionSample abrasion;
903 if (floodAbrasion > 0.f && genesis != "hypogene") {
904 abrasion = sampleCaveFloodAbrasion({passage.along, passage.angle, passage.hydraulicIntensity,
905 passage.bendStrength, passage.outerBankAngle, sedimentLoad,
906 params.getSeed()});
907 }
908 CavePluckingSample plucking;
909 if (floodPlucking > 0.f && genesis != "hypogene" && fractures.size() >= 2) {
910 plucking =
911 sampleCaveFloodPlucking({p.x, p.y, p.z, passage.angle, passage.hydraulicIntensity, fractureMask,
912 secondaryFractureMask, pluckingBlockScale, params.getSeed()});
913 }
914 CaveConstrictionScourSample constriction;
915 if (constrictionScour > 0.f && genesis != "hypogene") {
916 constriction = sampleCaveConstrictionScour(p, constrictionScourSites);
917 }
918 CaveKnickpointSample knickpoint;
919 if (knickpointErosion > 0.f && genesis != "hypogene") {
920 knickpoint = sampleCaveKnickpointErosion(p, knickpointSites);
921 }
922 CaveKarrenSample karren;
923 if (streamBedKarren > 0.f && genesis != "hypogene" && fractures.size() >= 2) {
924 karren = sampleCaveStreamKarren(
925 {passage.angle, passage.hydraulicIntensity, fractureMask, secondaryFractureMask});
926 }
927 CavePotholeSample pothole;
928 if (eddyPotholes > 0.f && genesis != "hypogene") {
929 pothole = sampleCavePotholeErosion(p, potholeSites);
930 }
931 CaveObstacleScourSample obstacleScour;
932 if (breakdownScour > 0.f && genesis != "hypogene") {
933 obstacleScour = sampleCaveObstacleScour(p, obstacleScourSites);
934 }
935 CaveFractureChannelizationSample fractureChannel;
936 if (fractureFlowFeedback > 0.f && fractureApertureVariability > 0.f) {
937 fractureChannel = sampleCaveFractureChannelization(
938 {fractureMask, secondaryFractureMask, fractureAperture, secondaryFractureAperture,
939 passage.hydraulicIntensity, passage.along, hydrology.reactantPenetration});
940 }
941 if (facets.retreat > 1e-6f) {
942 ++facetAffectedVoxels;
943 maximumFacetRetreat = std::max(maximumFacetRetreat, facets.retreat);
944 }
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;
950 }
951 if (differentialVeins.hostRetreat > 1e-6f) {
952 ++differentialVeinAffectedVoxels;
953 maximumDifferentialVeinRetreat =
954 std::max(maximumDifferentialVeinRetreat, differentialVeins.hostRetreat);
955 maximumVeinProtection = std::max(maximumVeinProtection, differentialVeins.veinProtection);
956 }
957 const float dissolution = bedding * beddingMask * 0.055f + fractureDissolution * fractureMask * 0.075f +
958 pockets * 0.032f +
959 scallopErosion * scallops.erosion * biogenic.fluvialScallopRetention *
960 0.045f * (genesis == "hypogene" ? 0.35f : 1.f);
961 const CaveMicrostructureSample rock = sampleCaveMicrostructure(
962 p.x, p.y, p.z, params.getSeed(), microstructure, microporosityAccess, permeabilityContrast);
963 // Preferential paths transport fresher undersaturated water. Their local
964 // permeability co-evolves with accessible reactive surface instead of
965 // treating every limestone voxel as chemically identical.
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);
979 }
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);
992 }
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);
999 }
1000 const float mixingRetreat =
1001 shell * chemicalRetention * chemicalMassTransfer * mixingCorrosion * mixing.erosion * 0.07f;
1002 d -= mixingRetreat;
1003 if (mixingRetreat > 1e-6f) {
1004 ++mixingCorrosionAffectedVoxels;
1005 maximumMixingCorrosionRetreat = std::max(maximumMixingCorrosionRetreat, mixingRetreat);
1006 }
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);
1015 }
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);
1022 }
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);
1030 }
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);
1038 }
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);
1047 }
1048 const float karrenRetreat = shell * streamBedKarren * karren.erosion * 0.052f;
1049 d -= karrenRetreat;
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);
1056 }
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);
1066 }
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);
1076 }
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);
1080 density[voxel] = d;
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;
1086 }
1087 }
1088 }
1089
1090 // The resolved wall geometry now feeds back into intrinsic surface reactivity.
1091 // Normal dispersion is orientation-independent and is recomputed after each retreat.
1092 const CaveSurfaceReactivityResult surfaceReactivity =
1093 evolveCaveSurfaceByReactivity(density, reactiveRate, nx, ny, nz, surfaceSlopeReactivity);
1094
1095 // Mineral-scale reactivity is spatially heterogeneous rather than voxel-white noise.
1096 // A seeded two-band spectrum creates coherent etch patches and feeds each retreat
1097 // iteration back into the current zero isosurface.
1098 const CaveReactivePatchinessResult reactivePatchEvolution = evolveCaveSurfaceByCorrelatedReactivity(
1099 density, reactiveRate, flowDirection, nx, ny, nz, reactivePatchiness, params.getSeed());
1100
1101 // Humid, weakly flushed ceiling sectors receive a separate late-stage corrosion
1102 // overprint before curvature retreat and secondary calcite deposition.
1103 const CaveCondensationResult condensation =
1104 erodeCaveByCondensation(density, hydraulicExposure, nx, ny, nz, condensationCorrosion, params.getSeed());
1105
1106 // Carbonate wall retreat precedes secondary calcite deposition. Curvature-driven
1107 // dissolution rounds exposed convex asperities without blurring protected recesses.
1108 const CaveSurfaceEvolutionResult surfaceEvolution =
1109 evolveCaveSurfaceByCurvature(density, reactiveRate, nx, ny, nz, curvatureDissolution);
1110
1111 // Deposits are generated from stable seeded candidates, but attach only after all
1112 // wall-retreat stages have produced the final host surface. This prevents the old
1113 // analytic chamber boundary from leaving calcite floating in evolved cave air.
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;
1125 const auto span =
1126 findCaveVerticalSpan(density, nx, ny, nz, ceilingCandidate.start.x, ceilingCandidate.start.z, preferredY);
1127 if (!span) {
1128 ++rejectedDripstonePairs;
1129 continue;
1130 }
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;
1134 if (column) {
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,
1141 sampleY,
1142 ceilingCandidate.start.z + std::sin(angle) * clearanceRadius};
1143 if (sampleCaveDensity(density, nx, ny, nz, clearancePoint) > -voxelSize * 0.35f) {
1144 column = false;
1145 break;
1146 }
1147 }
1148 }
1149 }
1150 const float maximumLength = gap * (column ? 0.515f : 0.42f);
1151 const float ceilingLength =
1152 column ? maximumLength
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;
1158 continue;
1159 }
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};
1162 if (column) {
1163 // A mature column is one continuous implicit body. Two overlapping
1164 // tapered segments produced a false waist, discontinuous normals and
1165 // a self-shadowing ring even when their endpoints overlapped.
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;
1174 continue;
1175 }
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;
1190 }
1191 dripstones = std::move(anchoredDripstones);
1192
1193 int rejectedFlowstones = 0;
1194 std::vector<Flowstone> anchoredFlowstones;
1195 anchoredFlowstones.reserve(flowstones.size());
1196 for (Flowstone flowstone : flowstones) {
1197 const auto anchor = projectToFinalCaveSurface(
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;
1201 continue;
1202 }
1203 flowstone.normal = {anchor->rockNormal.x, anchor->rockNormal.y, anchor->rockNormal.z};
1204 Vec3 tangent{-flowstone.normal.z, 0.f, flowstone.normal.x};
1205 const float tangentLength = std::sqrt(dot(tangent, tangent));
1206 if (tangentLength < 1e-4f) {
1207 ++rejectedFlowstones;
1208 continue;
1209 }
1210 flowstone.tangent = mul(tangent, 1.f / tangentLength);
1211 flowstone.center =
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);
1215 }
1216 flowstones = std::move(anchoredFlowstones);
1217
1218 int rejectedCurtains = 0;
1219 std::vector<Curtain> anchoredCurtains;
1220 anchoredCurtains.reserve(curtains.size());
1221 for (Curtain curtain : curtains) {
1222 const auto span =
1223 findCaveVerticalSpan(density, nx, ny, nz, curtain.anchor.x, curtain.anchor.z, curtain.anchor.y);
1224 if (!span || span->ceiling.rockNormal.y < 0.35f) {
1225 ++rejectedCurtains;
1226 continue;
1227 }
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) {
1232 ++rejectedCurtains;
1233 continue;
1234 }
1235 anchoredCurtains.push_back(curtain);
1236 }
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)
1247 d = smoothMaximum(d, -taperedSegmentDistance(p, dripstone, width / height, depth / height), 0.010f);
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);
1253 d = addCaveBreakdownBlocks(p.x, p.y, p.z, d, breakdownSet);
1254 d = addCaveSediment(p.x, p.y, p.z, d, sedimentSet);
1255 }
1256 }
1257 }
1258
1259 const CaveDetachmentResult detachment = detachUnsupportedCaveFragments(density, nx, ny, nz, fragmentDetachment);
1260 const CaveBoundaryClosure boundary =
1261 closeCaveDensityBoundary(density, nx, ny, nz, boundaryClosure, params.getSeed());
1262
1263 CaveResampledField resampled;
1264 const std::vector<float>* extractionDensity = &density;
1265 int extractionNx = nx, extractionNy = ny, extractionNz = nz;
1266 if (isosurfaceSampling > 1) {
1267 resampled = resampleCaveDensity(density, nx, ny, nz, isosurfaceSampling);
1268 extractionDensity = &resampled.density;
1269 extractionNx = resampled.nx;
1270 extractionNy = resampled.ny;
1271 extractionNz = resampled.nz;
1272 }
1273 if (!marchingCubes(extractionDensity->data(), extractionNx, extractionNy, extractionNz, 0.f, out, &error) ||
1274 out.empty()) {
1275 if (error.empty()) error = "mesh.cave: generated an empty cave";
1276 return false;
1277 }
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}));
1280 for (int triangle = 0; triangle < out.getIndexCount() / 3; ++triangle) {
1281 Vec3 center{};
1282 for (int corner = 0; corner < 3; ++corner) {
1283 const int index = out.indices()[size_t(triangle * 3 + corner)];
1284 // marchingCubes emits a cube centered at the origin in [-0.5, 0.5].
1285 center.x += out.getPositionX(index) * 2.f;
1286 center.y += out.getPositionY(index) * 2.f;
1287 center.z += out.getPositionZ(index) * 2.f;
1288 }
1289 center = mul(center, 1.f / 3.f);
1290 const bool createdByDeposition =
1291 sampleCaveDensity(depositDelta, nx, ny, nz, {center.x, center.y, center.z}) > voxelSize * 0.01f;
1292 if (createdByDeposition) {
1293 for (const Dripstone& dripstone : dripstones) {
1294 if (std::fabs(taperedSegmentDistance(center, dripstone, width / height, depth / height)) <=
1295 groupTolerance) {
1296 caveWallGroups[size_t(triangle)] = 1;
1297 break;
1298 }
1299 }
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;
1304 break;
1305 }
1306 }
1307 }
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;
1312 break;
1313 }
1314 }
1315 }
1316 }
1317 if (isCaveBreakdownBlockSurface(center.x, center.y, center.z, groupTolerance, breakdownSet))
1318 caveWallGroups[size_t(triangle)] = 3;
1319 if (isCaveSedimentSurface(center.x, center.y, center.z, groupTolerance, sedimentSet))
1320 caveWallGroups[size_t(triangle)] = 4;
1321 }
1322 auto groupResult = out.restoreGroupData({"caveWalls", "speleothems", "wetWalls", "breakdown", "sediment"},
1323 std::move(caveWallGroups), 0);
1324 if (!groupResult) {
1325 const auto* diagnostic = groupResult.error();
1326 error = "mesh.cave: failed to assign cave wall group";
1327 if (diagnostic) error += ": " + diagnostic->message();
1328 return false;
1329 }
1330 int refinedSourceTriangles = 0;
1331 int adaptiveSplitEdges = 0;
1332 if (surfaceRefinement > 0) {
1333 MeshBuild refined;
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);
1342 Vec3 projected =
1343 projectToDensitySurface(midpoint, *extractionDensity, extractionNx, extractionNy, extractionNz);
1344 Vec3 error = sub(projected, midpoint);
1345 const float errorLength = std::sqrt(dot(error, error));
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);
1350 projected = add(midpoint, error);
1351 }
1352 const bool split = surfaceRefinement == 1 || std::sqrt(dot(error, error)) >= refinementThreshold;
1353 if (split) ++adaptiveSplitEdges;
1354 return edgeProjections.emplace(key, EdgeProjection{projected, split}).first->second;
1355 };
1356 for (int triangle = 0; triangle < out.getIndexCount() / 3; ++triangle) {
1357 const int group = out.getTriangleGroup(triangle);
1358 refined.setActiveGroup(out.getGroupName(group));
1359 Vec3 corners[3];
1360 for (int corner = 0; corner < 3; ++corner) {
1361 const int index = out.getIndex(triangle * 3 + corner);
1362 corners[corner] = {out.getPositionX(index), out.getPositionY(index), out.getPositionZ(index)};
1363 }
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]);
1369 const Vec3 referenceNormal{ab.y * ac.z - ab.z * ac.y, ab.z * ac.x - ab.x * ac.z, ab.x * ac.y - ab.y * ac.x};
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);
1376 continue;
1377 }
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;
1383 case 1:
1384 emit(corners[0], e01.projected, corners[2]);
1385 emit(e01.projected, corners[1], corners[2]);
1386 break;
1387 case 2:
1388 emit(corners[1], e12.projected, corners[0]);
1389 emit(e12.projected, corners[2], corners[0]);
1390 break;
1391 case 3:
1392 emit(corners[1], e12.projected, e01.projected);
1393 emit(corners[0], e01.projected, corners[2]);
1394 emit(e01.projected, e12.projected, corners[2]);
1395 break;
1396 case 4:
1397 emit(corners[2], e20.projected, corners[1]);
1398 emit(e20.projected, corners[0], corners[1]);
1399 break;
1400 case 5:
1401 emit(corners[0], e01.projected, e20.projected);
1402 emit(corners[2], e20.projected, corners[1]);
1403 emit(e20.projected, e01.projected, corners[1]);
1404 break;
1405 case 6:
1406 emit(corners[2], e20.projected, e12.projected);
1407 emit(corners[1], e12.projected, corners[0]);
1408 emit(e12.projected, e20.projected, corners[0]);
1409 break;
1410 case 7:
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);
1415 break;
1416 }
1417 }
1418 out = std::move(refined);
1419 }
1420 const CaveWetnessRefinement wetness =
1421 refineCaveWetnessBoundary(out, spine, tunnelRadius, params.getSeed(), wetnessRefinement > 0);
1422 auto& positions = out.positions();
1423 // marchingCubes already normalizes grid coordinates to [-0.5, 0.5], so world
1424 // dimensions are direct axis scales. Applying another voxel-size division would
1425 // collapse the cave into a tiny patch at the negative world bound.
1426 const float sx = width;
1427 const float sy = height;
1428 const float sz = depth;
1429 if (applyCaveSurfaceNormals(out, *extractionDensity, extractionNx, extractionNy, extractionNz, width, height, depth,
1430 surfaceNormalMode, normalSmoothing, error) != CaveNormalStatus::applied)
1431 return false;
1432 for (size_t i = 0; i < positions.size(); i += 3) {
1433 positions[i] *= sx;
1434 positions[i + 1] *= sy;
1435 positions[i + 2] *= sz;
1436 }
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");
1452 out.setMeta(
1453 "erosionModel",
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");
1779 return true;
1780}
1781
1782} // namespace
1783
1784MeshRecipeFn caveMeshGenerator() { return generateCaveMesh; }
1785
1786} // namespace eve::procgen
double value
Duration start
SQInteger top
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
building::EdgeCurveGroup group
float gap
Vec3 projected
Definition CaveMesh.cpp:122
float profilePhase
Definition CaveMesh.cpp:74
float startRadius
Definition CaveMesh.cpp:70
float phase
Definition CaveMesh.cpp:58
int bz
Definition CaveMesh.cpp:114
Vec3 tangent
Definition CaveMesh.cpp:80
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
float rippleAmplitude
Definition CaveMesh.cpp:84
int bx
Definition CaveMesh.cpp:114
float profileAmplitude
Definition CaveMesh.cpp:72
float waveAmplitude
Definition CaveMesh.cpp:96
float rippleFrequency
Definition CaveMesh.cpp:85
float length
Definition CaveMesh.cpp:94
bool split
Definition CaveMesh.cpp:123
float endRadius
Definition CaveMesh.cpp:71
Vec3 anchor
Definition CaveMesh.cpp:90
int az
Definition CaveMesh.cpp:113
int by
Definition CaveMesh.cpp:114
float thickness
Definition CaveMesh.cpp:83
float profileFrequency
Definition CaveMesh.cpp:73
Vec3 radii
Definition CaveMesh.cpp:56
float irregularity
Definition CaveMesh.cpp:57
float waveFrequency
Definition CaveMesh.cpp:97
float nx
float nz
float ny
float wetness
glm::vec4 p[6]
int column
std::array< std::uint8_t, 32 > hash
Definition Evpack.cpp:172
int triangle
std::uint32_t key
ShaderImageInput shape
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
std::uint32_t ab
std::uint32_t ac
std::vector< float > positions
float v
std::int32_t c
std::int32_t first
int h
std::uint32_t height
std::uint32_t width
std::array< float, 3 > position
std::array< float, 3 > scale
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float roughness
Texture * normal
std::string error
Definition Package.cpp:60
float radius
float halfWidth
float halfHeight
std::string path
Definition PlayHost.cpp:110
std::uint32_t seed
Definition PointSet.cpp:807
std::shared_ptr< const std::vector< glm::vec2 > > points
float d
float t
Mesh * mesh
RoadLaneDirection direction
bool found
double current
float dz
float dx
TacticalUnit * unit
CommandLogBoundary boundary
Anchor rule, see above.
std::size_t cursor
ecs::EntityHandle side
float step
Definition TreeMesh.cpp:314
float size
Definition TreeMesh.cpp:156
UIHostHandle host
uint32_t index
std::uint32_t depth
glm::vec3 point
float bottom
float angle
std::vector< ParamSpec > params
float length2(float x, float z)
Length 2.
Definition AnimMath.h:150
constexpr HexVec3 lerp(HexVec3 a, HexVec3 b, float t) noexcept
Linear interpolation between two positions.
Definition HexMetrics.h:51
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.
Definition UrbanTypes.h:38
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 &params, 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.