14constexpr float kPi = 3.14159265358979323846f;
17 float x = 0.f,
y = 0.f,
z = 0.f;
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; }
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}; }
27 const float n = std::sqrt(std::max(1e-12f,
dot(
a,
a)));
28 return mul(
a, 1.f /
n);
31float randomRange(std::mt19937& rng,
float lo,
float hi) {
return std::uniform_real_distribution<float>(lo, hi)(rng); }
33float hash01(uint32_t
value) {
39 return float(
value & 0x00ffffffu) / float(0x01000000u);
42void basisFor(V3 axis, V3&
right, V3& forward) {
44 const V3 helper = std::fabs(
axis.y) < 0.92f ? V3{0.f, 1.f, 0.f} : V3{1.f, 0.f, 0.f};
49void addTaperedCylinder(MeshBuild& out, V3
a, V3
b,
float r0,
float r1,
int sides) {
50 const V3
axis = norm(sub(
b,
a));
52 basisFor(axis,
right, forward);
53 const uint32_t base = uint32_t(out.getVertexCount());
54 for (
int ring = 0; ring < 2; ++ring) {
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)));
63 out.addVertex(
p.x,
p.y,
p.z, radial.x, radial.y, radial.z,
t * 0.45f,
float(ring));
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);
76void addLeafCard(MeshBuild& out, V3
c, V3
direction,
float size,
float twist, std::mt19937& rng) {
83 const float halfW =
card * randomRange(rng, 0.55f, 0.72f);
84 const float halfH =
card * randomRange(rng, 0.55f, 0.72f);
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},
113 const uint32_t base = uint32_t(out.getVertexCount());
114 for (
int i = 0; i < 4; ++i)
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);
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;
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)};
132 out.addVertex(
p.x,
p.y,
p.z,
n.x,
n.y,
n.z, 0.55f +
u * 0.45f,
v);
135 for (
int y = 0;
y < rings; ++
y) {
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);
155struct ClusterSettings {
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;
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))]);
200 const float spacing = std::max(0.05f,
settings.spacing * (1.45f - 0.75f * density));
202 std::vector<V3> accepted;
203 accepted.reserve(
size_t(
settings.limit));
205 for (
size_t k = 0; k <
order.size() && clusters <
settings.limit; ++k) {
207 bool blocked =
false;
208 for (
const V3& taken : accepted) {
214 if (blocked)
continue;
216 const float radius = baseRadius * (0.62f + 0.38f *
tip.scale) * randomRange(rng, 0.88f, 1.12f);
219 const V3
center = add(sub(
tip.p, mul(
tip.dir,
radius * 0.10f)), {0.f, radius * 0.06f, 0.f});
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;
227 cluster.leafSize = leafSize *
settings.leafScale * randomRange(rng, 0.85f, 1.15f);
231 cluster.tiltDegrees =
settings.tilt;
232 cluster.maxLeavesPerPlane =
settings.leavesPerPlane;
233 cluster.normalRounding = 1.f;
234 cluster.doubleSided =
true;
237 accepted.push_back(
tip.p);
249 float verticalAcceleration,
int segments) {
251 path.points.reserve(
size_t(segments + 1));
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);
258 const float primary =
curve *
t *
t;
259 const float back = curveBack * std::sin(2.f * kPi *
t) *
t * (1.f -
t);
261 mul(
up,
length * verticalAcceleration *
t *
t))));
262 path.points.push_back(
p);
264 path.endDirection = norm(sub(
path.points.back(),
path.points[
path.points.size() - 2]));
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);
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);
302struct ColonizationSettings {
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;
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;
337 attractors.push_back(add(crownCenter, {
q.x *
settings.crownRadius,
338 q.y * crownHalfHeight + 0.12f * crownHalfHeight * (1.f -
q.y *
q.y),
342 std::vector<ColonyNode>
nodes;
345 const int seedCount = 9;
346 for (
int i = 0; i < seedCount; ++i) {
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;
357 std::vector<uint8_t>
alive(
size_t(
settings.attractorCount), 1);
358 const float influenceSquared =
settings.influenceRadius *
settings.influenceRadius;
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);
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;
376 float best = influenceSquared;
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) {
389 const V3 attractionDirection = norm(toward);
390 if (
dot(attractionDirection,
nodes[ni].
dir) < -0.08f ||
401 directionSums[nearest] = add(directionSums[nearest], norm(sub(attractors[ai],
nodes[nearest].
p)));
402 ++directionCounts[nearest];
405 if (liveAttractors == 0)
break;
407 std::vector<ColonyNode> additions;
408 for (
size_t ni = 0; ni < nodeCount; ++ni) {
413 add(mul(directionSums[ni], 1.f /
float(directionCounts[ni])), mul(
nodes[ni].
dir,
settings.inertia)));
419 if (
nodes[ni].
depth > 0 && alignment < maxTurnCos) {
428 if (headingAlignment < cumulativeTurnCos) {
432 norm(add(mul(
nodes[ni].
heading, cumulativeTurnCos), mul(norm(
tangent), cumulativeTurnSin)));
441 bool separated =
true;
442 for (
size_t oi = 0; oi <
nodes.size() && separated; ++oi)
444 for (
size_t oi = 0; oi < additions.size() && separated; ++oi)
445 separated =
distanceSquared(candidate, additions[oi].
p) >= separationSquared;
450 ++
nodes[ni].children;
453 if (additions.empty())
break;
454 nodes.insert(
nodes.end(), additions.begin(), additions.end());
457 for (
int i =
int(
nodes.size()) - 1; i >= 0; --i) {
463 const float twigRadius =
settings.trunkRadius * 0.055f;
464 for (
size_t i = 0; i <
nodes.size(); ++i) {
466 if (
node.parent < 0)
continue;
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))));
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) {
477 const float coverage =
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)});
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";
495 if (leafMode !=
"cards" && leafMode !=
"clusters" && leafMode !=
"canopy" && leafMode !=
"none") {
496 error =
"mesh.tree: leafMode must be cards|clusters|canopy|none";
499 if (branchAlgorithm !=
"weberPenn" && branchAlgorithm !=
"spaceColonization") {
500 error =
"mesh.tree: branchAlgorithm must be weberPenn|spaceColonization";
504 const bool realistic = style ==
"realistic";
505 const float height = std::max(0.5f,
params.getFloat(
"height", 6.f));
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);
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);
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);
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),
548 std::mt19937 rng(
params.getSeed());
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);
559 std::vector<Tip> tips;
560 std::vector<Tip> foliageAnchors;
561 if (branchAlgorithm ==
"spaceColonization") {
562 const ColonizationSettings colonization{
568 .droop =
droop * 0.45f,
569 .inertia = branchInertia,
571 .iterations = colonizationIterations,
577 .lengthFalloff = branchLengthFalloff,
578 .radiusFalloff = branchRadiusFalloff,
584 growSpaceColonizedTree(out, trunk, rng, colonization, foliageAnchors);
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) {
592 const float angle = float(i) * phyllotaxis + randomRange(rng, -0.20f, 0.20f) +
level;
596 samplePath(trunk,
h,
start, parentDirection);
598 const Tip&
parent = tips[size_t(i) % tips.size()];
600 parentDirection =
parent.dir;
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 =
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);
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));
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))});
636 next.push_back({stem.points.back(), stem.endDirection, levelScale});
638 foliageAnchors.insert(foliageAnchors.end(), next.begin(), next.end());
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});
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),
657 }
else if (leafMode ==
"clusters" && density > 0.f) {
659 addFoliageClusters(out, foliageAnchors, rng,
crownRadius, leafSize, density,
params.getSeed(), clusters);
660 }
else if (leafMode ==
"canopy" && density > 0.f) {
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);
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);
680 out.
setMeta(
"recipe",
"mesh.tree");
682 out.
setMeta(
"leafMode", leafMode);
683 out.
setMeta(
"branchAlgorithm", branchAlgorithm);
685 if (leafMode ==
"clusters") out.
setMeta(
"clusters", std::to_string(clusterCount));
687 error =
"mesh.tree: generated an empty mesh";
std::array< double, 10 > q
std::array< float, 3 > scale
std::shared_ptr< const std::vector< glm::vec2 > > points
RoadLaneDirection direction
std::map< Cell, int > best
TerrainThermalSettings settings
std::vector< double > phi
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)
Sets the meta.
Owning, typed generation parameters.
std::vector< ParamSpec > params
std::vector< double > forward(const Policy &p, const Observation &o)
Forward.
double dot(const Vec2 &a, const Vec2 &b)
Dot.
double cross(const Vec2 &a, const Vec2 &b)
Cross.
int addFoliageCluster(MeshBuild &out, const FoliageClusterDesc &desc)
Append one blue-noise leaf cluster to a mesh.
bool generateTreeMesh(const Params ¶ms, MeshBuild &out, std::string &error)
Build a deterministic procedural tree. Registered as the mesh.tree recipe.
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.
WidgetDesc row(std::vector< WidgetDesc > children, std::string id)
Horizontal elastic layout row.