175 const std::string style =
params.getString(
"style",
"mound");
176 const std::string leafMode =
params.getString(
"leafMode",
"mixed");
177 if (style !=
"mound" && style !=
"sphere") {
178 error =
"mesh.bush: style must be mound|sphere";
181 if (leafMode !=
"blobs" && leafMode !=
"cards" && leafMode !=
"mixed" && leafMode !=
"none") {
182 error =
"mesh.bush: leafMode must be blobs|cards|mixed|none";
186 const float height = std::max(0.3f,
params.getFloat(
"height", 1.4f));
187 const float width = std::max(0.4f,
params.getFloat(
"width", 2.2f));
189 const int blobs = std::clamp(
params.getInt(
"blobs", 9), 1, 40);
190 const int rings = std::clamp(
params.getInt(
"rings", 3), 2, 10);
191 const int sides = std::clamp(
params.getInt(
"radialSegments", 7), 4, 24);
192 const float density = std::clamp(
params.getFloat(
"leafDensity", 0.62f), 0.f, 1.f);
193 const float leafSize = std::max(0.02f,
params.getFloat(
"leafSize",
height * 0.16f));
194 const float lobeScale = std::clamp(
params.getFloat(
"lobeScale", 0.68f), 0.35f, 1.25f);
195 const float irregularity = std::clamp(
params.getFloat(
"irregularity", 0.62f), 0.f, 1.f);
196 const int twigs = std::clamp(
params.getInt(
"twigs", 4), 0, 16);
197 const float twigLen = std::max(0.05f,
params.getFloat(
"twigLength",
height * 0.30f));
198 const float twigScale = std::clamp(twigLen / (
height * 0.30f), 0.35f, 1.65f);
199 const bool sphere = style ==
"sphere";
201 std::mt19937 rng(
params.getSeed());
207 std::vector<V3> crownAnchors;
208 const float branchR =
width * 0.022f;
209 for (
int i = 0; i < twigs; ++i) {
210 const float angle = float(i) * 2.399963f + randomRange(rng, -0.12f, 0.12f);
212 const V3 joint{std::cos(
angle) *
halfW * randomRange(rng, 0.22f, 0.34f) * twigScale,
213 height * randomRange(rng, 0.30f, 0.43f) * twigScale,
214 std::sin(
angle) *
halfW * randomRange(rng, 0.22f, 0.34f) * twigScale};
215 const float reach =
halfW * randomRange(rng, 0.58f, 0.76f) * twigScale;
216 const V3
tip{std::cos(
angle) * reach,
height * randomRange(rng, 0.58f, 0.78f) * twigScale,
217 std::sin(
angle) * reach};
218 const float forkAngle =
angle + (i & 1 ? 0.38f : -0.38f);
219 const V3 forkTip{joint.x + std::cos(forkAngle) *
halfW * randomRange(rng, 0.28f, 0.40f) * twigScale,
220 joint.y +
height * randomRange(rng, 0.22f, 0.33f) * twigScale,
221 joint.z + std::sin(forkAngle) *
halfW * randomRange(rng, 0.28f, 0.40f) * twigScale};
222 addTwig(out,
root, joint, branchR * 1.35f, branchR, std::max(5,
sides - 2), kBarkUMin, kBarkUMax);
223 addTwig(out, joint,
tip, branchR, branchR * 0.38f, std::max(4,
sides - 3), kBarkUMin, kBarkUMax);
224 addTwig(out, joint, forkTip, branchR * 0.78f, branchR * 0.28f, std::max(4,
sides - 3), kBarkUMin,
226 crownAnchors.push_back(
tip);
227 crownAnchors.push_back(forkTip);
230 std::vector<FoliageLobe> foliageLobes;
231 foliageLobes.reserve(
size_t(blobs) + 1u);
236 if (leafMode ==
"blobs" || leafMode ==
"mixed") {
237 for (
int i = 0; i < blobs; ++i) {
238 const float radial =
halfW * 0.58f * std::sqrt(random01(rng));
239 const float theta = randomRange(rng, 0.f, 2.f * kPi);
240 const float heightFactor = 1.f - (radial /
halfW) * (radial /
halfW);
241 const float cy =
height * (sphere ? 0.5f + 0.20f * random01(rng)
242 : 0.18f + heightFactor * (0.34f + 0.34f * random01(rng)));
243 const float rx =
halfW * 0.29f * lobeScale * randomRange(rng, 0.82f, 1.16f);
244 const float ry =
height * (sphere ? 0.21f : 0.22f) * lobeScale * randomRange(rng, 0.65f, 1.02f);
245 const float rz = rx * randomRange(rng, 0.80f, 1.20f);
246 V3
center{std::cos(theta) * radial,
cy, std::sin(theta) * radial};
247 if (!crownAnchors.empty() && i <
int(crownAnchors.size())) {
248 center = crownAnchors[size_t(i)];
249 center.x += randomRange(rng, -0.10f, 0.10f) *
halfW;
251 center.z += randomRange(rng, -0.10f, 0.10f) *
halfW;
253 const V3
radius{rx, ry, rz};
259 const float topRx =
halfW * 0.18f * lobeScale;
260 addEllipsoidBlob(out, {0.f,
height * (sphere ? 0.62f : 0.72f), 0.f}, {topRx,
height * 0.20f, topRx},
261 rings,
sides, kBlobUMin, kBlobUMax, randomRange(rng, 0.f, 2.f * kPi),
irregularity);
262 foliageLobes.push_back({{0.f,
height * (sphere ? 0.62f : 0.72f), 0.f},
263 {topRx,
height * 0.20f, topRx}});
264 }
else if (leafMode ==
"cards") {
266 for (
int i = 0; i < blobs; ++i) {
267 const float radial =
halfW * 0.58f * std::sqrt(random01(rng));
268 const float theta = randomRange(rng, 0.f, 2.f * kPi);
269 const float heightFactor = 1.f - (radial /
halfW) * (radial /
halfW);
270 const float cy =
height * (sphere ? 0.5f + 0.20f * random01(rng)
271 : 0.18f + heightFactor * (0.34f + 0.34f * random01(rng)));
272 const float rx =
halfW * 0.29f * lobeScale * randomRange(rng, 0.82f, 1.16f);
273 const float ry =
height * (sphere ? 0.21f : 0.22f) * lobeScale * randomRange(rng, 0.65f, 1.02f);
274 const float rz = rx * randomRange(rng, 0.80f, 1.20f);
275 V3
center{std::cos(theta) * radial,
cy, std::sin(theta) * radial};
276 if (!crownAnchors.empty() && i <
int(crownAnchors.size())) {
277 center = crownAnchors[size_t(i)];
279 foliageLobes.push_back({
center, {rx, ry, rz}});
281 foliageLobes.push_back({{0.f,
height * (sphere ? 0.62f : 0.72f), 0.f},
286 if (leafMode ==
"cards" || leafMode ==
"mixed") {
287 const int cards = std::max(1,
int(std::round(
float(blobs) * 21.f * density)));
288 for (
int i = 0; i < cards; ++i) {
289 const FoliageLobe &lobe = foliageLobes[size_t(i) % foliageLobes.size()];
290 const float theta = randomRange(rng, 0.f, 2.f * kPi);
291 const float ny = randomRange(rng, -0.45f, 1.f);
292 const float radial = std::sqrt(std::max(0.f, 1.f -
ny *
ny));
293 const V3 face = norm({std::cos(theta) * radial,
ny, std::sin(theta) * radial});
294 const V3
c{lobe.center.x + face.x * lobe.radius.x * 1.05f,
295 lobe.center.y + face.y * lobe.radius.y * 1.05f,
296 lobe.center.z + face.z * lobe.radius.z * 1.05f};
297 addLeafCard(out, rng,
c, face, leafSize * randomRange(rng, 0.85f, 1.35f));
301 out.
setMeta(
"recipe",
"mesh.bush");
303 out.
setMeta(
"leafMode", leafMode);
306 error =
"mesh.bush: generated an empty mesh";