载入中...
搜索中...
未找到
TreeMesh.cpp
浏览该文件的文档.
2
4
5#include <algorithm>
6#include <cmath>
7#include <cstdint>
8#include <random>
9#include <vector>
10
11namespace eve::procgen {
12namespace {
13
14constexpr float kPi = 3.14159265358979323846f;
15
16struct V3 {
17 float x = 0.f, y = 0.f, z = 0.f;
18};
19
20V3 add(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
21V3 sub(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
22V3 mul(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
23float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
24float distanceSquared(V3 a, V3 b) { return dot(sub(a, b), sub(a, b)); }
25V3 cross(V3 a, V3 b) { 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}; }
26V3 norm(V3 a) {
27 const float n = std::sqrt(std::max(1e-12f, dot(a, a)));
28 return mul(a, 1.f / n);
29}
30
31float randomRange(std::mt19937& rng, float lo, float hi) { return std::uniform_real_distribution<float>(lo, hi)(rng); }
32
33float hash01(uint32_t value) {
34 value ^= value >> 16;
35 value *= 0x7feb352du;
36 value ^= value >> 15;
37 value *= 0x846ca68bu;
38 value ^= value >> 16;
39 return float(value & 0x00ffffffu) / float(0x01000000u);
40}
41
42void basisFor(V3 axis, V3& right, V3& forward) {
43 axis = norm(axis);
44 const V3 helper = std::fabs(axis.y) < 0.92f ? V3{0.f, 1.f, 0.f} : V3{1.f, 0.f, 0.f};
45 right = norm(cross(helper, axis));
46 forward = norm(cross(axis, right));
47}
48
49void addTaperedCylinder(MeshBuild& out, V3 a, V3 b, float r0, float r1, int sides) {
50 const V3 axis = norm(sub(b, a));
51 V3 right, forward;
52 basisFor(axis, right, forward);
53 const uint32_t base = uint32_t(out.getVertexCount());
54 for (int ring = 0; ring < 2; ++ring) {
55 const V3 center = ring ? b : a;
56 const float radius = ring ? r1 : r0;
57 for (int i = 0; i < sides; ++i) {
58 const float t = float(i) / float(sides);
59 const float angle = t * 2.f * kPi;
60 const V3 radial = add(mul(right, std::cos(angle)), mul(forward, std::sin(angle)));
61 const V3 p = add(center, mul(radial, radius));
62 // Left side of the tree atlas is bark; foliage uses the right side.
63 out.addVertex(p.x, p.y, p.z, radial.x, radial.y, radial.z, t * 0.45f, float(ring));
64 }
65 }
66 for (int i = 0; i < sides; ++i) {
67 const uint32_t n = uint32_t((i + 1) % sides);
68 const uint32_t i0 = base + uint32_t(i), i1 = base + n;
69 const uint32_t i2 = base + uint32_t(sides) + uint32_t(i);
70 const uint32_t i3 = base + uint32_t(sides) + n;
71 out.addTriangle(i0, i2, i1);
72 out.addTriangle(i1, i2, i3);
73 }
74}
75
76void addLeafCard(MeshBuild& out, V3 c, V3 direction, float size, float twist, std::mt19937& rng) {
77 V3 right, up;
78 basisFor(norm(direction), right, up);
79 right = add(mul(right, std::cos(twist)), mul(up, std::sin(twist)));
80 up = norm(cross(norm(direction), right));
81 const V3 normal = norm(cross(right, up));
82 const float card = size * 1.25f;
83 const float halfW = card * randomRange(rng, 0.55f, 0.72f);
84 const float halfH = card * randomRange(rng, 0.55f, 0.72f);
85 const V3 r = mul(right, halfW), h = mul(up, halfH);
86 const V3 center = add(c, mul(normal, size * 0.04f));
87 const V3 points[4] = {sub(sub(center, r), h), add(sub(center, h), r), add(add(center, r), h),
88 add(sub(center, r), h)};
89
90 // One of six leaf-card panels (8–16 blue-noise ovate stamps each).
91 constexpr float kCardU0 = 0.52f;
92 constexpr float kCardU1 = 1.00f;
93 constexpr int kCols = 2;
94 constexpr int kRows = 3;
95 const int panel = int(randomRange(rng, 0.f, float(kCols * kRows))) % (kCols * kRows);
96 const int col = panel % kCols;
97 const int row = panel / kCols;
98 const float inset = 0.04f;
99 const float cellWU = (kCardU1 - kCardU0) / float(kCols);
100 const float cellWV = 1.f / float(kRows);
101 const float u0 = kCardU0 + (float(col) + inset) * cellWU;
102 const float u1 = kCardU0 + (float(col) + 1.f - inset) * cellWU;
103 const float v0 = (float(row) + inset) * cellWV;
104 const float v1 = (float(row) + 1.f - inset) * cellWV;
105 const bool flipU = randomRange(rng, 0.f, 1.f) > 0.5f;
106 const bool flipV = randomRange(rng, 0.f, 1.f) > 0.5f;
107 const float uv[4][2] = {
108 {flipU ? u1 : u0, flipV ? v1 : v0},
109 {flipU ? u0 : u1, flipV ? v1 : v0},
110 {flipU ? u0 : u1, flipV ? v0 : v1},
111 {flipU ? u1 : u0, flipV ? v0 : v1},
112 };
113 const uint32_t base = uint32_t(out.getVertexCount());
114 for (int i = 0; i < 4; ++i)
115 out.addVertex(points[i].x, points[i].y, points[i].z, normal.x, normal.y, normal.z, uv[i][0], uv[i][1]);
116 out.addTriangle(base, base + 1, base + 2);
117 out.addTriangle(base, base + 2, base + 3);
118 out.addTriangle(base + 2, base + 1, base);
119 out.addTriangle(base + 3, base + 2, base);
120}
121
122void addCanopyBlob(MeshBuild& out, V3 center, V3 radius, int rings, int sides) {
123 const uint32_t base = uint32_t(out.getVertexCount());
124 for (int y = 0; y <= rings; ++y) {
125 const float v = float(y) / float(rings);
126 const float phi = v * kPi;
127 for (int x = 0; x < sides; ++x) {
128 const float u = float(x) / float(sides);
129 const float theta = u * 2.f * kPi;
130 const V3 n = {std::sin(phi) * std::cos(theta), std::cos(phi), std::sin(phi) * std::sin(theta)};
131 const V3 p = {center.x + n.x * radius.x, center.y + n.y * radius.y, center.z + n.z * radius.z};
132 out.addVertex(p.x, p.y, p.z, n.x, n.y, n.z, 0.55f + u * 0.45f, v);
133 }
134 }
135 for (int y = 0; y < rings; ++y) {
136 for (int x = 0; x < sides; ++x) {
137 const int nx = (x + 1) % sides;
138 const uint32_t a = base + uint32_t(y * sides + x);
139 const uint32_t b = base + uint32_t(y * sides + nx);
140 const uint32_t c = base + uint32_t((y + 1) * sides + x);
141 const uint32_t d = base + uint32_t((y + 1) * sides + nx);
142 out.addTriangle(a, c, b);
143 out.addTriangle(b, c, d);
144 }
145 }
146}
147
148struct Tip {
149 V3 p;
150 V3 dir;
151 float scale;
152};
153
155struct ClusterSettings {
156 float size; // cluster radius as a fraction of the crown radius
157 float leafScale; // leaf length as a fraction of the `leafSize` parameter
158 float spacing; // blue-noise centre distance, in leaf-size units
159 float separation; // minimum cluster-centre distance, in cluster radii
160 float tilt; // maximum ring-plane tilt away from vertical, in degrees
161 int planes; // planes rotated around the cluster's Y axis
162 int caps; // near-horizontal planes closing the cluster's poles
163 int leavesPerPlane; // per-plane leaf budget
164 int limit; // maximum clusters per tree
165};
166
187int addFoliageClusters(MeshBuild& out, const std::vector<Tip>& anchors, std::mt19937& rng, float crownRadius,
188 float leafSize, float density, uint32_t seed, const ClusterSettings& settings) {
189 if (anchors.empty() || settings.limit <= 0) return 0;
190
191 std::vector<size_t> order(anchors.size());
192 for (size_t i = 0; i < order.size(); ++i) order[i] = i;
193 for (size_t i = order.size(); i > 1; --i)
194 std::swap(order[i - 1], order[size_t(randomRange(rng, 0.f, float(i) - 1e-4f))]);
195
196 const float baseRadius = std::max(1e-3f, crownRadius * settings.size);
197 const float separation = std::max(0.05f, settings.separation) * baseRadius * 2.f;
198 const float separationSq = separation * separation;
199 // Higher density packs the same cluster volume with more, closer leaves.
200 const float spacing = std::max(0.05f, settings.spacing * (1.45f - 0.75f * density));
201
202 std::vector<V3> accepted;
203 accepted.reserve(size_t(settings.limit));
204 int clusters = 0;
205 for (size_t k = 0; k < order.size() && clusters < settings.limit; ++k) {
206 const Tip& tip = anchors[order[k]];
207 bool blocked = false;
208 for (const V3& taken : accepted) {
209 if (distanceSquared(taken, tip.p) < separationSq) {
210 blocked = true;
211 break;
212 }
213 }
214 if (blocked) continue;
215
216 const float radius = baseRadius * (0.62f + 0.38f * tip.scale) * randomRange(rng, 0.88f, 1.12f);
217 // Pull the cluster back along the shoot so the twig visibly enters the
218 // foliage instead of stopping just short of it.
219 const V3 center = add(sub(tip.p, mul(tip.dir, radius * 0.10f)), {0.f, radius * 0.06f, 0.f});
220
221 FoliageClusterDesc cluster;
222 cluster.seed = seed ^ (uint32_t(clusters + 1) * 2654435761u);
223 cluster.centerX = center.x;
224 cluster.centerY = center.y;
225 cluster.centerZ = center.z;
226 cluster.radius = radius;
227 cluster.leafSize = leafSize * settings.leafScale * randomRange(rng, 0.85f, 1.15f);
228 cluster.leafSpacing = spacing;
229 cluster.planes = settings.planes;
230 cluster.capPlanes = settings.caps;
231 cluster.tiltDegrees = settings.tilt;
232 cluster.maxLeavesPerPlane = settings.leavesPerPlane;
233 cluster.normalRounding = 1.f;
234 cluster.doubleSided = true;
235 addFoliageCluster(out, cluster);
236
237 accepted.push_back(tip.p);
238 ++clusters;
239 }
240 return clusters;
241}
242
243struct StemPath {
244 std::vector<V3> points;
246};
247
248StemPath makeCurvedStem(V3 start, V3 direction, V3 bendAxis, float length, float curve, float curveBack,
249 float verticalAcceleration, int segments) {
250 StemPath path;
251 path.points.reserve(size_t(segments + 1));
252 direction = norm(direction);
253 bendAxis = norm(sub(bendAxis, mul(direction, dot(bendAxis, direction))));
254 const V3 up{0.f, 1.f, 0.f};
255 for (int i = 0; i <= segments; ++i) {
256 const float t = float(i) / float(segments);
257 // Both offsets have zero slope at the branch base, avoiding an immediate kink.
258 const float primary = curve * t * t;
259 const float back = curveBack * std::sin(2.f * kPi * t) * t * (1.f - t);
260 const V3 p = add(start, add(mul(direction, length * t), add(mul(bendAxis, length * (primary + back)),
261 mul(up, length * verticalAcceleration * t * t))));
262 path.points.push_back(p);
263 }
264 path.endDirection = norm(sub(path.points.back(), path.points[path.points.size() - 2]));
265 return path;
266}
267
268void addStem(MeshBuild& out, const StemPath& path, float r0, float r1, int sides) {
269 const int segmentCount = int(path.points.size()) - 1;
270 for (int i = 0; i < segmentCount; ++i) {
271 const float t0 = float(i) / float(segmentCount);
272 const float t1 = float(i + 1) / float(segmentCount);
273 const float a = std::pow(t0, 1.25f);
274 const float b = std::pow(t1, 1.25f);
275 addTaperedCylinder(out, path.points[size_t(i)], path.points[size_t(i + 1)], r0 + (r1 - r0) * a,
276 r0 + (r1 - r0) * b, sides);
277 }
278}
279
280void samplePath(const StemPath& path, float t, V3& point, V3& direction) {
281 const int segmentCount = int(path.points.size()) - 1;
282 const float x = std::clamp(t, 0.f, 1.f) * float(segmentCount);
283 const int i = std::min(int(x), segmentCount - 1);
284 const float local = x - float(i);
285 point = add(path.points[size_t(i)], mul(sub(path.points[size_t(i + 1)], path.points[size_t(i)]), local));
286 direction = norm(sub(path.points[size_t(i + 1)], path.points[size_t(i)]));
287}
288
289struct ColonyNode {
290 V3 p;
291 V3 dir;
292 V3 heading{0.f, 1.f, 0.f};
293 int parent = -1;
294 int descendants = 1;
295 int children = 0;
296 int depth = 0;
297 float remainingLength = 0.f;
298 float vigor = 1.f;
299 float budHeight = 0.f;
300};
301
302struct ColonizationSettings {
303 float height;
307 float tropism;
308 float droop;
309 float inertia;
314 float step;
322 int sides;
323};
324
325void growSpaceColonizedTree(MeshBuild& out, const StemPath& trunk, std::mt19937& rng,
326 const ColonizationSettings& settings, std::vector<Tip>& foliageAnchors) {
327 V3 crownCenter, ignoredDirection;
328 samplePath(trunk, settings.foliageStart + (1.f - settings.foliageStart) * 0.53f, crownCenter, ignoredDirection);
329 const float crownHalfHeight = settings.height * (1.f - settings.foliageStart) * 0.52f;
330
331 std::vector<V3> attractors;
332 attractors.reserve(size_t(settings.attractorCount));
333 while (int(attractors.size()) < settings.attractorCount) {
334 const V3 q{randomRange(rng, -1.f, 1.f), randomRange(rng, -1.f, 1.f), randomRange(rng, -1.f, 1.f)};
335 if (dot(q, q) > 1.f) continue;
336 // A modest upward bias avoids a perfectly symmetric, balloon-like crown.
337 attractors.push_back(add(crownCenter, {q.x * settings.crownRadius,
338 q.y * crownHalfHeight + 0.12f * crownHalfHeight * (1.f - q.y * q.y),
339 q.z * settings.crownRadius}));
340 }
341
342 std::vector<ColonyNode> nodes;
343 // Multiple trunk buds provide the primary scaffold. Colonization then shapes and
344 // splits those limbs; starting from a single bud produces a shrub-like fan.
345 const int seedCount = 9;
346 for (int i = 0; i < seedCount; ++i) {
347 V3 p, dir;
348 const float t =
349 settings.foliageStart + (1.f - settings.foliageStart) * (0.08f + 0.78f * float(i) / float(seedCount - 1));
350 samplePath(trunk, t, p, dir);
351 const float crownHeight = float(i) / float(seedCount - 1);
352 const float lengthScale = 1.f - settings.lengthFalloff * crownHeight;
353 const float vigor = 1.f - settings.radiusFalloff * crownHeight;
354 nodes.push_back({p, dir, dir, -1, 1, 0, 0, settings.crownRadius * 1.35f * lengthScale, vigor, crownHeight});
355 }
356
357 std::vector<uint8_t> alive(size_t(settings.attractorCount), 1);
358 const float influenceSquared = settings.influenceRadius * settings.influenceRadius;
359 const float killSquared = settings.killRadius * settings.killRadius;
360 const float separationSquared = settings.step * settings.step * 0.22f;
361 const float maxTurn = settings.maxTurnAngle * kPi / 180.f;
362 const float maxTurnCos = std::cos(maxTurn);
363 const float maxTurnSin = std::sin(maxTurn);
364 const float cumulativeTurn = settings.maxCumulativeAngle * kPi / 180.f;
365 const float cumulativeTurnCos = std::cos(cumulativeTurn);
366 const float cumulativeTurnSin = std::sin(cumulativeTurn);
367 for (int iteration = 0; iteration < settings.iterations; ++iteration) {
368 const size_t nodeCount = nodes.size();
369 std::vector<V3> directionSums(nodeCount);
370 std::vector<int> directionCounts(nodeCount, 0);
371 int liveAttractors = 0;
372 for (size_t ai = 0; ai < attractors.size(); ++ai) {
373 if (!alive[ai]) continue;
374 ++liveAttractors;
375 size_t nearest = 0;
376 float best = influenceSquared;
377 bool found = false;
378 for (size_t ni = 0; ni < nodeCount; ++ni) {
379 const V3 toward = sub(attractors[ai], nodes[ni].p);
380 const float d = dot(toward, toward);
381 if (d < killSquared) {
382 alive[ai] = 0;
383 found = false;
384 break;
385 }
386 // Once a shoot has left the trunk, attraction from behind may not
387 // reverse it. This is the main distinction between a branch and a vine.
388 if (nodes[ni].depth > 0) {
389 const V3 attractionDirection = norm(toward);
390 if (dot(attractionDirection, nodes[ni].dir) < -0.08f ||
391 dot(attractionDirection, nodes[ni].heading) < 0.04f)
392 continue;
393 }
394 if (d < best) {
395 best = d;
396 nearest = ni;
397 found = true;
398 }
399 }
400 if (found) {
401 directionSums[nearest] = add(directionSums[nearest], norm(sub(attractors[ai], nodes[nearest].p)));
402 ++directionCounts[nearest];
403 }
404 }
405 if (liveAttractors == 0) break;
406
407 std::vector<ColonyNode> additions;
408 for (size_t ni = 0; ni < nodeCount; ++ni) {
409 if (directionCounts[ni] == 0 || nodes[ni].children >= settings.maxChildren ||
410 nodes[ni].remainingLength < settings.step)
411 continue;
412 V3 direction = norm(
413 add(mul(directionSums[ni], 1.f / float(directionCounts[ni])), mul(nodes[ni].dir, settings.inertia)));
414 const float vertical = settings.tropism - settings.droop * std::max(0.f, 1.f - direction.y);
415 direction = norm(add(direction, {0.f, vertical, 0.f}));
416 // Clamp curvature per growth step. Linear blending alone can still turn
417 // sharply when the remaining attractors move to the other side of a tip.
418 const float alignment = std::clamp(dot(nodes[ni].dir, direction), -1.f, 1.f);
419 if (nodes[ni].depth > 0 && alignment < maxTurnCos) {
420 V3 tangent = sub(direction, mul(nodes[ni].dir, alignment));
421 if (dot(tangent, tangent) > 1e-8f)
422 direction = norm(add(mul(nodes[ni].dir, maxTurnCos), mul(norm(tangent), maxTurnSin)));
423 else
424 direction = nodes[ni].dir;
425 }
426 if (nodes[ni].depth > 0) {
427 const float headingAlignment = std::clamp(dot(nodes[ni].heading, direction), -1.f, 1.f);
428 if (headingAlignment < cumulativeTurnCos) {
429 V3 tangent = sub(direction, mul(nodes[ni].heading, headingAlignment));
430 if (dot(tangent, tangent) > 1e-8f)
431 direction =
432 norm(add(mul(nodes[ni].heading, cumulativeTurnCos), mul(norm(tangent), cumulativeTurnSin)));
433 else
434 direction = nodes[ni].heading;
435 }
436 } else if (direction.y < -0.05f) {
437 // Primary limbs may spread horizontally, but do not launch downward.
438 direction = norm({direction.x, -0.05f, direction.z});
439 }
440 const V3 candidate = add(nodes[ni].p, mul(direction, settings.step));
441 bool separated = true;
442 for (size_t oi = 0; oi < nodes.size() && separated; ++oi)
443 separated = distanceSquared(candidate, nodes[oi].p) >= separationSquared;
444 for (size_t oi = 0; oi < additions.size() && separated; ++oi)
445 separated = distanceSquared(candidate, additions[oi].p) >= separationSquared;
446 if (separated) {
447 const V3 heading = nodes[ni].depth == 0 ? direction : nodes[ni].heading;
448 additions.push_back({candidate, direction, heading, int(ni), 1, 0, nodes[ni].depth + 1,
449 nodes[ni].remainingLength - settings.step, nodes[ni].vigor, nodes[ni].budHeight});
450 ++nodes[ni].children;
451 }
452 }
453 if (additions.empty()) break;
454 nodes.insert(nodes.end(), additions.begin(), additions.end());
455 }
456
457 for (int i = int(nodes.size()) - 1; i >= 0; --i) {
458 const int parent = nodes[size_t(i)].parent;
459 if (parent >= 0) {
460 nodes[size_t(parent)].descendants += nodes[size_t(i)].descendants;
461 }
462 }
463 const float twigRadius = settings.trunkRadius * 0.055f;
464 for (size_t i = 0; i < nodes.size(); ++i) {
465 const ColonyNode& node = nodes[i];
466 if (node.parent < 0) continue;
467 const ColonyNode& parent = nodes[size_t(node.parent)];
468 const float r0 = std::min(settings.trunkRadius * 0.34f * parent.vigor,
469 twigRadius * parent.vigor * (1.f + std::sqrt(float(parent.descendants))));
470 const float r1 = std::min(r0 * 0.92f, twigRadius * node.vigor * (1.f + std::sqrt(float(node.descendants))));
471 addTaperedCylinder(out, parent.p, node.p, r0, r1, std::max(3, settings.sides - 2));
472 if (node.children == 0) {
473 foliageAnchors.push_back({node.p, node.dir, 0.42f + 0.22f * node.vigor});
474 } else if (node.depth >= 2 && (node.depth % 2) == 0) {
475 // Older lower limbs carry foliage along the branch, whereas young upper
476 // shoots keep most foliage near their tips.
477 const float coverage =
478 settings.lowerLeafCoverage + (settings.upperLeafCoverage - settings.lowerLeafCoverage) * node.budHeight;
479 if (hash01(uint32_t(i) * 747796405u + 2891336453u) < coverage)
480 foliageAnchors.push_back({node.p, node.dir, 0.48f + 0.20f * (1.f - node.budHeight)});
481 }
482 }
483}
484
485} // namespace
486
487bool generateTreeMesh(const Params& params, MeshBuild& out, std::string& error) {
488 const std::string style = params.getString("style", "lowpoly");
489 const std::string leafMode = params.getString("leafMode", "cards");
490 const std::string branchAlgorithm = params.getString("branchAlgorithm", "weberPenn");
491 if (style != "lowpoly" && style != "realistic") {
492 error = "mesh.tree: style must be lowpoly|realistic";
493 return false;
494 }
495 if (leafMode != "cards" && leafMode != "clusters" && leafMode != "canopy" && leafMode != "none") {
496 error = "mesh.tree: leafMode must be cards|clusters|canopy|none";
497 return false;
498 }
499 if (branchAlgorithm != "weberPenn" && branchAlgorithm != "spaceColonization") {
500 error = "mesh.tree: branchAlgorithm must be weberPenn|spaceColonization";
501 return false;
502 }
503
504 const bool realistic = style == "realistic";
505 const float height = std::max(0.5f, params.getFloat("height", 6.f));
506 const float trunkRadius = std::max(0.02f, params.getFloat("trunkRadius", height * 0.055f));
507 const float crownRadius = std::max(0.1f, params.getFloat("crownRadius", height * 0.34f));
508 const float leafSize = std::max(0.02f, params.getFloat("leafSize", height * 0.075f));
509 const float density = std::clamp(params.getFloat("leafDensity", 0.65f), 0.f, 1.f);
510 const float foliageStart = std::clamp(params.getFloat("foliageStart", 0.35f), 0.1f, 0.9f);
511 const int branchLevels = std::clamp(params.getInt("branchLevels", realistic ? 3 : 2), 1, 5);
512 const int branchCount = std::clamp(params.getInt("branchCount", realistic ? 9 : 6), 2, 20);
513 const int sides = std::clamp(params.getInt("radialSegments", realistic ? 10 : 6), 3, 24);
514 const int curveSegments = std::clamp(params.getInt("curveSegments", realistic ? 9 : 5), 2, 20);
515 const float trunkCurve = std::clamp(params.getFloat("trunkCurve", 0.10f), 0.f, 0.45f);
516 const float curveBack = std::clamp(params.getFloat("curveBack", 0.16f), -0.5f, 0.5f);
517 const float branchCurve = std::clamp(params.getFloat("branchCurve", 0.13f), 0.f, 0.5f);
518 const float branchAngle = std::clamp(params.getFloat("branchAngle", 62.f), 5.f, 88.f);
519 const float angleVariation = std::clamp(params.getFloat("branchAngleVariation", 12.f), 0.f, 40.f);
520 const float phyllotaxis = params.getFloat("phyllotaxis", 137.5f) * kPi / 180.f;
521 const float tropism = std::clamp(params.getFloat("tropism", 0.22f), -0.5f, 0.8f);
522 const float droop = std::clamp(params.getFloat("droop", 0.18f), 0.f, 0.8f);
523 const float apicalDominance = std::clamp(params.getFloat("apicalDominance", 0.62f), 0.f, 1.f);
524 const int attractorCount = std::clamp(params.getInt("attractorCount", realistic ? 180 : 80), 12, 1200);
525 const int colonizationIterations = std::clamp(params.getInt("colonizationIterations", realistic ? 46 : 30), 4, 160);
526 const float influenceRadius = std::max(0.05f, params.getFloat("influenceRadius", crownRadius * 1.08f));
527 const float killRadius = std::max(0.01f, params.getFloat("killRadius", crownRadius * 0.13f));
528 const float growthStep = std::max(0.01f, params.getFloat("growthStep", crownRadius * (realistic ? 0.105f : 0.14f)));
529 const float branchInertia = std::clamp(params.getFloat("branchInertia", 1.20f), 0.f, 4.f);
530 const float maxTurnAngle = std::clamp(params.getFloat("maxTurnAngle", realistic ? 16.f : 22.f), 2.f, 60.f);
531 const float maxCumulativeAngle = std::clamp(params.getFloat("maxCumulativeAngle", 58.f), 10.f, 85.f);
532 const float branchLengthFalloff = std::clamp(params.getFloat("branchLengthFalloff", 0.58f), 0.f, 0.9f);
533 const float branchRadiusFalloff = std::clamp(params.getFloat("branchRadiusFalloff", 0.50f), 0.f, 0.9f);
534 const float lowerLeafCoverage = std::clamp(params.getFloat("lowerLeafCoverage", 0.72f), 0.f, 1.f);
535 const float upperLeafCoverage = std::clamp(params.getFloat("upperLeafCoverage", 0.18f), 0.f, 1.f);
536 const int maxChildren = std::clamp(params.getInt("maxChildren", 2), 1, 4);
537 const ClusterSettings clusters{
538 .size = std::clamp(params.getFloat("clusterSize", 0.30f), 0.04f, 0.8f),
539 .leafScale = std::clamp(params.getFloat("clusterLeafScale", 0.85f), 0.1f, 3.f),
540 .spacing = std::clamp(params.getFloat("clusterSpacing", 0.80f), 0.25f, 3.f),
541 .separation = std::clamp(params.getFloat("clusterSeparation", 0.55f), 0.1f, 3.f),
542 .tilt = std::clamp(params.getFloat("clusterTilt", 26.f), 0.f, 80.f),
543 .planes = std::clamp(params.getInt("clusterPlanes", 10), 1, 24),
544 .caps = std::clamp(params.getInt("clusterCaps", 2), 0, 8),
545 .leavesPerPlane = std::clamp(params.getInt("clusterLeaves", 28), 1, 256),
546 .limit = std::clamp(params.getInt("clusterLimit", 120), 1, 512),
547 };
548 std::mt19937 rng(params.getSeed());
549 out.clear();
550
551 const V3 root{0.f, 0.f, 0.f};
552 const float trunkAzimuth = randomRange(rng, 0.f, 2.f * kPi);
553 const V3 trunkBend{std::cos(trunkAzimuth), 0.f, std::sin(trunkAzimuth)};
554 const StemPath trunk =
555 makeCurvedStem(root, {0.f, 1.f, 0.f}, trunkBend, height, trunkCurve * randomRange(rng, 0.72f, 1.28f),
556 curveBack * randomRange(rng, 0.72f, 1.28f), 0.f, curveSegments + 2);
557 addStem(out, trunk, trunkRadius, trunkRadius * 0.16f, sides);
558
559 std::vector<Tip> tips;
560 std::vector<Tip> foliageAnchors;
561 if (branchAlgorithm == "spaceColonization") {
562 const ColonizationSettings colonization{
563 .height = height,
564 .foliageStart = foliageStart,
565 .crownRadius = crownRadius,
566 .trunkRadius = trunkRadius,
567 .tropism = tropism,
568 .droop = droop * 0.45f,
569 .inertia = branchInertia,
570 .attractorCount = attractorCount,
571 .iterations = colonizationIterations,
572 .influenceRadius = influenceRadius,
573 .killRadius = killRadius,
574 .step = growthStep,
575 .maxTurnAngle = maxTurnAngle,
576 .maxCumulativeAngle = maxCumulativeAngle,
577 .lengthFalloff = branchLengthFalloff,
578 .radiusFalloff = branchRadiusFalloff,
579 .lowerLeafCoverage = lowerLeafCoverage,
580 .upperLeafCoverage = upperLeafCoverage,
581 .maxChildren = maxChildren,
582 .sides = sides,
583 };
584 growSpaceColonizedTree(out, trunk, rng, colonization, foliageAnchors);
585 } else {
586 for (int level = 0; level < branchLevels; ++level) {
587 const int count = std::max(2, branchCount - level * 2);
588 const float levelScale = std::pow(0.62f, float(level));
589 std::vector<Tip> next;
590 for (int i = 0; i < count; ++i) {
591 const float h = foliageStart + (1.f - foliageStart) * (float(i) + 0.35f) / float(count);
592 const float angle = float(i) * phyllotaxis + randomRange(rng, -0.20f, 0.20f) + level;
593 V3 start;
594 V3 parentDirection;
595 if (level == 0) {
596 samplePath(trunk, h, start, parentDirection);
597 } else {
598 const Tip& parent = tips[size_t(i) % tips.size()];
599 start = parent.p;
600 parentDirection = parent.dir;
601 }
602 V3 ringRight, ringForward;
603 basisFor(parentDirection, ringRight, ringForward);
604 const V3 radial = add(mul(ringRight, std::cos(angle)), mul(ringForward, std::sin(angle)));
605 const float levelAngle =
606 branchAngle - float(level) * 7.f + randomRange(rng, -angleVariation, angleVariation);
607 const float radians = std::clamp(levelAngle, 5.f, 88.f) * kPi / 180.f;
608 V3 dir = norm(add(mul(parentDirection, std::cos(radians)), mul(radial, std::sin(radians))));
609 dir = norm(add(dir, mul(V3{0.f, 1.f, 0.f}, apicalDominance * 0.24f * h)));
610 const float relativeHeight =
611 std::clamp((start.y / height - foliageStart) / (1.f - foliageStart), 0.f, 1.f);
612 const float lengthTaper = 1.f - branchLengthFalloff * relativeHeight;
613 const float radiusTaper = 1.f - branchRadiusFalloff * relativeHeight;
614 const float length = crownRadius * randomRange(rng, 0.72f, 1.18f) * lengthTaper * levelScale;
615 const float r = trunkRadius * (0.36f * radiusTaper) * levelScale;
616 V3 bendRight, bendForward;
617 basisFor(dir, bendRight, bendForward);
618 const V3 bendAxis =
619 norm(add(mul(bendRight, std::cos(angle + 1.1f)), mul(bendForward, std::sin(angle + 1.1f))));
620 const float flexibility = 1.f + float(level) * 0.38f;
621 const float downWeight = droop * (0.35f + 0.65f * (1.f - h)) * flexibility;
622 const float verticalAcceleration = tropism * (0.55f + apicalDominance * h) - downWeight;
623 const StemPath stem = makeCurvedStem(start, dir, bendAxis, length,
624 branchCurve * flexibility * randomRange(rng, 0.65f, 1.35f),
625 curveBack * 0.55f * flexibility * randomRange(rng, 0.55f, 1.25f),
626 verticalAcceleration, std::max(2, curveSegments - level));
627 addStem(out, stem, r, r * 0.22f, std::max(3, sides - level * 2));
628 const float coverage = lowerLeafCoverage + (upperLeafCoverage - lowerLeafCoverage) * relativeHeight;
629 const uint32_t coverageKey = params.getSeed() ^ uint32_t(level * 131 + i * 977);
630 if (hash01(coverageKey) < coverage && stem.points.size() > 3) {
631 const size_t middle = stem.points.size() / 2;
632 const V3 middleDirection = norm(sub(stem.points[middle], stem.points[middle - 1]));
633 foliageAnchors.push_back(
634 {stem.points[middle], middleDirection, levelScale * (0.72f + 0.28f * (1.f - relativeHeight))});
635 }
636 next.push_back({stem.points.back(), stem.endDirection, levelScale});
637 }
638 foliageAnchors.insert(foliageAnchors.end(), next.begin(), next.end());
639 tips.swap(next);
640 }
641 }
642
643 int clusterCount = 0;
644 if (leafMode == "cards") {
645 const int perTip = int(std::round((realistic ? 12.f : 6.f) * density));
646 for (const Tip& tip : foliageAnchors) {
647 for (int i = 0; i < perTip; ++i) {
648 V3 c = add(tip.p, {randomRange(rng, -1.f, 1.f) * crownRadius * 0.24f * tip.scale,
649 randomRange(rng, -0.25f, 0.55f) * crownRadius * tip.scale,
650 randomRange(rng, -1.f, 1.f) * crownRadius * 0.24f * tip.scale});
651 const V3 face =
652 norm({randomRange(rng, -1.f, 1.f), randomRange(rng, -0.2f, 0.8f), randomRange(rng, -1.f, 1.f)});
653 addLeafCard(out, c, face, leafSize * randomRange(rng, 0.72f, 1.25f), randomRange(rng, 0.f, kPi),
654 rng);
655 }
656 }
657 } else if (leafMode == "clusters" && density > 0.f) {
658 clusterCount =
659 addFoliageClusters(out, foliageAnchors, rng, crownRadius, leafSize, density, params.getSeed(), clusters);
660 } else if (leafMode == "canopy" && density > 0.f) {
661 // A canopy lobe belongs to a branch: center it just behind the terminal
662 // point so the branch visibly penetrates the foliage instead of ending in air.
663 int wanted = std::max(1, int(std::round(float(foliageAnchors.size()) * (0.28f + density * 0.62f))));
664 if (branchAlgorithm == "spaceColonization") wanted = int(foliageAnchors.size());
665 const int stride = std::max(1, int(foliageAnchors.size()) / wanted);
666 int emitted = 0;
667 for (int i = int(foliageAnchors.size()) - 1; i >= 0 && emitted < wanted; i -= stride) {
668 const Tip& tip = foliageAnchors[size_t(i)];
669 const float lobeMin = branchAlgorithm == "spaceColonization" ? 0.14f : 0.27f;
670 const float lobeMax = branchAlgorithm == "spaceColonization" ? 0.22f : 0.40f;
671 const float r = crownRadius * randomRange(rng, lobeMin, lobeMax) * (0.72f + 0.28f * density) *
672 (0.58f + 0.42f * tip.scale);
673 V3 c = sub(tip.p, mul(tip.dir, r * 0.22f));
674 c.y += r * randomRange(rng, 0.08f, 0.22f);
675 addCanopyBlob(out, c, {r, r * randomRange(rng, 0.72f, 1.15f), r}, realistic ? 8 : 4, realistic ? 12 : 7);
676 ++emitted;
677 }
678 }
679
680 out.setMeta("recipe", "mesh.tree");
681 out.setMeta("style", style);
682 out.setMeta("leafMode", leafMode);
683 out.setMeta("branchAlgorithm", branchAlgorithm);
684 out.setMeta("seed", std::to_string(params.getSeed()));
685 if (leafMode == "clusters") out.setMeta("clusters", std::to_string(clusterCount));
686 if (out.empty()) {
687 error = "mesh.tree: generated an empty mesh";
688 return false;
689 }
690 return true;
691}
692
693} // namespace eve::procgen
double value
Duration start
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
int tip
Definition AnimSmr.cpp:121
int root
Definition AnimSmr.cpp:119
const std::string & s
float halfW
float halfH
float twist
float uv
Vec3 tangent
Definition CaveMesh.cpp:80
float length
Definition CaveMesh.cpp:94
float nx
float planes[6][4]
glm::vec4 p[6]
std::uint32_t alive
uint32_t i1
Definition Grass.cpp:61
uint32_t i2
Definition Grass.cpp:61
uint32_t i0
Definition Grass.cpp:61
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
double r
float v
HexVec3 up
HexVec3 right
std::int32_t c
int h
std::uint32_t height
std::string local
std::array< float, 3 > scale
std::int32_t parent
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
std::vector< std::int32_t > order
std::vector< BvhNode > nodes
Texture * normal
int level
std::string error
Definition Package.cpp:60
float radius
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
const RoadNode * node
RoadLaneDirection direction
bool found
std::uint32_t count
std::map< Cell, int > best
Heightmap curve
TerrainThermalSettings settings
int spacing
float maxTurnAngle
Definition TreeMesh.cpp:315
int leavesPerPlane
Definition TreeMesh.cpp:163
float upperLeafCoverage
Definition TreeMesh.cpp:320
int limit
Definition TreeMesh.cpp:164
float lengthFalloff
Definition TreeMesh.cpp:317
float budHeight
Definition TreeMesh.cpp:299
float inertia
Definition TreeMesh.cpp:309
float radiusFalloff
Definition TreeMesh.cpp:318
int iterations
Definition TreeMesh.cpp:311
float lowerLeafCoverage
Definition TreeMesh.cpp:319
float tropism
Definition TreeMesh.cpp:307
float separation
Definition TreeMesh.cpp:159
float maxCumulativeAngle
Definition TreeMesh.cpp:316
int maxChildren
Definition TreeMesh.cpp:321
float vigor
Definition TreeMesh.cpp:298
int attractorCount
Definition TreeMesh.cpp:310
float killRadius
Definition TreeMesh.cpp:313
float crownRadius
Definition TreeMesh.cpp:305
V3 endDirection
Definition TreeMesh.cpp:245
float remainingLength
Definition TreeMesh.cpp:297
float step
Definition TreeMesh.cpp:314
float tilt
Definition TreeMesh.cpp:160
int sides
Definition TreeMesh.cpp:322
int children
Definition TreeMesh.cpp:295
V3 heading
Definition TreeMesh.cpp:292
float trunkRadius
Definition TreeMesh.cpp:306
float size
Definition TreeMesh.cpp:156
int descendants
Definition TreeMesh.cpp:294
float leafScale
Definition TreeMesh.cpp:157
float foliageStart
Definition TreeMesh.cpp:304
int caps
Definition TreeMesh.cpp:162
V3 dir
Definition TreeMesh.cpp:150
float droop
Definition TreeMesh.cpp:308
float influenceRadius
Definition TreeMesh.cpp:312
std::uint32_t depth
std::vector< double > phi
glm::vec3 point
float angle
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:19
void setMeta(const std::string &key, const std::string &value)
Sets the meta.
bool empty() const
Empty.
void clear()
Clears .
Definition MeshBuild.cpp:8
Owning, typed generation parameters.
Definition Params.h:27
std::vector< ParamSpec > params
std::vector< double > forward(const Policy &p, const Observation &o)
Forward.
Definition Learning.h:65
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
int addFoliageCluster(MeshBuild &out, const FoliageClusterDesc &desc)
Append one blue-noise leaf cluster to a mesh.
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
float distanceSquared(float ax, float ay, float bx, float by)
int axis(int64_t a, size_t rank)
Axis.
WidgetDesc card(std::vector< WidgetDesc > children, std::string id)
Bordered surface container with editor-friendly padding.
Definition Widget.cpp:502
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.
Definition Widget.cpp:679