载入中...
搜索中...
未找到
RockMesh.cpp
浏览该文件的文档.
2
3#include <algorithm>
4#include <cmath>
5#include <cstdint>
6#include <unordered_map>
7#include <vector>
8
9namespace eve::procgen {
10namespace {
11
12struct Vec3 {
13 float x = 0.f, y = 0.f, z = 0.f;
14};
15
16struct Tri {
17 uint32_t a = 0, b = 0, c = 0;
18};
19
20struct ShapeProfile {
21 const char *name;
22 float axisX, axisY, axisZ;
23 float power;
24 float skewX, skewZ;
25 int cuts;
26 float cutDepth;
27};
28
29Vec3 normalize(Vec3 v) {
30 const float len = std::sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
31 if (len <= 1e-8f) return {0.f, 1.f, 0.f};
32 return {v.x / len, v.y / len, v.z / len};
33}
34
35Vec3 cross(Vec3 a, Vec3 b) {
36 return {a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z,
37 a.x * b.y - a.y * b.x};
38}
39
40inline float fade(float t) { return t * t * t * (t * (t * 6.f - 15.f) + 10.f); }
41inline float mix(float a, float b, float t) { return a + (b - a) * t; }
42
43float grad3(uint32_t h, float x, float y, float z) {
44 const uint32_t g = h & 15u;
45 const float u = g < 8 ? x : y;
46 const float v = g < 4 ? y : (g == 12 || g == 14 ? x : z);
47 return ((g & 1u) ? -u : u) + ((g & 2u) ? -v : v);
48}
49
50float noise3(float x, float y, float z, uint32_t seed) {
51 const int xi = int(std::floor(x)), yi = int(std::floor(y)), zi = int(std::floor(z));
52 const float xf = x - float(xi), yf = y - float(yi), zf = z - float(zi);
53 const float u = fade(xf), v = fade(yf), w = fade(zf);
54 auto hash = [&](int ix, int iy, int iz) {
55 return uint32_t(ix) * 374761393u + uint32_t(iy) * 668265263u +
56 uint32_t(iz) * 1274126177u + seed * 2246822519u;
57 };
58 const float n000 = grad3(hash(xi, yi, zi), xf, yf, zf);
59 const float n100 = grad3(hash(xi + 1, yi, zi), xf - 1.f, yf, zf);
60 const float n010 = grad3(hash(xi, yi + 1, zi), xf, yf - 1.f, zf);
61 const float n110 = grad3(hash(xi + 1, yi + 1, zi), xf - 1.f, yf - 1.f, zf);
62 const float n001 = grad3(hash(xi, yi, zi + 1), xf, yf, zf - 1.f);
63 const float n101 = grad3(hash(xi + 1, yi, zi + 1), xf - 1.f, yf, zf - 1.f);
64 const float n011 = grad3(hash(xi, yi + 1, zi + 1), xf, yf - 1.f, zf - 1.f);
65 const float n111 = grad3(hash(xi + 1, yi + 1, zi + 1), xf - 1.f, yf - 1.f, zf - 1.f);
66 return mix(mix(mix(n000, n100, u), mix(n010, n110, u), v),
67 mix(mix(n001, n101, u), mix(n011, n111, u), v), w);
68}
69
70float fbm3(float x, float y, float z, uint32_t seed, int octaves) {
71 float sum = 0.f, amplitude = 0.5f, frequency = 1.f, norm = 0.f;
72 for (int i = 0; i < octaves; ++i) {
73 sum += noise3(x * frequency, y * frequency, z * frequency,
74 seed + uint32_t(i) * 1013u) * amplitude;
75 norm += amplitude;
76 amplitude *= 0.5f;
77 frequency *= 2.f;
78 }
79 return norm > 0.f ? sum / norm : 0.f;
80}
81
82uint32_t nextRandom(uint32_t &state) {
83 state ^= state << 13u;
84 state ^= state >> 17u;
85 state ^= state << 5u;
86 return state;
87}
88
89float random01(uint32_t &state) {
90 return float(nextRandom(state) & 0x00ffffffu) / float(0x01000000u);
91}
92
93float randomSigned(uint32_t &state) { return random01(state) * 2.f - 1.f; }
94
95bool resolveShape(const std::string &requested, uint32_t seed, ShapeProfile &shape) {
96 static constexpr ShapeProfile profiles[] = {
97 {"boulder", 1.f, 1.f, 0.94f, 0.92f, 0.02f, -0.01f, 3, 0.13f},
98 {"slab", 1.28f, 0.62f, 1.02f, 0.74f, 0.08f, -0.03f, 5, 0.18f},
99 {"block", 1.04f, 0.90f, 0.96f, 0.46f, 0.03f, 0.02f, 6, 0.20f},
100 {"shard", 0.70f, 1.34f, 0.56f, 0.62f, 0.22f, -0.12f, 7, 0.24f},
101 };
102 std::string name = requested;
103 if (name == "mixed") name = profiles[seed % 4u].name;
104 for (const ShapeProfile &candidate : profiles) {
105 if (name == candidate.name) {
106 shape = candidate;
107 return true;
108 }
109 }
110 return false;
111}
112
113float shapedCoordinate(float value, float power) {
114 return std::copysign(std::pow(std::abs(value), power), value);
115}
116
117uint64_t edgeKey(uint32_t a, uint32_t b) {
118 if (a > b) std::swap(a, b);
119 return (uint64_t(a) << 32u) | uint64_t(b);
120}
121
122uint32_t midpoint(uint32_t a, uint32_t b, std::vector<Vec3> &vertices,
123 std::unordered_map<uint64_t, uint32_t> &cache) {
124 const uint64_t key = edgeKey(a, b);
125 const auto found = cache.find(key);
126 if (found != cache.end()) return found->second;
127 const Vec3 &va = vertices[a], &vb = vertices[b];
128 const uint32_t index = uint32_t(vertices.size());
129 vertices.push_back(normalize({(va.x + vb.x) * 0.5f, (va.y + vb.y) * 0.5f,
130 (va.z + vb.z) * 0.5f}));
131 cache.emplace(key, index);
132 return index;
133}
134
135void createIcosphere(int subdivisions, std::vector<Vec3> &vertices, std::vector<Tri> &triangles) {
136 constexpr float phi = 1.6180339887498948482f;
137 vertices = {{-1, phi, 0}, {1, phi, 0}, {-1, -phi, 0}, {1, -phi, 0},
138 {0, -1, phi}, {0, 1, phi}, {0, -1, -phi}, {0, 1, -phi},
139 {phi, 0, -1}, {phi, 0, 1}, {-phi, 0, -1}, {-phi, 0, 1}};
140 for (Vec3 &v : vertices) v = normalize(v);
141 triangles = {{0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
142 {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
143 {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
144 {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1}};
145
146 for (int level = 0; level < subdivisions; ++level) {
147 std::unordered_map<uint64_t, uint32_t> cache;
148 std::vector<Tri> next;
149 next.reserve(triangles.size() * 4);
150 for (const Tri &t : triangles) {
151 const uint32_t ab = midpoint(t.a, t.b, vertices, cache);
152 const uint32_t bc = midpoint(t.b, t.c, vertices, cache);
153 const uint32_t ca = midpoint(t.c, t.a, vertices, cache);
154 next.push_back({t.a, ab, ca});
155 next.push_back({t.b, bc, ab});
156 next.push_back({t.c, ca, bc});
157 next.push_back({ab, bc, ca});
158 }
159 triangles.swap(next);
160 }
161}
162
163} // namespace
164
165bool generateRockMesh(const Params &params, MeshBuild &out, std::string &error) {
166 const int subdivisions = params.getInt("subdivisions", 3);
167 if (subdivisions < 0 || subdivisions > 5) {
168 error = "mesh.rock: subdivisions must be in [0,5]";
169 return false;
170 }
171 const float radius = std::max(0.05f, params.getFloat("radius", 0.72f));
172 const float flattening = std::clamp(params.getFloat("flattening", 0.22f), 0.f, 0.7f);
173 const float angularity = std::clamp(params.getFloat("angularity", 0.38f), 0.f, 1.f);
174 const float erosion = std::clamp(params.getFloat("erosion", 0.16f), 0.f, 0.45f);
175 const float scale = std::max(0.25f, params.getFloat("scale", 2.4f));
176 const int octaves = std::clamp(params.getInt("octaves", 4), 1, 8);
177 const uint32_t seed = params.getSeed();
178 const std::string requestedShape = params.getString("baseShape", "mixed");
179 ShapeProfile shape{};
180 if (!resolveShape(requestedShape, seed, shape)) {
181 error = "mesh.rock: unknown baseShape '" + requestedShape +
182 "' (use mixed|boulder|slab|block|shard)";
183 return false;
184 }
185
186 uint32_t randomState = seed ^ 0x9e3779b9u;
187 const float variation = std::clamp(params.getFloat("variation", 0.42f), 0.f, 1.f);
188 shape.axisX *= 1.f + randomSigned(randomState) * 0.18f * variation;
189 shape.axisY *= 1.f + randomSigned(randomState) * 0.14f * variation;
190 shape.axisZ *= 1.f + randomSigned(randomState) * 0.18f * variation;
191 shape.power = std::clamp(shape.power + randomSigned(randomState) * 0.12f * variation,
192 0.32f, 1.1f);
193 shape.skewX += randomSigned(randomState) * 0.12f * variation;
194 shape.skewZ += randomSigned(randomState) * 0.12f * variation;
195 if (params.has("axisX")) shape.axisX = std::max(0.2f, params.getFloat("axisX", shape.axisX));
196 if (params.has("axisY")) shape.axisY = std::max(0.2f, params.getFloat("axisY", shape.axisY));
197 if (params.has("axisZ")) shape.axisZ = std::max(0.2f, params.getFloat("axisZ", shape.axisZ));
198 if (params.has("shapePower"))
199 shape.power = std::clamp(params.getFloat("shapePower", shape.power), 0.25f, 1.25f);
200 if (params.has("skewX")) shape.skewX = params.getFloat("skewX", shape.skewX);
201 if (params.has("skewZ")) shape.skewZ = params.getFloat("skewZ", shape.skewZ);
202 const int cutCount = std::clamp(params.getInt("cutCount", shape.cuts), 0, 12);
203 const float cutDepth = std::clamp(params.getFloat("cutDepth", shape.cutDepth), 0.f, 0.42f);
204
205 std::vector<Vec3> directions;
206 std::vector<Tri> triangles;
207 createIcosphere(subdivisions, directions, triangles);
208 std::vector<Vec3> positions(directions.size());
209 std::vector<Vec3> normals(directions.size());
210 const float steps = 3.f + angularity * 9.f;
211 for (size_t i = 0; i < directions.size(); ++i) {
212 const Vec3 d = directions[i];
213 const float qx = std::round(d.x * steps) / steps;
214 const float qy = std::round(d.y * steps) / steps;
215 const float qz = std::round(d.z * steps) / steps;
216 const float strata = fbm3((qx + 2.3f) * scale, (qy + 4.7f) * scale,
217 (qz + 8.1f) * scale, seed, octaves);
218 const float pits = fbm3((d.x + 7.2f) * scale * 2.7f,
219 (d.y + 1.9f) * scale * 2.7f,
220 (d.z + 5.4f) * scale * 2.7f,
221 seed + 7919u, std::max(2, octaves - 1));
222 const float displacement = strata * (0.08f + angularity * 0.16f) -
223 std::max(0.f, pits) * erosion;
224 const float r = std::max(radius * 0.45f, radius + displacement);
225 Vec3 p = {shapedCoordinate(d.x, shape.power) * r * shape.axisX,
226 shapedCoordinate(d.y, shape.power) * r * shape.axisY * (1.f - flattening),
227 shapedCoordinate(d.z, shape.power) * r * shape.axisZ};
228 p.x += p.y * shape.skewX;
229 p.z += p.y * shape.skewZ;
230 positions[i] = p;
231 }
232
233 // Clamp the deformed surface against deterministic planes. This preserves the icosphere's
234 // economical topology while creating broad fracture faces and non-round silhouettes.
235 for (int cut = 0; cut < cutCount; ++cut) {
236 Vec3 plane = normalize({randomSigned(randomState), randomSigned(randomState),
237 randomSigned(randomState)});
238 if (cut == 0) plane = normalize({0.2f, -1.f, 0.1f}); // stable resting face
239 const float support = radius * std::sqrt(
240 plane.x * plane.x * shape.axisX * shape.axisX +
241 plane.y * plane.y * shape.axisY * shape.axisY * (1.f - flattening) *
242 (1.f - flattening) +
243 plane.z * plane.z * shape.axisZ * shape.axisZ);
244 const float depth = cutDepth * (0.72f + random01(randomState) * 0.56f);
245 const float limit = support * (1.f - depth);
246 for (Vec3 &p : positions) {
247 const float excess = p.x * plane.x + p.y * plane.y + p.z * plane.z - limit;
248 if (excess > 0.f) {
249 p.x -= plane.x * excess;
250 p.y -= plane.y * excess;
251 p.z -= plane.z * excess;
252 }
253 }
254 }
255
256 for (const Tri &t : triangles) {
257 const Vec3 &a = positions[t.a], &b = positions[t.b], &c = positions[t.c];
258 const Vec3 face = cross({b.x - a.x, b.y - a.y, b.z - a.z},
259 {c.x - a.x, c.y - a.y, c.z - a.z});
260 for (uint32_t index : {t.a, t.b, t.c}) {
261 normals[index].x += face.x;
262 normals[index].y += face.y;
263 normals[index].z += face.z;
264 }
265 }
266 for (Vec3 &n : normals) n = normalize(n);
267
268 out.clear();
269 out.reserve(int(positions.size()), int(triangles.size() * 3));
270 for (size_t i = 0; i < positions.size(); ++i) {
271 const Vec3 &p = positions[i], &n = normals[i];
272 out.addVertex(p.x, p.y, p.z, n.x, n.y, n.z, p.x + 0.5f, p.y + 0.5f);
273 }
274 for (const Tri &t : triangles) out.addTriangle(t.a, t.b, t.c);
275 out.setMeta("algorithm", "mesh.rock");
276 out.setMeta("topology", "icosphere");
277 out.setMeta("baseShape", shape.name);
278 out.setMeta("cutCount", std::to_string(cutCount));
279 out.setMeta("subdivisions", std::to_string(subdivisions));
280 return true;
281}
282
283} // namespace eve::procgen
std::vector< std::uint32_t > triangles
Definition Builder.cpp:26
uint32_t seed
std::string value
int y
Definition Grass.cpp:135
int z
Definition Grass.cpp:135
float u
Definition Grass.cpp:234
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
std::string error
JobSystemThreadPool::State * state
float depth
uint32_t a
uint32_t b
uint32_t c
glm::vec4 p[6]
float axisZ
Definition RockMesh.cpp:22
float axisY
Definition RockMesh.cpp:22
float skewZ
Definition RockMesh.cpp:24
float skewX
Definition RockMesh.cpp:24
float cutDepth
Definition RockMesh.cpp:26
const char * name
Definition RockMesh.cpp:21
float axisX
Definition RockMesh.cpp:22
int cuts
Definition RockMesh.cpp:25
float power
Definition RockMesh.cpp:23
int d
int v
float scale
Definition TreeMesh.cpp:122
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
Definition MeshBuild.h:14
void addVertex(float px, float py, float pz, float nx, float ny, float nz, float u, float v)
Definition MeshBuild.cpp:22
void addTriangle(uint32_t i0, uint32_t i1, uint32_t i2)
Definition MeshBuild.cpp:34
void reserve(int vertexCount, int indexCount)
Definition MeshBuild.cpp:13
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
bool has(const std::string &key) const
Definition Params.cpp:21
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
std::string hash(std::string function, Data *input)
Hash the input, producing an set of bytes as output.
bool generateRockMesh(const Params &params, MeshBuild &out, std::string &error)
Build a shared-vertex, deformed icosphere rock for economical game props.
Definition RockMesh.cpp:165