载入中...
搜索中...
未找到
BushMesh.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
14// tex.foliage atlas layout:
15// u in [0, 0.22] brown bark for twigs (same look as tree trunks),
16// u in [0.24, 0.50] opaque leafy fill for ellipsoid blobs,
17// u in [0.52, 1] 2×3 = 6 leaf-card panels (each 8–16 blue-noise ovate stamps).
18constexpr float kBarkUMin = 0.00f;
19constexpr float kBarkUMax = 0.22f;
20constexpr float kBlobUMin = 0.24f;
21constexpr float kBlobUMax = 0.50f;
22constexpr float kCardAtlasU0 = 0.52f;
23constexpr float kCardAtlasU1 = 1.00f;
24constexpr int kLeafCardCols = 2;
25constexpr int kLeafCardRows = 3;
26constexpr int kLeafCardPanels = kLeafCardCols * kLeafCardRows;
27
28struct V3 {
29 float x = 0.f, y = 0.f, z = 0.f;
30};
31
32struct FoliageLobe {
35};
36
37V3 add(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
38V3 sub(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
39V3 mul(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
40float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
41V3 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}; }
42V3 norm(V3 a) {
43 const float n = std::sqrt(std::max(1e-12f, dot(a, a)));
44 return mul(a, 1.f / n);
45}
46
47float randomRange(std::mt19937 &rng, float lo, float hi) { return std::uniform_real_distribution<float>(lo, hi)(rng); }
48
49float random01(std::mt19937 &rng) { return randomRange(rng, 0.f, 1.f); }
50
51void basisFor(V3 axis, V3 &right, V3 &forward) {
52 axis = norm(axis);
53 const V3 helper = std::fabs(axis.y) < 0.92f ? V3{0.f, 1.f, 0.f} : V3{1.f, 0.f, 0.f};
54 right = norm(cross(helper, axis));
55 forward = norm(cross(axis, right));
56}
57
58// Closed ellipsoid. The per-vertex normal is the analytic ellipsoid normal so the
59// bush shades smoothly under directional light. UVs fall inside [uMin, uMax].
60void addEllipsoidBlob(MeshBuild &out, V3 c, V3 r, int rings, int sides, float uMin, float uMax,
61 float phase = 0.f, float irregularity = 0.55f) {
62 const uint32_t base = uint32_t(out.getVertexCount());
63 for (int y = 0; y <= rings; ++y) {
64 const float v = float(y) / float(rings);
65 const float phi = v * kPi;
66 for (int x = 0; x < sides; ++x) {
67 const float u = float(x) / float(sides);
68 const float theta = u * 2.f * kPi;
69 const V3 n = {std::sin(phi) * std::cos(theta), std::cos(phi), std::sin(phi) * std::sin(theta)};
70 // Two low-frequency harmonics break the unmistakable primitive-sphere
71 // silhouette without producing noisy, faceted normals. Keep both poles
72 // fixed so neighbouring lobes still close cleanly.
73 const float equator = std::sin(phi);
74 const float swell = 1.f + equator * irregularity *
75 (0.14f * std::sin(theta * 3.f + phase) +
76 0.065f * std::sin(theta * 5.f - phase * 0.7f));
77 const V3 p = {c.x + n.x * r.x * swell, c.y + n.y * r.y, c.z + n.z * r.z * swell};
78 // Ellipsoid normal = N/R divided by its length (finite since radii > 0).
79 const V3 normal = norm({n.x / r.x, n.y / r.y, n.z / r.z});
80 out.addVertex(p.x, p.y, p.z, normal.x, normal.y, normal.z, uMin + u * (uMax - uMin), v);
81 }
82 }
83 for (int y = 0; y < rings; ++y) {
84 for (int x = 0; x < sides; ++x) {
85 const int nx = (x + 1) % sides;
86 const uint32_t a = base + uint32_t(y * sides + x);
87 const uint32_t b = base + uint32_t(y * sides + nx);
88 const uint32_t c = base + uint32_t((y + 1) * sides + x);
89 const uint32_t d = base + uint32_t((y + 1) * sides + nx);
90 out.addTriangle(a, b, c);
91 out.addTriangle(b, d, c);
92 }
93 }
94}
95
96// Open tapered cylinder (a woody twig). Vertices use the bark UV half.
97void addTwig(MeshBuild &out, V3 a, V3 b, float r0, float r1, int sides, float uMin, float uMax) {
98 const V3 axis = norm(sub(b, a));
99 V3 right, forward;
100 basisFor(axis, right, forward);
101 const uint32_t base = uint32_t(out.getVertexCount());
102 for (int ring = 0; ring < 2; ++ring) {
103 const V3 center = ring ? b : a;
104 const float radius = ring ? r1 : r0;
105 for (int i = 0; i < sides; ++i) {
106 const float t = float(i) / float(sides);
107 const float angle = t * 2.f * kPi;
108 const V3 radial = add(mul(right, std::cos(angle)), mul(forward, std::sin(angle)));
109 const V3 p = add(center, mul(radial, radius));
110 out.addVertex(p.x, p.y, p.z, radial.x, radial.y, radial.z, uMin + t * (uMax - uMin), float(ring));
111 }
112 }
113 for (int i = 0; i < sides; ++i) {
114 const uint32_t n = uint32_t((i + 1) % sides);
115 const uint32_t i0 = base + uint32_t(i), i1 = base + n;
116 const uint32_t i2 = base + uint32_t(sides) + uint32_t(i);
117 const uint32_t i3 = base + uint32_t(sides) + n;
118 out.addTriangle(i0, i2, i1);
119 out.addTriangle(i1, i2, i3);
120 }
121}
122
123// Leaf card: transparent quad sampling one of six atlas panels. Each panel holds
124// 8–16 solid ovate leaves placed with blue-noise spacing + random rotation.
125void addLeafCard(MeshBuild &out, std::mt19937 &rng, V3 c, V3 direction, float size) {
126 V3 right, up;
127 basisFor(norm(direction), right, up);
128 const float twist = randomRange(rng, 0.f, 2.f * kPi);
129 right = add(mul(right, std::cos(twist)), mul(up, std::sin(twist)));
130 up = norm(cross(norm(direction), right));
131 const V3 normal = norm(cross(right, up));
132 // Quad card 25% larger than the base leafSize.
133 const float card = size * 1.25f;
134 const float halfW = card * randomRange(rng, 0.55f, 0.72f);
135 const float halfH = card * randomRange(rng, 0.55f, 0.72f);
136 const V3 r = mul(right, halfW);
137 const V3 h = mul(up, halfH);
138 const V3 center = add(c, mul(normal, size * 0.04f));
139 const V3 points[4] = {sub(sub(center, r), h), add(sub(center, h), r), add(add(center, r), h),
140 add(sub(center, r), h)};
141
142 // Randomly assign one of the six pre-baked leaf-card panels.
143 const int panel = int(randomRange(rng, 0.f, float(kLeafCardPanels))) % kLeafCardPanels;
144 const int col = panel % kLeafCardCols;
145 const int row = panel / kLeafCardCols;
146 const float inset = 0.04f;
147 const float cellWU = (kCardAtlasU1 - kCardAtlasU0) / float(kLeafCardCols);
148 const float cellWV = 1.f / float(kLeafCardRows);
149 const float u0 = kCardAtlasU0 + (float(col) + inset) * cellWU;
150 const float u1 = kCardAtlasU0 + (float(col) + 1.f - inset) * cellWU;
151 const float v0 = (float(row) + inset) * cellWV;
152 const float v1 = (float(row) + 1.f - inset) * cellWV;
153 const bool flipU = random01(rng) > 0.5f;
154 const bool flipV = random01(rng) > 0.5f;
155 const float uv[4][2] = {
156 {flipU ? u1 : u0, flipV ? v1 : v0},
157 {flipU ? u0 : u1, flipV ? v1 : v0},
158 {flipU ? u0 : u1, flipV ? v0 : v1},
159 {flipU ? u1 : u0, flipV ? v0 : v1},
160 };
161 const uint32_t base = uint32_t(out.getVertexCount());
162 for (int i = 0; i < 4; ++i) {
163 out.addVertex(points[i].x, points[i].y, points[i].z, normal.x, normal.y, normal.z, uv[i][0],
164 uv[i][1]);
165 }
166 out.addTriangle(base, base + 1, base + 2);
167 out.addTriangle(base, base + 2, base + 3);
168 out.addTriangle(base + 2, base + 1, base);
169 out.addTriangle(base + 3, base + 2, base);
170}
171
172} // namespace
173
174bool generateBushMesh(const Params &params, MeshBuild &out, std::string &error) {
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";
179 return false;
180 }
181 if (leafMode != "blobs" && leafMode != "cards" && leafMode != "mixed" && leafMode != "none") {
182 error = "mesh.bush: leafMode must be blobs|cards|mixed|none";
183 return false;
184 }
185
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));
188 const float halfW = width * 0.5f;
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";
200
201 std::mt19937 rng(params.getSeed());
202 out.clear();
203
204 // Build the woody skeleton first. Each stem bends outward and forks once;
205 // foliage is then placed around these endpoints instead of being scattered
206 // independently. The clear lower third keeps the branch structure readable.
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);
211 const V3 root{std::cos(angle) * halfW * 0.05f, 0.02f, std::sin(angle) * halfW * 0.05f};
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,
225 kBarkUMax);
226 crownAnchors.push_back(tip);
227 crownAnchors.push_back(forkTip);
228 }
229
230 std::vector<FoliageLobe> foliageLobes;
231 foliageLobes.reserve(size_t(blobs) + 1u);
232
233 // Cluster squashed lobes under a dome silhouette so the bush reads as one
234 // rounded mound rather than a set of disconnected balls.
235 // leafMode "cards" skips opaque blobs so only ovate leaf geometry remains.
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;
250 center.y += randomRange(rng, -0.04f, 0.08f) * height;
251 center.z += randomRange(rng, -0.10f, 0.10f) * halfW;
252 }
253 const V3 radius{rx, ry, rz};
254 addEllipsoidBlob(out, center, radius, rings, sides, kBlobUMin, kBlobUMax,
255 randomRange(rng, 0.f, 2.f * kPi), irregularity);
256 foliageLobes.push_back({center, radius});
257 }
258 // Always cap the top so the dome has no gap at its peak.
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") {
265 // Anchor cards on virtual lobes so the canopy still forms a mound.
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)];
278 }
279 foliageLobes.push_back({center, {rx, ry, rz}});
280 }
281 foliageLobes.push_back({{0.f, height * (sphere ? 0.62f : 0.72f), 0.f},
282 {halfW * 0.18f * lobeScale, height * 0.20f, halfW * 0.18f * lobeScale}});
283 }
284
285 // Leaf cards across the canopy (+50% count vs prior 14× density packing).
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));
298 }
299 }
300
301 out.setMeta("recipe", "mesh.bush");
302 out.setMeta("style", style);
303 out.setMeta("leafMode", leafMode);
304 out.setMeta("seed", std::to_string(params.getSeed()));
305 if (out.empty()) {
306 error = "mesh.bush: generated an empty mesh";
307 return false;
308 }
309 return true;
310}
311
312} // namespace eve::procgen
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 cy
Definition CardTypes.cpp:34
float uv
float phase
Definition CaveMesh.cpp:58
float irregularity
Definition CaveMesh.cpp:57
float nx
float ny
glm::vec4 p[6]
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
double r
float v
HexVec3 up
HexVec3 right
std::int32_t c
int h
std::uint32_t height
std::uint32_t width
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
Texture * normal
std::string error
Definition Package.cpp:60
float radius
std::shared_ptr< const std::vector< glm::vec2 > > points
float d
float t
RoadLaneDirection direction
int sides
Definition TreeMesh.cpp:322
float size
Definition TreeMesh.cpp:156
std::vector< double > phi
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
bool generateBushMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic procedural small bush. Registered as the mesh.bush recipe.
Definition BushMesh.cpp:174
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