6#include <unordered_map>
13 float x = 0.f,
y = 0.f,
z = 0.f;
17 uint32_t
a = 0,
b = 0,
c = 0;
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};
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};
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; }
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);
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;
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);
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;
79 return norm > 0.f ? sum / norm : 0.f;
82uint32_t nextRandom(uint32_t &
state) {
89float random01(uint32_t &
state) {
90 return float(nextRandom(
state) & 0x00ffffffu) / float(0x01000000u);
93float randomSigned(uint32_t &
state) {
return random01(
state) * 2.f - 1.f; }
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},
102 std::string
name = requested;
104 for (
const ShapeProfile &candidate : profiles) {
105 if (
name == candidate.name) {
113float shapedCoordinate(
float value,
float power) {
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);
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);
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}};
146 for (
int level = 0; level < subdivisions; ++level) {
147 std::unordered_map<uint64_t, uint32_t> cache;
148 std::vector<Tri> next;
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});
166 const int subdivisions = params.
getInt(
"subdivisions", 3);
167 if (subdivisions < 0 || subdivisions > 5) {
168 error =
"mesh.rock: subdivisions must be in [0,5]";
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);
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)";
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,
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);
205 std::vector<Vec3> directions;
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,
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;
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});
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) *
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;
249 p.x -= plane.x * excess;
250 p.y -= plane.y * excess;
251 p.z -= plane.z * excess;
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;
266 for (Vec3 &
n : normals)
n = normalize(
n);
270 for (
size_t i = 0; i < positions.size(); ++i) {
271 const Vec3 &
p = positions[i], &
n = normals[i];
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));
std::vector< std::uint32_t > triangles
JobSystemThreadPool::State * state
CPU triangle mesh from procedural mesh recipes (e.g. marching cubes). Positions/normals are xyz-packe...
void addVertex(float px, float py, float pz, float nx, float ny, float nz, float u, float v)
void addTriangle(uint32_t i0, uint32_t i1, uint32_t i2)
void reserve(int vertexCount, int indexCount)
void setMeta(const std::string &key, const std::string &value)
Generation parameters. Algorithm-specific keys live in values as strings (no overloads; typed setters...
bool has(const std::string &key) const
float getFloat(const std::string &key, float defaultValue) const
std::string getString(const std::string &key, const std::string &defaultValue) const
int getInt(const std::string &key, int defaultValue) const
std::string hash(std::string function, Data *input)
Hash the input, producing an set of bytes as output.
bool generateRockMesh(const Params ¶ms, MeshBuild &out, std::string &error)
Build a shared-vertex, deformed icosphere rock for economical game props.