载入中...
搜索中...
未找到
TreeMesh.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <cstdint>
6#include <random>
7#include <vector>
8
9namespace eve::procgen {
10namespace {
11
12constexpr float kPi = 3.14159265358979323846f;
13
14struct V3 {
15 float x = 0.f, y = 0.f, z = 0.f;
16};
17
18V3 add(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
19V3 sub(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
20V3 mul(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
21float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
22float distanceSquared(V3 a, V3 b) { return dot(sub(a, b), sub(a, b)); }
23V3 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}; }
24V3 norm(V3 a) {
25 const float n = std::sqrt(std::max(1e-12f, dot(a, a)));
26 return mul(a, 1.f / n);
27}
28
29float randomRange(std::mt19937& rng, float lo, float hi) { return std::uniform_real_distribution<float>(lo, hi)(rng); }
30
31float hash01(uint32_t value) {
32 value ^= value >> 16;
33 value *= 0x7feb352du;
34 value ^= value >> 15;
35 value *= 0x846ca68bu;
36 value ^= value >> 16;
37 return float(value & 0x00ffffffu) / float(0x01000000u);
38}
39
40void basisFor(V3 axis, V3& right, V3& forward) {
41 axis = norm(axis);
42 const V3 helper = std::fabs(axis.y) < 0.92f ? V3{0.f, 1.f, 0.f} : V3{1.f, 0.f, 0.f};
43 right = norm(cross(helper, axis));
44 forward = norm(cross(axis, right));
45}
46
47void addTaperedCylinder(MeshBuild& out, V3 a, V3 b, float r0, float r1, int sides) {
48 const V3 axis = norm(sub(b, a));
49 V3 right, forward;
50 basisFor(axis, right, forward);
51 const uint32_t base = uint32_t(out.getVertexCount());
52 for (int ring = 0; ring < 2; ++ring) {
53 const V3 center = ring ? b : a;
54 const float radius = ring ? r1 : r0;
55 for (int i = 0; i < sides; ++i) {
56 const float t = float(i) / float(sides);
57 const float angle = t * 2.f * kPi;
58 const V3 radial = add(mul(right, std::cos(angle)), mul(forward, std::sin(angle)));
59 const V3 p = add(center, mul(radial, radius));
60 // Left side of the tree atlas is bark; foliage uses the right side.
61 out.addVertex(p.x, p.y, p.z, radial.x, radial.y, radial.z, t * 0.45f, float(ring));
62 }
63 }
64 for (int i = 0; i < sides; ++i) {
65 const uint32_t n = uint32_t((i + 1) % sides);
66 const uint32_t i0 = base + uint32_t(i), i1 = base + n;
67 const uint32_t i2 = base + uint32_t(sides) + uint32_t(i);
68 const uint32_t i3 = base + uint32_t(sides) + n;
69 out.addTriangle(i0, i2, i1);
70 out.addTriangle(i1, i2, i3);
71 }
72}
73
74void addLeafCard(MeshBuild& out, V3 c, V3 direction, float size, float twist) {
75 V3 right, up;
76 basisFor(norm(direction), right, up);
77 right = add(mul(right, std::cos(twist)), mul(up, std::sin(twist)));
78 up = norm(cross(norm(direction), right));
79 const V3 r = mul(right, size * 0.5f), u = mul(up, size);
80 const V3 normal = norm(cross(right, up));
81 const uint32_t base = uint32_t(out.getVertexCount());
82 const V3 points[4] = {sub(sub(c, r), u), add(sub(c, u), r), add(add(c, r), u), add(sub(c, r), u)};
83 const float uv[4][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
84 for (int i = 0; i < 4; ++i)
85 out.addVertex(points[i].x, points[i].y, points[i].z, normal.x, normal.y, normal.z, 0.55f + uv[i][0] * 0.45f,
86 uv[i][1]);
87 out.addTriangle(base, base + 1, base + 2);
88 out.addTriangle(base, base + 2, base + 3);
89 out.addTriangle(base + 2, base + 1, base);
90 out.addTriangle(base + 3, base + 2, base);
91}
92
93void addCanopyBlob(MeshBuild& out, V3 center, V3 radius, int rings, int sides) {
94 const uint32_t base = uint32_t(out.getVertexCount());
95 for (int y = 0; y <= rings; ++y) {
96 const float v = float(y) / float(rings);
97 const float phi = v * kPi;
98 for (int x = 0; x < sides; ++x) {
99 const float u = float(x) / float(sides);
100 const float theta = u * 2.f * kPi;
101 const V3 n = {std::sin(phi) * std::cos(theta), std::cos(phi), std::sin(phi) * std::sin(theta)};
102 const V3 p = {center.x + n.x * radius.x, center.y + n.y * radius.y, center.z + n.z * radius.z};
103 out.addVertex(p.x, p.y, p.z, n.x, n.y, n.z, 0.55f + u * 0.45f, v);
104 }
105 }
106 for (int y = 0; y < rings; ++y) {
107 for (int x = 0; x < sides; ++x) {
108 const int nx = (x + 1) % sides;
109 const uint32_t a = base + uint32_t(y * sides + x);
110 const uint32_t b = base + uint32_t(y * sides + nx);
111 const uint32_t c = base + uint32_t((y + 1) * sides + x);
112 const uint32_t d = base + uint32_t((y + 1) * sides + nx);
113 out.addTriangle(a, c, b);
114 out.addTriangle(b, c, d);
115 }
116 }
117}
118
119struct Tip {
120 V3 p;
121 V3 dir;
122 float scale;
123};
124
125struct StemPath {
126 std::vector<V3> points;
128};
129
130StemPath makeCurvedStem(V3 start, V3 direction, V3 bendAxis, float length, float curve, float curveBack,
131 float verticalAcceleration, int segments) {
132 StemPath path;
133 path.points.reserve(size_t(segments + 1));
134 direction = norm(direction);
135 bendAxis = norm(sub(bendAxis, mul(direction, dot(bendAxis, direction))));
136 const V3 up{0.f, 1.f, 0.f};
137 for (int i = 0; i <= segments; ++i) {
138 const float t = float(i) / float(segments);
139 // Both offsets have zero slope at the branch base, avoiding an immediate kink.
140 const float primary = curve * t * t;
141 const float back = curveBack * std::sin(2.f * kPi * t) * t * (1.f - t);
142 const V3 p = add(start, add(mul(direction, length * t), add(mul(bendAxis, length * (primary + back)),
143 mul(up, length * verticalAcceleration * t * t))));
144 path.points.push_back(p);
145 }
146 path.endDirection = norm(sub(path.points.back(), path.points[path.points.size() - 2]));
147 return path;
148}
149
150void addStem(MeshBuild& out, const StemPath& path, float r0, float r1, int sides) {
151 const int segmentCount = int(path.points.size()) - 1;
152 for (int i = 0; i < segmentCount; ++i) {
153 const float t0 = float(i) / float(segmentCount);
154 const float t1 = float(i + 1) / float(segmentCount);
155 const float a = std::pow(t0, 1.25f);
156 const float b = std::pow(t1, 1.25f);
157 addTaperedCylinder(out, path.points[size_t(i)], path.points[size_t(i + 1)], r0 + (r1 - r0) * a,
158 r0 + (r1 - r0) * b, sides);
159 }
160}
161
162void samplePath(const StemPath& path, float t, V3& point, V3& direction) {
163 const int segmentCount = int(path.points.size()) - 1;
164 const float x = std::clamp(t, 0.f, 1.f) * float(segmentCount);
165 const int i = std::min(int(x), segmentCount - 1);
166 const float local = x - float(i);
167 point = add(path.points[size_t(i)], mul(sub(path.points[size_t(i + 1)], path.points[size_t(i)]), local));
168 direction = norm(sub(path.points[size_t(i + 1)], path.points[size_t(i)]));
169}
170
171struct ColonyNode {
172 V3 p;
173 V3 dir;
174 V3 heading{0.f, 1.f, 0.f};
175 int parent = -1;
176 int descendants = 1;
177 int children = 0;
178 int depth = 0;
179 float remainingLength = 0.f;
180 float vigor = 1.f;
181 float budHeight = 0.f;
182};
183
184struct ColonizationSettings {
185 float height;
189 float tropism;
190 float droop;
191 float inertia;
196 float step;
204 int sides;
205};
206
207void growSpaceColonizedTree(MeshBuild& out, const StemPath& trunk, std::mt19937& rng,
208 const ColonizationSettings& settings, std::vector<Tip>& foliageAnchors) {
209 V3 crownCenter, ignoredDirection;
210 samplePath(trunk, settings.foliageStart + (1.f - settings.foliageStart) * 0.53f, crownCenter, ignoredDirection);
211 const float crownHalfHeight = settings.height * (1.f - settings.foliageStart) * 0.52f;
212
213 std::vector<V3> attractors;
214 attractors.reserve(size_t(settings.attractorCount));
215 while (int(attractors.size()) < settings.attractorCount) {
216 const V3 q{randomRange(rng, -1.f, 1.f), randomRange(rng, -1.f, 1.f), randomRange(rng, -1.f, 1.f)};
217 if (dot(q, q) > 1.f) continue;
218 // A modest upward bias avoids a perfectly symmetric, balloon-like crown.
219 attractors.push_back(add(crownCenter, {q.x * settings.crownRadius,
220 q.y * crownHalfHeight + 0.12f * crownHalfHeight * (1.f - q.y * q.y),
221 q.z * settings.crownRadius}));
222 }
223
224 std::vector<ColonyNode> nodes;
225 // Multiple trunk buds provide the primary scaffold. Colonization then shapes and
226 // splits those limbs; starting from a single bud produces a shrub-like fan.
227 const int seedCount = 9;
228 for (int i = 0; i < seedCount; ++i) {
229 V3 p, dir;
230 const float t =
231 settings.foliageStart + (1.f - settings.foliageStart) * (0.08f + 0.78f * float(i) / float(seedCount - 1));
232 samplePath(trunk, t, p, dir);
233 const float crownHeight = float(i) / float(seedCount - 1);
234 const float lengthScale = 1.f - settings.lengthFalloff * crownHeight;
235 const float vigor = 1.f - settings.radiusFalloff * crownHeight;
236 nodes.push_back({p, dir, dir, -1, 1, 0, 0, settings.crownRadius * 1.35f * lengthScale, vigor, crownHeight});
237 }
238
239 std::vector<uint8_t> alive(size_t(settings.attractorCount), 1);
240 const float influenceSquared = settings.influenceRadius * settings.influenceRadius;
241 const float killSquared = settings.killRadius * settings.killRadius;
242 const float separationSquared = settings.step * settings.step * 0.22f;
243 const float maxTurn = settings.maxTurnAngle * kPi / 180.f;
244 const float maxTurnCos = std::cos(maxTurn);
245 const float maxTurnSin = std::sin(maxTurn);
246 const float cumulativeTurn = settings.maxCumulativeAngle * kPi / 180.f;
247 const float cumulativeTurnCos = std::cos(cumulativeTurn);
248 const float cumulativeTurnSin = std::sin(cumulativeTurn);
249 for (int iteration = 0; iteration < settings.iterations; ++iteration) {
250 const size_t nodeCount = nodes.size();
251 std::vector<V3> directionSums(nodeCount);
252 std::vector<int> directionCounts(nodeCount, 0);
253 int liveAttractors = 0;
254 for (size_t ai = 0; ai < attractors.size(); ++ai) {
255 if (!alive[ai]) continue;
256 ++liveAttractors;
257 size_t nearest = 0;
258 float best = influenceSquared;
259 bool found = false;
260 for (size_t ni = 0; ni < nodeCount; ++ni) {
261 const V3 toward = sub(attractors[ai], nodes[ni].p);
262 const float d = dot(toward, toward);
263 if (d < killSquared) {
264 alive[ai] = 0;
265 found = false;
266 break;
267 }
268 // Once a shoot has left the trunk, attraction from behind may not
269 // reverse it. This is the main distinction between a branch and a vine.
270 if (nodes[ni].depth > 0) {
271 const V3 attractionDirection = norm(toward);
272 if (dot(attractionDirection, nodes[ni].dir) < -0.08f ||
273 dot(attractionDirection, nodes[ni].heading) < 0.04f)
274 continue;
275 }
276 if (d < best) {
277 best = d;
278 nearest = ni;
279 found = true;
280 }
281 }
282 if (found) {
283 directionSums[nearest] = add(directionSums[nearest], norm(sub(attractors[ai], nodes[nearest].p)));
284 ++directionCounts[nearest];
285 }
286 }
287 if (liveAttractors == 0) break;
288
289 std::vector<ColonyNode> additions;
290 for (size_t ni = 0; ni < nodeCount; ++ni) {
291 if (directionCounts[ni] == 0 || nodes[ni].children >= settings.maxChildren ||
292 nodes[ni].remainingLength < settings.step)
293 continue;
294 V3 direction = norm(
295 add(mul(directionSums[ni], 1.f / float(directionCounts[ni])), mul(nodes[ni].dir, settings.inertia)));
296 const float vertical = settings.tropism - settings.droop * std::max(0.f, 1.f - direction.y);
297 direction = norm(add(direction, {0.f, vertical, 0.f}));
298 // Clamp curvature per growth step. Linear blending alone can still turn
299 // sharply when the remaining attractors move to the other side of a tip.
300 const float alignment = std::clamp(dot(nodes[ni].dir, direction), -1.f, 1.f);
301 if (nodes[ni].depth > 0 && alignment < maxTurnCos) {
302 V3 tangent = sub(direction, mul(nodes[ni].dir, alignment));
303 if (dot(tangent, tangent) > 1e-8f)
304 direction = norm(add(mul(nodes[ni].dir, maxTurnCos), mul(norm(tangent), maxTurnSin)));
305 else
306 direction = nodes[ni].dir;
307 }
308 if (nodes[ni].depth > 0) {
309 const float headingAlignment = std::clamp(dot(nodes[ni].heading, direction), -1.f, 1.f);
310 if (headingAlignment < cumulativeTurnCos) {
311 V3 tangent = sub(direction, mul(nodes[ni].heading, headingAlignment));
312 if (dot(tangent, tangent) > 1e-8f)
313 direction =
314 norm(add(mul(nodes[ni].heading, cumulativeTurnCos), mul(norm(tangent), cumulativeTurnSin)));
315 else
316 direction = nodes[ni].heading;
317 }
318 } else if (direction.y < -0.05f) {
319 // Primary limbs may spread horizontally, but do not launch downward.
320 direction = norm({direction.x, -0.05f, direction.z});
321 }
322 const V3 candidate = add(nodes[ni].p, mul(direction, settings.step));
323 bool separated = true;
324 for (size_t oi = 0; oi < nodes.size() && separated; ++oi)
325 separated = distanceSquared(candidate, nodes[oi].p) >= separationSquared;
326 for (size_t oi = 0; oi < additions.size() && separated; ++oi)
327 separated = distanceSquared(candidate, additions[oi].p) >= separationSquared;
328 if (separated) {
329 const V3 heading = nodes[ni].depth == 0 ? direction : nodes[ni].heading;
330 additions.push_back({candidate, direction, heading, int(ni), 1, 0, nodes[ni].depth + 1,
331 nodes[ni].remainingLength - settings.step, nodes[ni].vigor, nodes[ni].budHeight});
332 ++nodes[ni].children;
333 }
334 }
335 if (additions.empty()) break;
336 nodes.insert(nodes.end(), additions.begin(), additions.end());
337 }
338
339 for (int i = int(nodes.size()) - 1; i >= 0; --i) {
340 const int parent = nodes[size_t(i)].parent;
341 if (parent >= 0) {
342 nodes[size_t(parent)].descendants += nodes[size_t(i)].descendants;
343 }
344 }
345 const float twigRadius = settings.trunkRadius * 0.055f;
346 for (size_t i = 0; i < nodes.size(); ++i) {
347 const ColonyNode& node = nodes[i];
348 if (node.parent < 0) continue;
349 const ColonyNode& parent = nodes[size_t(node.parent)];
350 const float r0 = std::min(settings.trunkRadius * 0.34f * parent.vigor,
351 twigRadius * parent.vigor * (1.f + std::sqrt(float(parent.descendants))));
352 const float r1 = std::min(r0 * 0.92f, twigRadius * node.vigor * (1.f + std::sqrt(float(node.descendants))));
353 addTaperedCylinder(out, parent.p, node.p, r0, r1, std::max(3, settings.sides - 2));
354 if (node.children == 0) {
355 foliageAnchors.push_back({node.p, node.dir, 0.42f + 0.22f * node.vigor});
356 } else if (node.depth >= 2 && (node.depth % 2) == 0) {
357 // Older lower limbs carry foliage along the branch, whereas young upper
358 // shoots keep most foliage near their tips.
359 const float coverage =
360 settings.lowerLeafCoverage + (settings.upperLeafCoverage - settings.lowerLeafCoverage) * node.budHeight;
361 if (hash01(uint32_t(i) * 747796405u + 2891336453u) < coverage)
362 foliageAnchors.push_back({node.p, node.dir, 0.48f + 0.20f * (1.f - node.budHeight)});
363 }
364 }
365}
366
367} // namespace
368
369bool generateTreeMesh(const Params& params, MeshBuild& out, std::string& error) {
370 const std::string style = params.getString("style", "lowpoly");
371 const std::string leafMode = params.getString("leafMode", "cards");
372 const std::string branchAlgorithm = params.getString("branchAlgorithm", "weberPenn");
373 if (style != "lowpoly" && style != "realistic") {
374 error = "mesh.tree: style must be lowpoly|realistic";
375 return false;
376 }
377 if (leafMode != "cards" && leafMode != "canopy" && leafMode != "none") {
378 error = "mesh.tree: leafMode must be cards|canopy|none";
379 return false;
380 }
381 if (branchAlgorithm != "weberPenn" && branchAlgorithm != "spaceColonization") {
382 error = "mesh.tree: branchAlgorithm must be weberPenn|spaceColonization";
383 return false;
384 }
385
386 const bool realistic = style == "realistic";
387 const float height = std::max(0.5f, params.getFloat("height", 6.f));
388 const float trunkRadius = std::max(0.02f, params.getFloat("trunkRadius", height * 0.055f));
389 const float crownRadius = std::max(0.1f, params.getFloat("crownRadius", height * 0.34f));
390 const float leafSize = std::max(0.02f, params.getFloat("leafSize", height * 0.075f));
391 const float density = std::clamp(params.getFloat("leafDensity", 0.65f), 0.f, 1.f);
392 const float foliageStart = std::clamp(params.getFloat("foliageStart", 0.35f), 0.1f, 0.9f);
393 const int branchLevels = std::clamp(params.getInt("branchLevels", realistic ? 3 : 2), 1, 5);
394 const int branchCount = std::clamp(params.getInt("branchCount", realistic ? 9 : 6), 2, 20);
395 const int sides = std::clamp(params.getInt("radialSegments", realistic ? 10 : 6), 3, 24);
396 const int curveSegments = std::clamp(params.getInt("curveSegments", realistic ? 9 : 5), 2, 20);
397 const float trunkCurve = std::clamp(params.getFloat("trunkCurve", 0.10f), 0.f, 0.45f);
398 const float curveBack = std::clamp(params.getFloat("curveBack", 0.16f), -0.5f, 0.5f);
399 const float branchCurve = std::clamp(params.getFloat("branchCurve", 0.13f), 0.f, 0.5f);
400 const float branchAngle = std::clamp(params.getFloat("branchAngle", 62.f), 5.f, 88.f);
401 const float angleVariation = std::clamp(params.getFloat("branchAngleVariation", 12.f), 0.f, 40.f);
402 const float phyllotaxis = params.getFloat("phyllotaxis", 137.5f) * kPi / 180.f;
403 const float tropism = std::clamp(params.getFloat("tropism", 0.22f), -0.5f, 0.8f);
404 const float droop = std::clamp(params.getFloat("droop", 0.18f), 0.f, 0.8f);
405 const float apicalDominance = std::clamp(params.getFloat("apicalDominance", 0.62f), 0.f, 1.f);
406 const int attractorCount = std::clamp(params.getInt("attractorCount", realistic ? 180 : 80), 12, 1200);
407 const int colonizationIterations = std::clamp(params.getInt("colonizationIterations", realistic ? 46 : 30), 4, 160);
408 const float influenceRadius = std::max(0.05f, params.getFloat("influenceRadius", crownRadius * 1.08f));
409 const float killRadius = std::max(0.01f, params.getFloat("killRadius", crownRadius * 0.13f));
410 const float growthStep = std::max(0.01f, params.getFloat("growthStep", crownRadius * (realistic ? 0.105f : 0.14f)));
411 const float branchInertia = std::clamp(params.getFloat("branchInertia", 1.20f), 0.f, 4.f);
412 const float maxTurnAngle = std::clamp(params.getFloat("maxTurnAngle", realistic ? 16.f : 22.f), 2.f, 60.f);
413 const float maxCumulativeAngle = std::clamp(params.getFloat("maxCumulativeAngle", 58.f), 10.f, 85.f);
414 const float branchLengthFalloff = std::clamp(params.getFloat("branchLengthFalloff", 0.58f), 0.f, 0.9f);
415 const float branchRadiusFalloff = std::clamp(params.getFloat("branchRadiusFalloff", 0.50f), 0.f, 0.9f);
416 const float lowerLeafCoverage = std::clamp(params.getFloat("lowerLeafCoverage", 0.72f), 0.f, 1.f);
417 const float upperLeafCoverage = std::clamp(params.getFloat("upperLeafCoverage", 0.18f), 0.f, 1.f);
418 const int maxChildren = std::clamp(params.getInt("maxChildren", 2), 1, 4);
419 std::mt19937 rng(params.getSeed());
420 out.clear();
421
422 const V3 root{0.f, 0.f, 0.f};
423 const float trunkAzimuth = randomRange(rng, 0.f, 2.f * kPi);
424 const V3 trunkBend{std::cos(trunkAzimuth), 0.f, std::sin(trunkAzimuth)};
425 const StemPath trunk =
426 makeCurvedStem(root, {0.f, 1.f, 0.f}, trunkBend, height, trunkCurve * randomRange(rng, 0.72f, 1.28f),
427 curveBack * randomRange(rng, 0.72f, 1.28f), 0.f, curveSegments + 2);
428 addStem(out, trunk, trunkRadius, trunkRadius * 0.16f, sides);
429
430 std::vector<Tip> tips;
431 std::vector<Tip> foliageAnchors;
432 if (branchAlgorithm == "spaceColonization") {
433 const ColonizationSettings colonization{
434 .height = height,
435 .foliageStart = foliageStart,
436 .crownRadius = crownRadius,
437 .trunkRadius = trunkRadius,
438 .tropism = tropism,
439 .droop = droop * 0.45f,
440 .inertia = branchInertia,
441 .attractorCount = attractorCount,
442 .iterations = colonizationIterations,
443 .influenceRadius = influenceRadius,
444 .killRadius = killRadius,
445 .step = growthStep,
446 .maxTurnAngle = maxTurnAngle,
447 .maxCumulativeAngle = maxCumulativeAngle,
448 .lengthFalloff = branchLengthFalloff,
449 .radiusFalloff = branchRadiusFalloff,
450 .lowerLeafCoverage = lowerLeafCoverage,
451 .upperLeafCoverage = upperLeafCoverage,
452 .maxChildren = maxChildren,
453 .sides = sides,
454 };
455 growSpaceColonizedTree(out, trunk, rng, colonization, foliageAnchors);
456 } else {
457 for (int level = 0; level < branchLevels; ++level) {
458 const int count = std::max(2, branchCount - level * 2);
459 const float levelScale = std::pow(0.62f, float(level));
460 std::vector<Tip> next;
461 for (int i = 0; i < count; ++i) {
462 const float h = foliageStart + (1.f - foliageStart) * (float(i) + 0.35f) / float(count);
463 const float angle = float(i) * phyllotaxis + randomRange(rng, -0.20f, 0.20f) + level;
464 V3 start;
465 V3 parentDirection;
466 if (level == 0) {
467 samplePath(trunk, h, start, parentDirection);
468 } else {
469 const Tip& parent = tips[size_t(i) % tips.size()];
470 start = parent.p;
471 parentDirection = parent.dir;
472 }
473 V3 ringRight, ringForward;
474 basisFor(parentDirection, ringRight, ringForward);
475 const V3 radial = add(mul(ringRight, std::cos(angle)), mul(ringForward, std::sin(angle)));
476 const float levelAngle =
477 branchAngle - float(level) * 7.f + randomRange(rng, -angleVariation, angleVariation);
478 const float radians = std::clamp(levelAngle, 5.f, 88.f) * kPi / 180.f;
479 V3 dir = norm(add(mul(parentDirection, std::cos(radians)), mul(radial, std::sin(radians))));
480 dir = norm(add(dir, mul(V3{0.f, 1.f, 0.f}, apicalDominance * 0.24f * h)));
481 const float relativeHeight =
482 std::clamp((start.y / height - foliageStart) / (1.f - foliageStart), 0.f, 1.f);
483 const float lengthTaper = 1.f - branchLengthFalloff * relativeHeight;
484 const float radiusTaper = 1.f - branchRadiusFalloff * relativeHeight;
485 const float length = crownRadius * randomRange(rng, 0.72f, 1.18f) * lengthTaper * levelScale;
486 const float r = trunkRadius * (0.36f * radiusTaper) * levelScale;
487 V3 bendRight, bendForward;
488 basisFor(dir, bendRight, bendForward);
489 const V3 bendAxis =
490 norm(add(mul(bendRight, std::cos(angle + 1.1f)), mul(bendForward, std::sin(angle + 1.1f))));
491 const float flexibility = 1.f + float(level) * 0.38f;
492 const float downWeight = droop * (0.35f + 0.65f * (1.f - h)) * flexibility;
493 const float verticalAcceleration = tropism * (0.55f + apicalDominance * h) - downWeight;
494 const StemPath stem = makeCurvedStem(start, dir, bendAxis, length,
495 branchCurve * flexibility * randomRange(rng, 0.65f, 1.35f),
496 curveBack * 0.55f * flexibility * randomRange(rng, 0.55f, 1.25f),
497 verticalAcceleration, std::max(2, curveSegments - level));
498 addStem(out, stem, r, r * 0.22f, std::max(3, sides - level * 2));
499 const float coverage = lowerLeafCoverage + (upperLeafCoverage - lowerLeafCoverage) * relativeHeight;
500 const uint32_t coverageKey = params.getSeed() ^ uint32_t(level * 131 + i * 977);
501 if (hash01(coverageKey) < coverage && stem.points.size() > 3) {
502 const size_t middle = stem.points.size() / 2;
503 const V3 middleDirection = norm(sub(stem.points[middle], stem.points[middle - 1]));
504 foliageAnchors.push_back(
505 {stem.points[middle], middleDirection, levelScale * (0.72f + 0.28f * (1.f - relativeHeight))});
506 }
507 next.push_back({stem.points.back(), stem.endDirection, levelScale});
508 }
509 foliageAnchors.insert(foliageAnchors.end(), next.begin(), next.end());
510 tips.swap(next);
511 }
512 }
513
514 if (leafMode == "cards") {
515 const int perTip = int(std::round((realistic ? 12.f : 6.f) * density));
516 for (const Tip& tip : foliageAnchors) {
517 for (int i = 0; i < perTip; ++i) {
518 V3 c = add(tip.p, {randomRange(rng, -1.f, 1.f) * crownRadius * 0.24f * tip.scale,
519 randomRange(rng, -0.25f, 0.55f) * crownRadius * tip.scale,
520 randomRange(rng, -1.f, 1.f) * crownRadius * 0.24f * tip.scale});
521 const V3 face =
522 norm({randomRange(rng, -1.f, 1.f), randomRange(rng, -0.2f, 0.8f), randomRange(rng, -1.f, 1.f)});
523 addLeafCard(out, c, face, leafSize * randomRange(rng, 0.72f, 1.25f), randomRange(rng, 0.f, kPi));
524 }
525 }
526 } else if (leafMode == "canopy" && density > 0.f) {
527 // A canopy lobe belongs to a branch: center it just behind the terminal
528 // point so the branch visibly penetrates the foliage instead of ending in air.
529 int wanted = std::max(1, int(std::round(float(foliageAnchors.size()) * (0.28f + density * 0.62f))));
530 if (branchAlgorithm == "spaceColonization") wanted = int(foliageAnchors.size());
531 const int stride = std::max(1, int(foliageAnchors.size()) / wanted);
532 int emitted = 0;
533 for (int i = int(foliageAnchors.size()) - 1; i >= 0 && emitted < wanted; i -= stride) {
534 const Tip& tip = foliageAnchors[size_t(i)];
535 const float lobeMin = branchAlgorithm == "spaceColonization" ? 0.14f : 0.27f;
536 const float lobeMax = branchAlgorithm == "spaceColonization" ? 0.22f : 0.40f;
537 const float r = crownRadius * randomRange(rng, lobeMin, lobeMax) * (0.72f + 0.28f * density) *
538 (0.58f + 0.42f * tip.scale);
539 V3 c = sub(tip.p, mul(tip.dir, r * 0.22f));
540 c.y += r * randomRange(rng, 0.08f, 0.22f);
541 addCanopyBlob(out, c, {r, r * randomRange(rng, 0.72f, 1.15f), r}, realistic ? 8 : 4, realistic ? 12 : 7);
542 ++emitted;
543 }
544 }
545
546 out.setMeta("recipe", "mesh.tree");
547 out.setMeta("style", style);
548 out.setMeta("leafMode", leafMode);
549 out.setMeta("branchAlgorithm", branchAlgorithm);
550 out.setMeta("seed", std::to_string(params.getSeed()));
551 if (out.empty()) {
552 error = "mesh.tree: generated an empty mesh";
553 return false;
554 }
555 return true;
556}
557
558} // namespace eve::procgen
std::string value
int y
Definition Grass.cpp:135
int z
Definition Grass.cpp:135
uint32_t i1
Definition Grass.cpp:62
uint32_t i2
Definition Grass.cpp:62
uint32_t i0
Definition Grass.cpp:62
float height
Definition Grass.cpp:235
float u
Definition Grass.cpp:234
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
std::string error
float depth
uint32_t a
uint32_t b
uint32_t c
Texture * normal
glm::vec4 p[6]
int d
int v
float maxTurnAngle
Definition TreeMesh.cpp:197
float upperLeafCoverage
Definition TreeMesh.cpp:202
float lengthFalloff
Definition TreeMesh.cpp:199
float budHeight
Definition TreeMesh.cpp:181
float inertia
Definition TreeMesh.cpp:191
float radiusFalloff
Definition TreeMesh.cpp:200
int iterations
Definition TreeMesh.cpp:193
float scale
Definition TreeMesh.cpp:122
float lowerLeafCoverage
Definition TreeMesh.cpp:201
float tropism
Definition TreeMesh.cpp:189
float maxCumulativeAngle
Definition TreeMesh.cpp:198
int parent
Definition TreeMesh.cpp:175
int maxChildren
Definition TreeMesh.cpp:203
float vigor
Definition TreeMesh.cpp:180
int attractorCount
Definition TreeMesh.cpp:192
float killRadius
Definition TreeMesh.cpp:195
float crownRadius
Definition TreeMesh.cpp:187
V3 endDirection
Definition TreeMesh.cpp:127
float remainingLength
Definition TreeMesh.cpp:179
float step
Definition TreeMesh.cpp:196
int sides
Definition TreeMesh.cpp:204
int children
Definition TreeMesh.cpp:177
V3 heading
Definition TreeMesh.cpp:174
float trunkRadius
Definition TreeMesh.cpp:188
int descendants
Definition TreeMesh.cpp:176
std::vector< V3 > points
Definition TreeMesh.cpp:126
float foliageStart
Definition TreeMesh.cpp:186
V3 dir
Definition TreeMesh.cpp:121
float droop
Definition TreeMesh.cpp:190
float influenceRadius
Definition TreeMesh.cpp:194
uint32_t s
Definition Weather.cpp:28
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:14
void setMeta(const std::string &key, const std::string &value)
Definition MeshBuild.cpp:81
Generation parameters. Algorithm-specific keys live in values as strings (no overloads; typed setters...
Definition Params.h:13
uint32_t getSeed() const
Definition Params.cpp:8
float getFloat(const std::string &key, float defaultValue) const
Definition Params.cpp:32
std::string getString(const std::string &key, const std::string &defaultValue) const
Definition Params.cpp:41
int getInt(const std::string &key, int defaultValue) const
Definition Params.cpp:23
bool generateTreeMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic procedural tree. Registered as the mesh.tree recipe.
Definition TreeMesh.cpp:369
NodeDesc node(std::string id, std::vector< NodeDesc > children, std::string name)
Definition NodeDesc.cpp:214
std::vector< NodeDesc > children
Definition NodeDesc.h:33