载入中...
搜索中...
未找到
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// Shared with the tree atlas: the left half is bark/stems, the right half foliage.
15// A bush is almost entirely foliage, so lobes/cards sample the right half while
16// the few emergent twigs sample the left half.
17constexpr float kFoliageUMin = 0.55f;
18constexpr float kFoliageUMax = 1.0f;
19constexpr float kBarkUMin = 0.0f;
20constexpr float kBarkUMax = 0.45f;
21
22struct V3 {
23 float x = 0.f, y = 0.f, z = 0.f;
24};
25
26V3 add(V3 a, V3 b) { return {a.x + b.x, a.y + b.y, a.z + b.z}; }
27V3 sub(V3 a, V3 b) { return {a.x - b.x, a.y - b.y, a.z - b.z}; }
28V3 mul(V3 a, float s) { return {a.x * s, a.y * s, a.z * s}; }
29float dot(V3 a, V3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; }
30V3 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}; }
31V3 norm(V3 a) {
32 const float n = std::sqrt(std::max(1e-12f, dot(a, a)));
33 return mul(a, 1.f / n);
34}
35
36float randomRange(std::mt19937 &rng, float lo, float hi) { return std::uniform_real_distribution<float>(lo, hi)(rng); }
37
38float random01(std::mt19937 &rng) { return randomRange(rng, 0.f, 1.f); }
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
47// Closed ellipsoid. The per-vertex normal is the analytic ellipsoid normal so the
48// bush shades smoothly under directional light. UVs fall inside [uMin, uMax].
49void addEllipsoidBlob(MeshBuild &out, V3 c, V3 r, int rings, int sides, float uMin, float uMax) {
50 const uint32_t base = uint32_t(out.getVertexCount());
51 for (int y = 0; y <= rings; ++y) {
52 const float v = float(y) / float(rings);
53 const float phi = v * kPi;
54 for (int x = 0; x < sides; ++x) {
55 const float u = float(x) / float(sides);
56 const float theta = u * 2.f * kPi;
57 const V3 n = {std::sin(phi) * std::cos(theta), std::cos(phi), std::sin(phi) * std::sin(theta)};
58 const V3 p = {c.x + n.x * r.x, c.y + n.y * r.y, c.z + n.z * r.z};
59 // Ellipsoid normal = N/R divided by its length (finite since radii > 0).
60 const V3 normal = norm({n.x / r.x, n.y / r.y, n.z / r.z});
61 out.addVertex(p.x, p.y, p.z, normal.x, normal.y, normal.z, uMin + u * (uMax - uMin), v);
62 }
63 }
64 for (int y = 0; y < rings; ++y) {
65 for (int x = 0; x < sides; ++x) {
66 const int nx = (x + 1) % sides;
67 const uint32_t a = base + uint32_t(y * sides + x);
68 const uint32_t b = base + uint32_t(y * sides + nx);
69 const uint32_t c = base + uint32_t((y + 1) * sides + x);
70 const uint32_t d = base + uint32_t((y + 1) * sides + nx);
71 out.addTriangle(a, c, b);
72 out.addTriangle(b, c, d);
73 }
74 }
75}
76
77// Open tapered cylinder (a woody twig). Vertices use the bark UV half.
78void addTwig(MeshBuild &out, V3 a, V3 b, float r0, float r1, int sides, float uMin, float uMax) {
79 const V3 axis = norm(sub(b, a));
80 V3 right, forward;
81 basisFor(axis, right, forward);
82 const uint32_t base = uint32_t(out.getVertexCount());
83 for (int ring = 0; ring < 2; ++ring) {
84 const V3 center = ring ? b : a;
85 const float radius = ring ? r1 : r0;
86 for (int i = 0; i < sides; ++i) {
87 const float t = float(i) / float(sides);
88 const float angle = t * 2.f * kPi;
89 const V3 radial = add(mul(right, std::cos(angle)), mul(forward, std::sin(angle)));
90 const V3 p = add(center, mul(radial, radius));
91 out.addVertex(p.x, p.y, p.z, radial.x, radial.y, radial.z, uMin + t * (uMax - uMin), float(ring));
92 }
93 }
94 for (int i = 0; i < sides; ++i) {
95 const uint32_t n = uint32_t((i + 1) % sides);
96 const uint32_t i0 = base + uint32_t(i), i1 = base + n;
97 const uint32_t i2 = base + uint32_t(sides) + uint32_t(i);
98 const uint32_t i3 = base + uint32_t(sides) + n;
99 out.addTriangle(i0, i2, i1);
100 out.addTriangle(i1, i2, i3);
101 }
102}
103
104// A two-triangle leaf card. Facing the given direction with a random twist.
105void addLeafCard(MeshBuild &out, std::mt19937 &rng, V3 c, V3 direction, float size, float uMin, float uMax) {
106 V3 right, up;
107 basisFor(norm(direction), right, up);
108 const float twist = randomRange(rng, 0.f, 2.f * kPi);
109 right = add(mul(right, std::cos(twist)), mul(up, std::sin(twist)));
110 up = norm(cross(norm(direction), right));
111 const V3 r = mul(right, size * 0.5f), u = mul(up, size);
112 const V3 normal = norm(cross(right, up));
113 const uint32_t base = uint32_t(out.getVertexCount());
114 const V3 points[4] = {sub(sub(c, r), u), add(sub(c, u), r), add(add(c, r), u), add(sub(c, r), u)};
115 const float uv[4][2] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
116 for (int i = 0; i < 4; ++i)
117 out.addVertex(points[i].x, points[i].y, points[i].z, normal.x, normal.y, normal.z,
118 uMin + uv[i][0] * (uMax - uMin), uv[i][1]);
119 out.addTriangle(base, base + 1, base + 2);
120 out.addTriangle(base, base + 2, base + 3);
121 out.addTriangle(base + 2, base + 1, base);
122 out.addTriangle(base + 3, base + 2, base);
123}
124
125} // namespace
126
127bool generateBushMesh(const Params &params, MeshBuild &out, std::string &error) {
128 const std::string style = params.getString("style", "mound");
129 const std::string leafMode = params.getString("leafMode", "mixed");
130 if (style != "mound" && style != "sphere") {
131 error = "mesh.bush: style must be mound|sphere";
132 return false;
133 }
134 if (leafMode != "blobs" && leafMode != "cards" && leafMode != "mixed" && leafMode != "none") {
135 error = "mesh.bush: leafMode must be blobs|cards|mixed|none";
136 return false;
137 }
138
139 const float height = std::max(0.3f, params.getFloat("height", 1.4f));
140 const float width = std::max(0.4f, params.getFloat("width", 2.2f));
141 const float halfW = width * 0.5f;
142 const int blobs = std::clamp(params.getInt("blobs", 9), 1, 40);
143 const int rings = std::clamp(params.getInt("rings", 3), 2, 10);
144 const int sides = std::clamp(params.getInt("radialSegments", 7), 4, 24);
145 const float density = std::clamp(params.getFloat("leafDensity", 0.62f), 0.f, 1.f);
146 const float leafSize = std::max(0.02f, params.getFloat("leafSize", height * 0.16f));
147 const int twigs = std::clamp(params.getInt("twigs", 4), 0, 16);
148 const float twigLen = std::max(0.05f, params.getFloat("twigLength", height * 0.30f));
149 const bool sphere = style == "sphere";
150
151 std::mt19937 rng(params.getSeed());
152 out.clear();
153
154 // Cluster squashed lobes under a dome silhouette so the bush reads as one
155 // rounded mound rather than a set of disconnected balls.
156 for (int i = 0; i < blobs; ++i) {
157 const float radial = halfW * std::sqrt(random01(rng));
158 const float theta = randomRange(rng, 0.f, 2.f * kPi);
159 const float heightFactor = 1.f - (radial / halfW) * (radial / halfW);
160 const float cy = height * (sphere ? 0.5f + 0.20f * random01(rng)
161 : heightFactor * (0.45f + 0.45f * random01(rng)));
162 const float rx = halfW * 0.30f * randomRange(rng, 0.70f, 1.25f);
163 const float ry = height * (sphere ? 0.24f : 0.28f) * randomRange(rng, 0.60f, 1.05f);
164 const float rz = rx * randomRange(rng, 0.80f, 1.20f);
165 const V3 center{std::cos(theta) * radial, cy, std::sin(theta) * radial};
166 addEllipsoidBlob(out, center, {rx, ry, rz}, rings, sides, kFoliageUMin, kFoliageUMax);
167 }
168 // Always cap the top so the dome has no gap at its peak.
169 const float topRx = halfW * 0.22f;
170 addEllipsoidBlob(out, {0.f, height * (sphere ? 0.62f : 0.72f), 0.f}, {topRx, height * 0.20f, topRx},
171 rings, sides, kFoliageUMin, kFoliageUMax);
172
173 // Emergent woody twigs plus a small foliage tuft on each tip.
174 const float twigR0 = width * 0.015f;
175 for (int i = 0; i < twigs; ++i) {
176 const float angle = float(i) * 2.399963f;
177 const float radial = halfW * randomRange(rng, 0.15f, 0.60f);
178 const V3 base{std::cos(angle) * radial, height * randomRange(rng, 0.10f, 0.45f),
179 std::sin(angle) * radial};
180 V3 dir = norm(add(V3{0.f, 1.f, 0.f}, {randomRange(rng, -0.35f, 0.35f), 0.f, randomRange(rng, -0.35f, 0.35f)}));
181 const float len = twigLen * randomRange(rng, 0.60f, 1.10f);
182 const V3 tip = add(base, mul(dir, len));
183 addTwig(out, base, tip, twigR0, twigR0 * 0.35f, std::max(3, sides - 3), kBarkUMin, kBarkUMax);
184 if (leafMode == "cards" || leafMode == "mixed") {
185 addEllipsoidBlob(out, tip, {leafSize * 0.8f, leafSize * 0.9f, leafSize * 0.8f}, 2, std::max(4, sides - 2),
186 kFoliageUMin, kFoliageUMax);
187 }
188 }
189
190 // Optional loose leaf cards across the canopy for a fuller look.
191 if (leafMode == "cards" || leafMode == "mixed") {
192 const int cards = std::max(1, int(std::round(float(blobs) * 3.5f * density)));
193 for (int i = 0; i < cards; ++i) {
194 const float radial = halfW * std::sqrt(random01(rng));
195 const float theta = randomRange(rng, 0.f, 2.f * kPi);
196 const float heightFactor = 1.f - (radial / halfW) * (radial / halfW);
197 const V3 c{std::cos(theta) * radial, height * heightFactor * randomRange(rng, 0.45f, 0.90f),
198 std::sin(theta) * radial};
199 const V3 face = norm({randomRange(rng, -1.f, 1.f), randomRange(rng, 0.2f, 0.9f),
200 randomRange(rng, -1.f, 1.f)});
201 addLeafCard(out, rng, c, face, leafSize * randomRange(rng, 0.70f, 1.20f), kFoliageUMin, kFoliageUMax);
202 }
203 }
204
205 out.setMeta("recipe", "mesh.bush");
206 out.setMeta("style", style);
207 out.setMeta("leafMode", leafMode);
208 out.setMeta("seed", std::to_string(params.getSeed()));
209 if (out.empty()) {
210 error = "mesh.bush: generated an empty mesh";
211 return false;
212 }
213 return true;
214}
215
216} // namespace eve::procgen
float cy
Definition CardTypes.cpp:32
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
std::string error
uint32_t a
uint32_t b
uint32_t c
Texture * normal
int width
glm::vec4 p[6]
int d
int v
int sides
Definition TreeMesh.cpp:204
std::vector< V3 > points
Definition TreeMesh.cpp:126
V3 dir
Definition TreeMesh.cpp:121
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 generateBushMesh(const Params &params, MeshBuild &out, std::string &error)
Build a deterministic procedural small bush. Registered as the mesh.bush recipe.
Definition BushMesh.cpp:127