12constexpr float kPi = 3.14159265358979323846f;
15 float x = 0.f,
y = 0.f,
z = 0.f;
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}; }
25 const float n = std::sqrt(std::max(1e-12f, dot(
a,
a)));
26 return mul(
a, 1.f /
n);
29float randomRange(std::mt19937& rng,
float lo,
float hi) {
return std::uniform_real_distribution<float>(lo, hi)(rng); }
31float hash01(uint32_t
value) {
37 return float(
value & 0x00ffffffu) / float(0x01000000u);
40void basisFor(V3 axis, V3& right, V3& forward) {
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));
47void addTaperedCylinder(MeshBuild& out, V3
a, V3
b,
float r0,
float r1,
int sides) {
48 const V3 axis = norm(sub(
b,
a));
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));
61 out.addVertex(
p.x,
p.y,
p.z, radial.x, radial.y, radial.z, t * 0.45f,
float(ring));
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);
74void addLeafCard(MeshBuild& out, V3
c, V3 direction,
float size,
float twist) {
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)
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);
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;
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);
106 for (
int y = 0;
y < rings; ++
y) {
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);
130StemPath makeCurvedStem(V3 start, V3 direction, V3 bendAxis,
float length,
float curve,
float curveBack,
131 float verticalAcceleration,
int segments) {
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);
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);
146 path.endDirection = norm(sub(path.points.back(), path.points[path.points.size() - 2]));
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);
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)]));
184struct ColonizationSettings {
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;
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;
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}));
224 std::vector<ColonyNode> nodes;
227 const int seedCount = 9;
228 for (
int i = 0; i < seedCount; ++i) {
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});
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;
258 float best = influenceSquared;
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) {
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)
283 directionSums[nearest] = add(directionSums[nearest], norm(sub(attractors[ai], nodes[nearest].
p)));
284 ++directionCounts[nearest];
287 if (liveAttractors == 0)
break;
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)
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}));
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)));
306 direction = nodes[ni].dir;
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)
314 norm(add(mul(nodes[ni].
heading, cumulativeTurnCos), mul(norm(tangent), cumulativeTurnSin)));
316 direction = nodes[ni].heading;
318 }
else if (direction.y < -0.05f) {
320 direction = norm({direction.x, -0.05f, direction.z});
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;
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;
335 if (additions.empty())
break;
336 nodes.insert(nodes.end(), additions.begin(), additions.end());
339 for (
int i =
int(nodes.size()) - 1; i >= 0; --i) {
340 const int parent = nodes[size_t(i)].parent;
342 nodes[size_t(
parent)].descendants += nodes[size_t(i)].descendants;
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));
355 foliageAnchors.push_back({
node.p,
node.dir, 0.42f + 0.22f *
node.vigor});
356 }
else if (
node.depth >= 2 && (
node.depth % 2) == 0) {
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)});
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";
377 if (leafMode !=
"cards" && leafMode !=
"canopy" && leafMode !=
"none") {
378 error =
"mesh.tree: leafMode must be cards|canopy|none";
381 if (branchAlgorithm !=
"weberPenn" && branchAlgorithm !=
"spaceColonization") {
382 error =
"mesh.tree: branchAlgorithm must be weberPenn|spaceColonization";
386 const bool realistic = style ==
"realistic";
387 const float height = std::max(0.5f, params.
getFloat(
"height", 6.f));
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);
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);
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);
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);
419 std::mt19937 rng(params.
getSeed());
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);
430 std::vector<Tip> tips;
431 std::vector<Tip> foliageAnchors;
432 if (branchAlgorithm ==
"spaceColonization") {
433 const ColonizationSettings colonization{
439 .droop =
droop * 0.45f,
440 .inertia = branchInertia,
442 .iterations = colonizationIterations,
448 .lengthFalloff = branchLengthFalloff,
449 .radiusFalloff = branchRadiusFalloff,
455 growSpaceColonizedTree(out, trunk, rng, colonization, foliageAnchors);
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) {
463 const float angle = float(i) * phyllotaxis + randomRange(rng, -0.20f, 0.20f) + level;
467 samplePath(trunk,
h, start, parentDirection);
469 const Tip&
parent = tips[size_t(i) % tips.size()];
471 parentDirection =
parent.dir;
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 =
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);
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));
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))});
507 next.push_back({stem.points.back(), stem.endDirection, levelScale});
509 foliageAnchors.insert(foliageAnchors.end(), next.begin(), next.end());
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});
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));
526 }
else if (leafMode ==
"canopy" && density > 0.f) {
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);
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);
546 out.
setMeta(
"recipe",
"mesh.tree");
548 out.
setMeta(
"leafMode", leafMode);
549 out.
setMeta(
"branchAlgorithm", branchAlgorithm);
552 error =
"mesh.tree: generated an empty mesh";
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
void setMeta(const std::string &key, const std::string &value)
Generation parameters. Algorithm-specific keys live in values as strings (no overloads; typed setters...
float getFloat(const std::string &key, float defaultValue) const
std::string getString(const std::string &key, const std::string &defaultValue) const
int getInt(const std::string &key, int defaultValue) const
bool generateTreeMesh(const Params ¶ms, MeshBuild &out, std::string &error)
Build a deterministic procedural tree. Registered as the mesh.tree recipe.
NodeDesc node(std::string id, std::vector< NodeDesc > children, std::string name)
std::vector< NodeDesc > children