载入中...
搜索中...
未找到
MarchingCubes.cpp
浏览该文件的文档.
17
18#include <algorithm>
19#include <cmath>
20#include <cstdint>
21#include <map>
22
23namespace eve::procgen {
24namespace {
25
26#include "procgen/algorithms/MarchingCubesTables.inc"
27
28using eve::procgen::mc_tables::kEdgeTable;
29using eve::procgen::mc_tables::kTriTable;
30
31// Edge endpoints for the 12 cube edges (corner index pairs).
32constexpr int kEdgeCorners[12][2] = {
33 {0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7},
34};
35
36// Corner offsets in unit cube (x,y,z).
37constexpr float kCornerOffset[8][3] = {
38 {0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1}, {0, 1, 1},
39};
40
41inline float lerp(float a, float b, float t) { return a + (b - a) * t; }
42
43inline void normalize3(float &x, float &y, float &z) {
44 const float len = std::sqrt(x * x + y * y + z * z);
45 if (len > 1e-8f) {
46 x /= len;
47 y /= len;
48 z /= len;
49 } else {
50 x = 0.f;
51 y = 1.f;
52 z = 0.f;
53 }
54}
55
56inline float fade(float t) { return t * t * t * (t * (t * 6.f - 15.f) + 10.f); }
57
58inline float grad3(uint32_t h, float x, float y, float z) {
59 const uint32_t g = h & 15u;
60 const float u = g < 8 ? x : y;
61 const float v = g < 4 ? y : (g == 12 || g == 14 ? x : z);
62 return ((g & 1u) ? -u : u) + ((g & 2u) ? -v : v);
63}
64
65float valueNoise3(float x, float y, float z, uint32_t seed) {
66 const int xi = int(std::floor(x));
67 const int yi = int(std::floor(y));
68 const int zi = int(std::floor(z));
69 const float xf = x - float(xi);
70 const float yf = y - float(yi);
71 const float zf = z - float(zi);
72 const float u = fade(xf);
73 const float v = fade(yf);
74 const float w = fade(zf);
75
76 auto h = [&](int ix, int iy, int iz) -> uint32_t {
77 return uint32_t(ix) * 374761393u + uint32_t(iy) * 668265263u + uint32_t(iz) * 1274126177u +
78 seed * 2246822519u;
79 };
80
81 const float n000 = grad3(h(xi, yi, zi), xf, yf, zf);
82 const float n100 = grad3(h(xi + 1, yi, zi), xf - 1, yf, zf);
83 const float n010 = grad3(h(xi, yi + 1, zi), xf, yf - 1, zf);
84 const float n110 = grad3(h(xi + 1, yi + 1, zi), xf - 1, yf - 1, zf);
85 const float n001 = grad3(h(xi, yi, zi + 1), xf, yf, zf - 1);
86 const float n101 = grad3(h(xi + 1, yi, zi + 1), xf - 1, yf, zf - 1);
87 const float n011 = grad3(h(xi, yi + 1, zi + 1), xf, yf - 1, zf - 1);
88 const float n111 = grad3(h(xi + 1, yi + 1, zi + 1), xf - 1, yf - 1, zf - 1);
89
90 const float x00 = lerp(n000, n100, u);
91 const float x10 = lerp(n010, n110, u);
92 const float x01 = lerp(n001, n101, u);
93 const float x11 = lerp(n011, n111, u);
94 const float y0 = lerp(x00, x10, v);
95 const float y1 = lerp(x01, x11, v);
96 return lerp(y0, y1, w); // roughly [-1,1]
97}
98
99float fbm3(float x, float y, float z, uint32_t seed, int octaves) {
100 float sum = 0.f;
101 float amp = 0.5f;
102 float freq = 1.f;
103 float norm = 0.f;
104 for (int i = 0; i < octaves; ++i) {
105 sum += valueNoise3(x * freq, y * freq, z * freq, seed + uint32_t(i) * 1013u) * amp;
106 norm += amp;
107 amp *= 0.5f;
108 freq *= 2.f;
109 }
110 return norm > 0.f ? sum / norm : 0.f;
111}
112
113struct Vec3 {
114 float x = 0.f, y = 0.f, z = 0.f;
115};
116
117Vec3 add(Vec3 a, Vec3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
118Vec3 sub(Vec3 a, Vec3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
119Vec3 mul(Vec3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
120float dot(Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
121Vec3 cross(Vec3 a, Vec3 b) {
122 return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x};
123}
124Vec3 normalized(Vec3 v) {
125 const float len = std::sqrt(dot(v, v));
126 return len > 1e-8f ? mul(v, 1.f / len) : Vec3{0.f, 1.f, 0.f};
127}
128
129struct Triangle {
130 uint32_t a, b, c;
131};
132
133uint64_t edgeKey(uint32_t a, uint32_t b) {
134 if (a > b) std::swap(a, b);
135 return (uint64_t(a) << 32u) | uint64_t(b);
136}
137
138uint32_t midpoint(uint32_t a, uint32_t b, std::vector<Vec3> &vertices,
139 std::map<uint64_t, uint32_t> &cache) {
140 const uint64_t key = edgeKey(a, b);
141 const auto it = cache.find(key);
142 if (it != cache.end()) return it->second;
143 const uint32_t index = uint32_t(vertices.size());
144 vertices.push_back(normalized(add(vertices[a], vertices[b])));
145 cache.emplace(key, index);
146 return index;
147}
148
149void addPlanetVertex(MeshBuild &out, Vec3 p, float radius) {
150 p = normalized(p);
151 constexpr float kPi = 3.14159265358979323846f;
152 const float u = std::atan2(p.z, p.x) / (2.f * kPi) + 0.5f;
153 const float v = std::asin(std::clamp(p.y, -1.f, 1.f)) / kPi + 0.5f;
154 out.addVertex(p.x * radius, p.y * radius, p.z * radius, p.x, p.y, p.z, u, v);
155}
156
157} // namespace
158
159bool generateHexPlanetMesh(const Params &params, MeshBuild &out, std::string &error) {
160 const int subdivisions = params.getInt("subdivisions", 2);
161 const float radius = params.getFloat("radius", 1.f);
162 const float inset = params.getFloat("tileInset", 0.06f);
163 if (subdivisions < 0 || subdivisions > 7) {
164 error = "mesh.hexplanet: subdivisions must be in [0, 7]";
165 return false;
166 }
167 if (!(radius > 0.f)) {
168 error = "mesh.hexplanet: radius must be positive";
169 return false;
170 }
171 if (inset < 0.f || inset >= 0.5f) {
172 error = "mesh.hexplanet: tileInset must be in [0, 0.5)";
173 return false;
174 }
175
176 const float phi = (1.f + std::sqrt(5.f)) * 0.5f;
177 std::vector<Vec3> vertices = {
178 {-1, phi, 0}, {1, phi, 0}, {-1, -phi, 0}, {1, -phi, 0},
179 {0, -1, phi}, {0, 1, phi}, {0, -1, -phi}, {0, 1, -phi},
180 {phi, 0, -1}, {phi, 0, 1}, {-phi, 0, -1}, {-phi, 0, 1},
181 };
182 for (Vec3 &v : vertices) v = normalized(v);
183 std::vector<Triangle> faces = {
184 {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
185 {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
186 {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
187 {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1},
188 };
189 for (int level = 0; level < subdivisions; ++level) {
190 std::map<uint64_t, uint32_t> cache;
191 std::vector<Triangle> next;
192 next.reserve(faces.size() * 4u);
193 for (const Triangle &f : faces) {
194 const uint32_t ab = midpoint(f.a, f.b, vertices, cache);
195 const uint32_t bc = midpoint(f.b, f.c, vertices, cache);
196 const uint32_t ca = midpoint(f.c, f.a, vertices, cache);
197 next.insert(next.end(), {{f.a, ab, ca}, {f.b, bc, ab}, {f.c, ca, bc}, {ab, bc, ca}});
198 }
199 faces.swap(next);
200 }
201
202 std::vector<std::vector<uint32_t>> incident(vertices.size());
203 std::vector<Vec3> faceCenters;
204 faceCenters.reserve(faces.size());
205 for (uint32_t i = 0; i < faces.size(); ++i) {
206 const Triangle &f = faces[i];
207 faceCenters.push_back(normalized(add(add(vertices[f.a], vertices[f.b]), vertices[f.c])));
208 incident[f.a].push_back(i);
209 incident[f.b].push_back(i);
210 incident[f.c].push_back(i);
211 }
212
213 out.clear();
214 int pentagons = 0, hexagons = 0;
215 for (uint32_t cell = 0; cell < vertices.size(); ++cell) {
216 const Vec3 center = vertices[cell];
217 const Vec3 reference = std::fabs(center.y) < 0.9f ? normalized(cross({0, 1, 0}, center))
218 : normalized(cross({1, 0, 0}, center));
220 auto &ring = incident[cell];
221 std::sort(ring.begin(), ring.end(), [&](uint32_t lhs, uint32_t rhs) {
222 const Vec3 a = sub(faceCenters[lhs], mul(center, dot(faceCenters[lhs], center)));
223 const Vec3 b = sub(faceCenters[rhs], mul(center, dot(faceCenters[rhs], center)));
224 return std::atan2(dot(a, tangent), dot(a, reference)) <
225 std::atan2(dot(b, tangent), dot(b, reference));
226 });
227 if (ring.size() == 5) ++pentagons;
228 else if (ring.size() == 6) ++hexagons;
229
230 const uint32_t base = uint32_t(out.getVertexCount());
231 addPlanetVertex(out, center, radius);
232 for (uint32_t faceIndex : ring) {
233 // Pull dual corners toward this cell's center. Re-normalizing keeps every tile spherical.
234 addPlanetVertex(out, normalized(add(mul(faceCenters[faceIndex], 1.f - inset),
235 mul(center, inset))), radius);
236 }
237 for (uint32_t i = 0; i < ring.size(); ++i) {
238 out.addTriangle(base, base + 1u + i, base + 1u + (i + 1u) % uint32_t(ring.size()));
239 }
240 }
241 out.setMeta("algorithm", "mesh.hexplanet");
242 out.setMeta("cells", std::to_string(vertices.size()));
243 out.setMeta("pentagons", std::to_string(pentagons));
244 out.setMeta("hexagons", std::to_string(hexagons));
245 out.setMeta("subdivisions", std::to_string(subdivisions));
246 return true;
247}
248
249bool marchingCubes(const float *density, int nx, int ny, int nz, float isolevel, MeshBuild &out,
250 std::string *error) {
251 if (!density) {
252 if (error) *error = "marchingCubes: null density";
253 return false;
254 }
255 if (nx < 2 || ny < 2 || nz < 2) {
256 if (error) *error = "marchingCubes: volume must be at least 2x2x2";
257 return false;
258 }
259
260 out.clear();
261 out.reserve((nx * ny * nz) / 2, (nx * ny * nz) * 3);
262
263 auto at = [&](int x, int y, int z) -> float {
264 return density[size_t(x) + size_t(y) * size_t(nx) + size_t(z) * size_t(nx) * size_t(ny)];
265 };
266
267 // World-space mapping: unit cube centered at origin spanning [-0.5, 0.5]^3.
268 const float sx = 1.f / float(nx - 1);
269 const float sy = 1.f / float(ny - 1);
270 const float sz = 1.f / float(nz - 1);
271
272 for (int z = 0; z < nz - 1; ++z) {
273 for (int y = 0; y < ny - 1; ++y) {
274 for (int x = 0; x < nx - 1; ++x) {
275 float val[8];
276 int cubeIndex = 0;
277 for (int i = 0; i < 8; ++i) {
278 const int cx = x + int(kCornerOffset[i][0]);
279 const int cy = y + int(kCornerOffset[i][1]);
280 const int cz = z + int(kCornerOffset[i][2]);
281 val[i] = at(cx, cy, cz);
282 if (val[i] < isolevel) cubeIndex |= (1 << i);
283 }
284 const int edges = kEdgeTable[cubeIndex];
285 if (edges == 0) continue;
286
287 float vertList[12][3];
288 for (int e = 0; e < 12; ++e) {
289 if (!(edges & (1 << e))) continue;
290 const int a = kEdgeCorners[e][0];
291 const int b = kEdgeCorners[e][1];
292 const float va = val[a];
293 const float vb = val[b];
294 float t = (isolevel - va) / (vb - va + 1e-12f);
295 t = std::clamp(t, 0.f, 1.f);
296 const float px =
297 (float(x) + lerp(kCornerOffset[a][0], kCornerOffset[b][0], t)) * sx - 0.5f;
298 const float py =
299 (float(y) + lerp(kCornerOffset[a][1], kCornerOffset[b][1], t)) * sy - 0.5f;
300 const float pz =
301 (float(z) + lerp(kCornerOffset[a][2], kCornerOffset[b][2], t)) * sz - 0.5f;
302 vertList[e][0] = px;
303 vertList[e][1] = py;
304 vertList[e][2] = pz;
305 }
306
307 for (int i = 0; kTriTable[cubeIndex][i] != -1; i += 3) {
308 const int e0 = kTriTable[cubeIndex][i];
309 const int e1 = kTriTable[cubeIndex][i + 1];
310 const int e2 = kTriTable[cubeIndex][i + 2];
311 const float *p0 = vertList[e0];
312 const float *p1 = vertList[e1];
313 const float *p2 = vertList[e2];
314
315 float ax = p1[0] - p0[0], ay = p1[1] - p0[1], az = p1[2] - p0[2];
316 float bx = p2[0] - p0[0], by = p2[1] - p0[1], bz = p2[2] - p0[2];
317 float nxn = ay * bz - az * by;
318 float nyn = az * bx - ax * bz;
319 float nzn = ax * by - ay * bx;
320 normalize3(nxn, nyn, nzn);
321
322 // Flip so normals point toward empty (lower density / outside).
323 // With cubeIndex bits for val < isolevel, winding already tends outward.
324 const uint32_t base = uint32_t(out.getVertexCount());
325 const float u0 = p0[0] + 0.5f, v0 = p0[1] + 0.5f;
326 const float u1 = p1[0] + 0.5f, v1 = p1[1] + 0.5f;
327 const float u2 = p2[0] + 0.5f, v2 = p2[1] + 0.5f;
328 out.addVertex(p0[0], p0[1], p0[2], nxn, nyn, nzn, u0, v0);
329 out.addVertex(p1[0], p1[1], p1[2], nxn, nyn, nzn, u1, v1);
330 out.addVertex(p2[0], p2[1], p2[2], nxn, nyn, nzn, u2, v2);
331 out.addTriangle(base, base + 1, base + 2);
332 }
333 }
334 }
335 }
336
337 out.setMeta("algorithm", "mesh.marchingcubes");
338 return true;
339}
340
341bool fillDensityField(const Params &params, std::vector<float> &density, int &nx, int &ny, int &nz,
342 std::string &error) {
343 const int res = params.getInt("resolution",
344 params.getWidth() > 0 ? params.getWidth() : 24);
345 nx = params.getInt("nx", res);
346 ny = params.getInt("ny", params.getHeight() > 0 ? params.getHeight() : res);
347 nz = params.getInt("nz", params.getInt("depth", res));
348 if (nx < 2 || ny < 2 || nz < 2) {
349 error = "mesh.marchingcubes: resolution must be at least 2 in each axis";
350 return false;
351 }
352 if (nx > 128 || ny > 128 || nz > 128) {
353 error = "mesh.marchingcubes: resolution capped at 128 per axis";
354 return false;
355 }
356
357 const std::string field = params.getString("field", "sphere");
358 const float scale = params.getFloat("scale", 1.f);
359 const int octaves = std::max(1, params.getInt("octaves", 3));
360 const uint32_t seed = params.getSeed();
361
362 density.assign(size_t(nx) * size_t(ny) * size_t(nz), 0.f);
363 for (int z = 0; z < nz; ++z) {
364 for (int y = 0; y < ny; ++y) {
365 for (int x = 0; x < nx; ++x) {
366 const float px = (float(x) / float(nx - 1) - 0.5f) * 2.f;
367 const float py = (float(y) / float(ny - 1) - 0.5f) * 2.f;
368 const float pz = (float(z) / float(nz - 1) - 0.5f) * 2.f;
369 float d = 0.f;
370 if (field == "sphere") {
371 const float r = params.getFloat("radius", 0.7f);
372 d = r - std::sqrt(px * px + py * py + pz * pz);
373 } else if (field == "rock") {
374 // An ellipsoid SDF whose radius is displaced by low-frequency strata and
375 // higher-frequency erosion. Quantising the direction before sampling the
376 // strata creates broad, natural fracture planes instead of a noisy sphere.
377 const float radius = params.getFloat("radius", 0.68f);
378 const float flattening =
379 std::clamp(params.getFloat("flattening", 0.22f), 0.f, 0.7f);
380 const float angularity =
381 std::clamp(params.getFloat("angularity", 0.35f), 0.f, 1.f);
382 const float erosion =
383 std::clamp(params.getFloat("erosion", 0.18f), 0.f, 0.45f);
384 const float detailScale = std::max(0.25f, scale);
385 const float sy = std::max(0.3f, 1.f - flattening);
386 const float ex = px;
387 const float ey = py / sy;
388 const float ez = pz;
389 const float len = std::sqrt(ex * ex + ey * ey + ez * ez);
390 const float invLen = len > 1e-5f ? 1.f / len : 0.f;
391 const float steps = 3.f + angularity * 9.f;
392 const float qx = std::round(ex * invLen * steps) / steps;
393 const float qy = std::round(ey * invLen * steps) / steps;
394 const float qz = std::round(ez * invLen * steps) / steps;
395 const float strata = fbm3((qx + 2.3f) * detailScale,
396 (qy + 4.7f) * detailScale,
397 (qz + 8.1f) * detailScale, seed, octaves);
398 const float pits = fbm3((px + 7.2f) * detailScale * 2.7f,
399 (py + 1.9f) * detailScale * 2.7f,
400 (pz + 5.4f) * detailScale * 2.7f,
401 seed + 7919u, std::max(2, octaves - 1));
402 const float displacement = strata * (0.08f + angularity * 0.16f) -
403 std::max(0.f, pits) * erosion;
404 d = radius + displacement - len;
405 } else if (field == "torus") {
406 const float R = params.getFloat("majorRadius", 0.55f);
407 const float r = params.getFloat("minorRadius", 0.22f);
408 const float q = std::sqrt(px * px + pz * pz) - R;
409 d = r - std::sqrt(q * q + py * py);
410 } else if (field == "terrain") {
411 const float h =
412 fbm3(px * scale + 3.1f, 0.f, pz * scale + 1.7f, seed, octaves) * 0.45f;
413 d = h - py;
414 } else if (field == "noise") {
415 const float n =
416 fbm3(px * scale + 2.f, py * scale + 5.f, pz * scale + 9.f, seed, octaves);
417 d = n - params.getFloat("threshold", 0.05f);
418 } else {
419 error = "mesh.marchingcubes: unknown field '" + field +
420 "' (use sphere|rock|torus|noise|terrain)";
421 return false;
422 }
423 // Soft boundary falloff so surfaces close.
424 const float margin = 0.92f;
425 const float bx = std::max(0.f, std::fabs(px) - margin);
426 const float by = std::max(0.f, std::fabs(py) - margin);
427 const float bz = std::max(0.f, std::fabs(pz) - margin);
428 d -= (bx * bx + by * by + bz * bz) * 4.f;
429 density[size_t(x) + size_t(y) * size_t(nx) + size_t(z) * size_t(nx) * size_t(ny)] =
430 d;
431 }
432 }
433 }
434 return true;
435}
436
437bool generateMarchingCubesMesh(const Params &params, MeshBuild &out, std::string &error) {
438 std::vector<float> density;
439 int nx = 0, ny = 0, nz = 0;
440 if (!fillDensityField(params, density, nx, ny, nz, error)) return false;
441 const float isolevel = params.getFloat("isolevel", 0.f);
442 if (!marchingCubes(density.data(), nx, ny, nz, isolevel, out, &error)) return false;
443 out.setMeta("field", params.getString("field", "sphere"));
444 if (out.empty()) {
445 error = "mesh.marchingcubes: empty mesh (adjust field/isolevel/resolution)";
446 return false;
447 }
448 return true;
449}
450
451MeshRecipeRegistry &MeshRecipeRegistry::instance() {
452 static MeshRecipeRegistry reg;
453 return reg;
454}
455
456void MeshRecipeRegistry::registerRecipe(const std::string &id, MeshRecipeFn fn) {
458 descriptor.id = id;
459 descriptor.displayName = id;
460 registerRecipe(std::move(descriptor), std::move(fn));
461}
462
463void MeshRecipeRegistry::registerRecipe(RecipeDescriptor descriptor, MeshRecipeFn fn) {
464 const std::string id = descriptor.id;
465 recipes_[id] = Entry{std::move(fn), std::move(descriptor)};
466}
467
468bool MeshRecipeRegistry::has(const std::string &id) const {
469 return recipes_.find(id) != recipes_.end();
470}
471
472bool MeshRecipeRegistry::generate(const std::string &id, const Params &params, MeshBuild &out,
473 std::string &error) const {
474 auto it = recipes_.find(id);
475 if (it == recipes_.end()) {
476 error = "unknown mesh recipe '" + id + "'";
477 return false;
478 }
479 return it->second.fn(params, out, error);
480}
481
482const RecipeDescriptor *MeshRecipeRegistry::descriptor(const std::string &id) const {
483 const auto it = recipes_.find(id);
484 return it == recipes_.end() ? nullptr : &it->second.descriptor;
485}
486
487bool MeshRecipeRegistry::applyDefaults(const std::string &id, Params &params) const {
488 const RecipeDescriptor *schema = descriptor(id);
489 if (!schema) return false;
490 schema->applyDefaults(params);
491 return true;
492}
493
494std::vector<std::string> MeshRecipeRegistry::list() const {
495 std::vector<std::string> ids;
496 ids.reserve(recipes_.size());
497 for (const auto &kv : recipes_) ids.push_back(kv.first);
498 std::sort(ids.begin(), ids.end());
499 return ids;
500}
501
502void MeshRecipeRegistry::registerBuiltins() {
503 if (builtinsRegistered_) return;
506 auto mesh = [](std::string id, std::string name) {
507 RecipeDescriptor schema{std::move(id), std::move(name), "Mesh", {}};
508 schema.params.push_back(ParamDescriptor::integer("seed", "Seed", 1, 0, 2147483647));
509 return schema;
510 };
511 auto addAdvanced = [](RecipeDescriptor &schema, ParamDescriptor param) {
512 param.advanced = true;
513 schema.params.push_back(std::move(param));
514 };
515 RecipeDescriptor marching = mesh("mesh.marchingcubes", "Marching Cubes");
516 marching.params.push_back(ParamDescriptor::choice("field", "Density Field", "sphere",
517 {"sphere", "rock", "terrain", "torus", "noise"}));
518 marching.params.push_back(ParamDescriptor::integer("resolution", "Resolution", 24, 4, 256));
519 marching.params.push_back(ParamDescriptor::floating("isolevel", "Iso Level", 0.f, -2.f, 2.f, 0.01f));
520 marching.params.push_back(ParamDescriptor::floating("scale", "Noise Scale", 1.f, 0.01f, 128.f, 0.01f));
521 marching.params.push_back(ParamDescriptor::integer("octaves", "Octaves", 3, 1, 12));
522 addAdvanced(marching, ParamDescriptor::integer("nx", "X Resolution", 24, 2, 512));
523 addAdvanced(marching, ParamDescriptor::integer("ny", "Y Resolution", 24, 2, 512));
524 addAdvanced(marching, ParamDescriptor::integer("nz", "Z Resolution", 24, 2, 512));
525 addAdvanced(marching,
526 ParamDescriptor::floating("radius", "Field Radius", 0.7f, 0.01f, 10.f, 0.01f));
527 addAdvanced(marching, ParamDescriptor::floating("flattening", "Rock Flattening", 0.22f,
528 0.f, 0.7f, 0.01f));
529 addAdvanced(marching, ParamDescriptor::floating("angularity", "Rock Angularity", 0.35f,
530 0.f, 1.f, 0.01f));
531 addAdvanced(marching,
532 ParamDescriptor::floating("erosion", "Rock Erosion", 0.18f, 0.f, 0.45f, 0.01f));
533 addAdvanced(marching, ParamDescriptor::floating("majorRadius", "Torus Major Radius", 0.55f,
534 0.01f, 10.f, 0.01f));
535 addAdvanced(marching, ParamDescriptor::floating("minorRadius", "Torus Minor Radius", 0.22f,
536 0.01f, 10.f, 0.01f));
537 addAdvanced(marching, ParamDescriptor::floating("threshold", "Noise Threshold", 0.05f,
538 -1.f, 1.f, 0.01f));
539 registerRecipe(std::move(marching), generateMarchingCubesMesh);
540
542
543 RecipeDescriptor rock = mesh("mesh.rock", "Rock");
544 rock.params.push_back(ParamDescriptor::choice("baseShape", "Base Shape", "mixed",
545 {"mixed", "boulder", "slab", "block", "shard", "cliff"}));
546 rock.params.push_back(ParamDescriptor::integer("subdivisions", "Subdivisions", 3, 0, 6));
547 rock.params.push_back(ParamDescriptor::floating("radius", "Radius", 0.72f, 0.05f, 32.f, 0.01f));
548 rock.params.push_back(ParamDescriptor::floating("scale", "Scale", 2.4f, 0.25f, 64.f, 0.05f));
549 rock.params.push_back(ParamDescriptor::floating("variation", "Variation", 0.42f, 0.f, 1.f, 0.01f));
550 rock.params.push_back(ParamDescriptor::floating("angularity", "Angularity", 0.38f, 0.f, 1.f, 0.01f));
551 rock.params.push_back(ParamDescriptor::floating("erosion", "Erosion", 0.16f, 0.f, 0.45f, 0.01f));
552 addAdvanced(rock,
553 ParamDescriptor::floating("flattening", "Flattening", 0.22f, 0.f, 0.7f, 0.01f));
554 addAdvanced(rock, ParamDescriptor::integer("octaves", "Octaves", 4, 1, 8));
555 registerRecipe(std::move(rock), generateRockMesh);
556
557 RecipeDescriptor planet = mesh("mesh.hexplanet", "Hex Planet");
558 planet.params.push_back(ParamDescriptor::floating("radius", "Radius", 1.f, 0.01f, 1000.f, 0.01f));
559 planet.params.push_back(ParamDescriptor::integer("subdivisions", "Subdivisions", 2, 0, 7));
560 planet.params.push_back(ParamDescriptor::floating("tileInset", "Tile Inset", 0.06f, 0.f, 0.49f, 0.01f));
561 registerRecipe(std::move(planet), generateHexPlanetMesh);
562
563 RecipeDescriptor terrain = mesh("mesh.hexterrain", "Hex Terrain World");
564 terrain.params.push_back(ParamDescriptor::integer("width", "Width", 32, 2, 256));
565 terrain.params.push_back(ParamDescriptor::integer("height", "Height", 24, 2, 256));
566 terrain.params.push_back(ParamDescriptor::floating("radius", "Hex Radius", 1.f, 0.05f, 64.f, 0.05f));
567 terrain.params.push_back(ParamDescriptor::integer("seed", "Seed", 1, 1, 2147483647));
568 terrain.params.push_back(ParamDescriptor::floating("seaLevel", "Sea Level", 0.43f, 0.f, 1.f, 0.01f));
569 terrain.params.push_back(ParamDescriptor::floating("heightScale", "Height Scale", 4.f, 0.05f, 128.f, 0.05f));
570 terrain.params.push_back(ParamDescriptor::integer("riverCount", "River Count", 8, 0, 128));
571 terrain.params.push_back(ParamDescriptor::boolean("decorations", "Terrain Decorations", true));
572 terrain.params.push_back(ParamDescriptor::floating("vegetationDensity", "Vegetation Density", 1.f, 0.f, 2.f, 0.05f));
573 registerRecipe(std::move(terrain), generateHexTerrainMesh);
574
575 RecipeDescriptor tree = mesh("mesh.tree", "Tree");
576 tree.params.push_back(ParamDescriptor::choice("style", "Style", "lowpoly", {"lowpoly", "realistic"}));
577 tree.params.push_back(
578 ParamDescriptor::choice("leafMode", "Leaf Mode", "cards", {"cards", "clusters", "canopy", "none"}));
579 tree.params.push_back(ParamDescriptor::choice("branchAlgorithm", "Branch Algorithm", "weberPenn",
580 {"weberPenn", "spaceColonization"}));
581 tree.params.push_back(ParamDescriptor::floating("height", "Height", 6.f, 0.5f, 100.f, 0.1f));
582 tree.params.push_back(ParamDescriptor::floating("trunkRadius", "Trunk Radius", 0.33f, 0.02f, 10.f, 0.01f));
583 tree.params.push_back(ParamDescriptor::floating("crownRadius", "Crown Radius", 2.04f, 0.1f, 50.f, 0.05f));
584 tree.params.push_back(ParamDescriptor::floating("leafDensity", "Leaf Density", 0.65f, 0.f, 1.f, 0.01f));
585 tree.params.push_back(ParamDescriptor::integer("branchLevels", "Branch Levels", 2, 1, 5));
586 tree.params.push_back(ParamDescriptor::integer("branchCount", "Branch Count", 6, 2, 20));
587 addAdvanced(tree,
588 ParamDescriptor::floating("leafSize", "Leaf Size", 0.45f, 0.02f, 10.f, 0.01f));
589 addAdvanced(tree, ParamDescriptor::floating("foliageStart", "Foliage Start", 0.35f,
590 0.1f, 0.9f, 0.01f));
591 addAdvanced(tree, ParamDescriptor::integer("radialSegments", "Radial Segments", 6, 3, 24));
592 addAdvanced(tree, ParamDescriptor::integer("curveSegments", "Curve Segments", 5, 2, 20));
593 addAdvanced(tree,
594 ParamDescriptor::floating("trunkCurve", "Trunk Curve", 0.1f, 0.f, 0.45f, 0.01f));
595 addAdvanced(tree,
596 ParamDescriptor::floating("curveBack", "Curve Back", 0.16f, -0.5f, 0.5f, 0.01f));
597 addAdvanced(tree, ParamDescriptor::floating("branchCurve", "Branch Curve", 0.13f, 0.f,
598 0.5f, 0.01f));
599 addAdvanced(tree, ParamDescriptor::floating("branchAngle", "Branch Angle", 62.f, 5.f, 88.f,
600 1.f));
601 addAdvanced(tree, ParamDescriptor::floating("branchAngleVariation", "Angle Variation", 12.f,
602 0.f, 40.f, 1.f));
603 addAdvanced(tree, ParamDescriptor::floating("phyllotaxis", "Phyllotaxis", 137.5f, 0.f,
604 360.f, 0.5f));
605 addAdvanced(tree,
606 ParamDescriptor::floating("tropism", "Tropism", 0.22f, -0.5f, 0.8f, 0.01f));
607 addAdvanced(tree, ParamDescriptor::floating("droop", "Droop", 0.18f, 0.f, 0.8f, 0.01f));
608 addAdvanced(tree, ParamDescriptor::floating("apicalDominance", "Apical Dominance", 0.62f,
609 0.f, 1.f, 0.01f));
610 addAdvanced(tree, ParamDescriptor::integer("attractorCount", "Attractor Count", 80, 12, 1200));
611 addAdvanced(tree,
612 ParamDescriptor::integer("colonizationIterations", "Growth Iterations", 30, 4, 160));
613 addAdvanced(tree, ParamDescriptor::floating("influenceRadius", "Influence Radius", 2.2032f,
614 0.05f, 100.f, 0.01f));
615 addAdvanced(tree, ParamDescriptor::floating("killRadius", "Kill Radius", 0.2652f, 0.01f,
616 100.f, 0.01f));
617 addAdvanced(tree,
618 ParamDescriptor::floating("growthStep", "Growth Step", 0.2856f, 0.01f, 100.f, 0.01f));
619 addAdvanced(tree, ParamDescriptor::floating("branchInertia", "Branch Inertia", 1.2f, 0.f,
620 4.f, 0.01f));
621 addAdvanced(tree, ParamDescriptor::floating("maxTurnAngle", "Maximum Turn Angle", 22.f, 2.f,
622 60.f, 1.f));
623 addAdvanced(tree, ParamDescriptor::floating("maxCumulativeAngle", "Maximum Crown Angle", 58.f,
624 10.f, 85.f, 1.f));
625 addAdvanced(tree, ParamDescriptor::floating("branchLengthFalloff", "Length Falloff", 0.58f,
626 0.f, 0.9f, 0.01f));
627 addAdvanced(tree, ParamDescriptor::floating("branchRadiusFalloff", "Radius Falloff", 0.5f,
628 0.f, 0.9f, 0.01f));
629 addAdvanced(tree, ParamDescriptor::floating("lowerLeafCoverage", "Lower Leaf Coverage", 0.72f,
630 0.f, 1.f, 0.01f));
631 addAdvanced(tree, ParamDescriptor::floating("upperLeafCoverage", "Upper Leaf Coverage", 0.18f,
632 0.f, 1.f, 0.01f));
633 addAdvanced(tree, ParamDescriptor::integer("maxChildren", "Maximum Children", 2, 1, 4));
634 addAdvanced(tree, ParamDescriptor::floating("clusterSize", "Cluster Size", 0.30f, 0.04f, 0.8f, 0.01f));
635 addAdvanced(tree, ParamDescriptor::floating("clusterLeafScale", "Cluster Leaf Scale", 0.85f, 0.1f, 3.f, 0.01f));
636 addAdvanced(tree, ParamDescriptor::floating("clusterSpacing", "Cluster Leaf Spacing", 0.80f, 0.25f, 3.f, 0.01f));
637 addAdvanced(tree, ParamDescriptor::floating("clusterSeparation", "Cluster Separation", 0.55f, 0.1f, 3.f, 0.01f));
638 addAdvanced(tree, ParamDescriptor::floating("clusterTilt", "Cluster Plane Tilt", 26.f, 0.f, 80.f, 1.f));
639 addAdvanced(tree, ParamDescriptor::integer("clusterPlanes", "Cluster Planes", 10, 1, 24));
640 addAdvanced(tree, ParamDescriptor::integer("clusterCaps", "Cluster Cap Planes", 2, 0, 8));
641 addAdvanced(tree, ParamDescriptor::integer("clusterLeaves", "Cluster Leaves Per Plane", 28, 1, 256));
642 addAdvanced(tree, ParamDescriptor::integer("clusterLimit", "Cluster Limit", 120, 1, 512));
643 registerRecipe(std::move(tree), generateTreeMesh);
644
645 RecipeDescriptor bush = mesh("mesh.bush", "Bush");
646 bush.params.push_back(ParamDescriptor::choice("style", "Style", "mound", {"mound", "sphere"}));
647 bush.params.push_back(
648 ParamDescriptor::choice("leafMode", "Leaf Mode", "mixed", {"mixed", "cards", "blobs", "none"}));
649 bush.params.push_back(ParamDescriptor::floating("height", "Height", 1.4f, 0.3f, 30.f, 0.05f));
650 bush.params.push_back(ParamDescriptor::floating("width", "Width", 2.2f, 0.4f, 30.f, 0.05f));
651 bush.params.push_back(ParamDescriptor::integer("blobs", "Foliage Blobs", 9, 1, 40));
652 bush.params.push_back(ParamDescriptor::floating("leafDensity", "Leaf Density", 0.62f, 0.f, 1.f, 0.01f));
653 bush.params.push_back(ParamDescriptor::floating("lobeScale", "Lobe Scale", 0.68f, 0.35f, 1.25f, 0.01f));
654 bush.params.push_back(
655 ParamDescriptor::floating("irregularity", "Irregularity", 0.62f, 0.f, 1.f, 0.01f));
656 addAdvanced(bush, ParamDescriptor::integer("rings", "Rings", 3, 2, 10));
657 addAdvanced(bush, ParamDescriptor::integer("radialSegments", "Radial Segments", 7, 4, 24));
658 addAdvanced(bush,
659 ParamDescriptor::floating("leafSize", "Leaf Size", 0.224f, 0.02f, 10.f, 0.01f));
660 addAdvanced(bush, ParamDescriptor::integer("twigs", "Twigs", 4, 0, 16));
661 addAdvanced(bush,
662 ParamDescriptor::floating("twigLength", "Twig Length", 0.42f, 0.05f, 10.f, 0.01f));
663 registerRecipe(std::move(bush), generateBushMesh);
664
665 RecipeDescriptor flower = mesh("mesh.flower", "Flower");
666 flower.params.push_back(ParamDescriptor::floating("height", "Height", 0.55f, 0.15f, 2.5f, 0.01f));
667 flower.params.push_back(
668 ParamDescriptor::floating("petalLength", "Petal Length", 0.22f, 0.05f, 1.2f, 0.01f));
669 flower.params.push_back(
670 ParamDescriptor::floating("petalWidth", "Petal Width", 0.12f, 0.03f, 1.0f, 0.01f));
671 flower.params.push_back(ParamDescriptor::integer("petals", "Petals", 6, 3, 12));
672 flower.params.push_back(
673 ParamDescriptor::floating("openAngle", "Open Angle", 58.f, 20.f, 85.f, 1.f));
674 addAdvanced(flower,
675 ParamDescriptor::floating("stemRadius", "Stem Radius", 0.02f, 0.005f, 0.12f, 0.001f));
676 addAdvanced(flower, ParamDescriptor::integer("sides", "Stem Sides", 6, 4, 16));
677 registerRecipe(std::move(flower), [](const Params &params, MeshBuild &out, std::string &error) {
678 auto result = generateFlowerMesh(params, out);
679 if (!result.ok()) {
680 error = result.status().describe();
681 return false;
682 }
683 return true;
684 });
685
686 RecipeDescriptor tower = mesh("mesh.skyscraper", "Skyscraper");
687 tower.params.push_back(ParamDescriptor::integer("tiers", "Tiers", 5, 1, 24));
688 tower.params.push_back(ParamDescriptor::floating("baseWidth", "Base Width", 10.f, 0.5f, 500.f, 0.1f));
689 tower.params.push_back(ParamDescriptor::floating("baseDepth", "Base Depth", 10.f, 0.5f, 500.f, 0.1f));
690 tower.params.push_back(ParamDescriptor::floating("tierHeight", "Tier Height", 6.f, 0.5f, 100.f, 0.1f));
691 tower.params.push_back(ParamDescriptor::floating("setback", "Setback", 0.08f, 0.f, 0.6f, 0.01f));
692 tower.params.push_back(ParamDescriptor::integer("windowCols", "Window Columns", 6, 1, 24));
693 tower.params.push_back(ParamDescriptor::integer("windowRows", "Window Rows", 4, 1, 24));
694 addAdvanced(tower, ParamDescriptor::floating("windowDepth", "Window Depth", 0.04f, 0.f,
695 10.f, 0.01f));
696 addAdvanced(tower, ParamDescriptor::floating("spireHeight", "Spire Height", 0.f, 0.f,
697 1000.f, 0.1f));
698 registerRecipe(std::move(tower), generateSkyscraperMesh);
702 urban::registerUrbanMeshRecipes(*this);
703 road::registerRoadMeshRecipes(*this);
705 builtinsRegistered_ = true;
706}
707
708} // namespace eve::procgen
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
std::string descriptor
std::string terrain
AuthorityStoreHandleRef reference
Definition Authority.cpp:24
const std::string & s
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
int bz
Definition CaveMesh.cpp:114
Vec3 tangent
Definition CaveMesh.cpp:80
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
int az
Definition CaveMesh.cpp:113
int by
Definition CaveMesh.cpp:114
float py
float nx
float nz
float ny
float pz
glm::vec4 p[6]
tensor::Graph g
Definition GpuGraph.cpp:7
std::uint32_t key
glm::uvec4 ids
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
std::uint32_t ab
double r
float v
std::int32_t c
int h
std::vector< Colorf > px
std::array< float, 3 > scale
uint32_t a
uint32_t b
float y
float x
float z
std::string name
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
int level
std::string error
Definition Package.cpp:60
float f
std::vector< Point > vertices
float radius
std::string id
Definition PlayHost.cpp:108
std::uint32_t seed
Definition PointSet.cpp:807
float d
int steps
float t
Mesh * mesh
Cell cell
int margin
uint32_t index
std::size_t at
std::vector< double > phi
std::vector< int > edges
float qy
float qx
float qz
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
void addVertex(float px, float py, float pz, float nx, float ny, float nz, float u, float v)
Adds vertex.
Definition MeshBuild.cpp:33
void addTriangle(uint32_t i0, uint32_t i1, uint32_t i2)
Adds triangle.
Definition MeshBuild.cpp:46
void reserve(int vertexCount, int indexCount)
Reserve.
Definition MeshBuild.cpp:20
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
bool empty() const
Empty.
void clear()
Clears .
Definition MeshBuild.cpp:8
EVENGINE_API_DOMAINS public API.
Owning, typed generation parameters.
Definition Params.h:27
std::vector< ParamSpec > params
constexpr HexVec3 lerp(HexVec3 a, HexVec3 b, float t) noexcept
Linear interpolation between two positions.
Definition HexMetrics.h:51
double dot(const Vec2 &a, const Vec2 &b)
Dot.
Definition UrbanTypes.h:38
double cross(const Vec2 &a, const Vec2 &b)
Cross.
Definition UrbanTypes.h:36
void registerCableChainRopeRecipes(MeshRecipeRegistry &registry)
Register mesh.cable / mesh.chain / mesh.rope mesh recipes.
bool generateHexPlanetMesh(const Params &params, MeshBuild &out, std::string &error)
Build the dual of a subdivided icosahedron. The result is a closed planet made of hexagonal cells plu...
void registerCastleMeshRecipe(MeshRecipeRegistry &r)
Register mesh.castle in the built-in mesh recipe registry.
bool generateBushMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic procedural small bush. Registered as the mesh.bush recipe.
Definition BushMesh.cpp:174
bool generateHexTerrainMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic, flat-top hexagonal continent mesh.
std::function< bool(const Params &params, MeshBuild &out, std::string &error)> MeshRecipeFn
void registerCaveMeshRecipe(MeshRecipeRegistry &registry)
Register the deterministic three-dimensional limestone cave recipe.
void registerMeshDeformationGeometryRecipes(MeshRecipeRegistry &registry)
Register the extended-plane and customizable hex-grid mesh recipes.
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...
void registerLinearStructureRecipes(MeshRecipeRegistry &registry)
Register all built-in linear structure mesh recipes into a registry.
bool generateTreeMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic procedural tree. Registered as the mesh.tree recipe.
Definition TreeMesh.cpp:487
eve::Result< void > generateFlowerMesh(const Params &params, MeshBuild &out)
Build a small accent flower (stem + radial petals + centre).
void registerPrototypePieceRecipes(MeshRecipeRegistry &registry)
Register all prototype modules as prototype.* mesh recipes.
void registerLSystemRecipes(MeshRecipeRegistry &registry)
Register the mesh.lsystem grammar-based plant recipe.
bool generateMarchingCubesMesh(const Params &params, MeshBuild &out, std::string &error)
Build mesh from Params (field + resolution + isolevel).
bool fillDensityField(const Params &params, std::vector< float > &density, int &nx, int &ny, int &nz, std::string &error)
Fill a density volume from a named field recipe (sphere / noise / terrain / torus).
bool generateRockMesh(const Params &params, MeshBuild &out, std::string &error)
Build a shared-vertex, deformed icosphere rock for economical game props.
Definition RockMesh.cpp:167
bool generateSkyscraperMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic procedural skyscraper. Registered as the mesh.skyscraper recipe.
SettlementPipeline::Stage fn
UI-independent metadata for one procedural recipe parameter.
Definition ParamSchema.h:14
Complete metadata and parameter schema shared by every procgen recipe family.
Definition ParamSchema.h:52
const ParamDescriptor * find(const std::string &key) const
Find one parameter descriptor by stable key.
std::vector< ParamDescriptor > params
Definition ParamSchema.h:56
void applyDefaults(Params &params) const
Fill missing Params values from this schema.