23 {0, 1}, {1, 2}, {2, 3}, {3, 0}, {4, 5}, {5, 6}, {6, 7}, {7, 4}, {0, 4}, {1, 5}, {2, 6}, {3, 7},
28 {0, 0, 0}, {1, 0, 0}, {1, 1, 0}, {0, 1, 0}, {0, 0, 1}, {1, 0, 1}, {1, 1, 1}, {0, 1, 1},
150 const int subdivisions = params.
getInt(
"subdivisions", 2);
151 const float radius = params.
getFloat(
"radius", 1.f);
152 const float inset = params.
getFloat(
"tileInset", 0.06f);
153 if (subdivisions < 0 || subdivisions > 7) {
154 error =
"mesh.hexplanet: subdivisions must be in [0, 7]";
157 if (!(radius > 0.f)) {
158 error =
"mesh.hexplanet: radius must be positive";
161 if (inset < 0.f || inset >= 0.5f) {
162 error =
"mesh.hexplanet: tileInset must be in [0, 0.5)";
166 const float phi = (1.f + std::sqrt(5.f)) * 0.5f;
167 std::vector<Vec3> vertices = {
168 {-1, phi, 0}, {1, phi, 0}, {-1, -phi, 0}, {1, -phi, 0},
169 {0, -1, phi}, {0, 1, phi}, {0, -1, -phi}, {0, 1, -phi},
170 {phi, 0, -1}, {phi, 0, 1}, {-phi, 0, -1}, {-phi, 0, 1},
172 for (Vec3 &
v : vertices)
v = normalized(
v);
173 std::vector<Triangle> faces = {
174 {0, 11, 5}, {0, 5, 1}, {0, 1, 7}, {0, 7, 10}, {0, 10, 11},
175 {1, 5, 9}, {5, 11, 4}, {11, 10, 2}, {10, 7, 6}, {7, 1, 8},
176 {3, 9, 4}, {3, 4, 2}, {3, 2, 6}, {3, 6, 8}, {3, 8, 9},
177 {4, 9, 5}, {2, 4, 11}, {6, 2, 10}, {8, 6, 7}, {9, 8, 1},
179 for (
int level = 0; level < subdivisions; ++level) {
180 std::map<uint64_t, uint32_t> cache;
181 std::vector<Triangle> next;
182 next.reserve(faces.size() * 4u);
183 for (
const Triangle &
f : faces) {
184 const uint32_t ab = midpoint(
f.a,
f.b, vertices, cache);
185 const uint32_t bc = midpoint(
f.b,
f.c, vertices, cache);
186 const uint32_t ca = midpoint(
f.c,
f.a, vertices, cache);
187 next.insert(next.end(), {{f.a, ab, ca}, {f.b, bc, ab}, {f.c, ca, bc}, {ab, bc, ca}});
192 std::vector<std::vector<uint32_t>> incident(vertices.size());
193 std::vector<Vec3> faceCenters;
194 faceCenters.reserve(faces.size());
195 for (uint32_t i = 0; i < faces.size(); ++i) {
196 const Triangle &
f = faces[i];
197 faceCenters.push_back(normalized(add(add(vertices[
f.a], vertices[
f.b]), vertices[
f.c])));
198 incident[
f.a].push_back(i);
199 incident[
f.b].push_back(i);
200 incident[
f.c].push_back(i);
204 int pentagons = 0, hexagons = 0;
205 for (uint32_t cell = 0; cell < vertices.size(); ++cell) {
206 const Vec3 center = vertices[cell];
207 const Vec3 reference = std::fabs(center.y) < 0.9f ? normalized(cross({0, 1, 0}, center))
208 : normalized(cross({1, 0, 0}, center));
209 const Vec3 tangent = cross(center, reference);
210 auto &ring = incident[cell];
211 std::sort(ring.begin(), ring.end(), [&](uint32_t lhs, uint32_t rhs) {
212 const Vec3 a = sub(faceCenters[lhs], mul(center, dot(faceCenters[lhs], center)));
213 const Vec3 b = sub(faceCenters[rhs], mul(center, dot(faceCenters[rhs], center)));
214 return std::atan2(dot(a, tangent), dot(a, reference)) <
215 std::atan2(dot(b, tangent), dot(b, reference));
217 if (ring.size() == 5) ++pentagons;
218 else if (ring.size() == 6) ++hexagons;
220 const uint32_t base = uint32_t(out.getVertexCount());
221 addPlanetVertex(out, center, radius);
222 for (uint32_t faceIndex : ring) {
224 addPlanetVertex(out, normalized(add(mul(faceCenters[faceIndex], 1.f - inset),
225 mul(center, inset))), radius);
227 for (uint32_t i = 0; i < ring.size(); ++i) {
228 out.addTriangle(base, base + 1u + i, base + 1u + (i + 1u) % uint32_t(ring.size()));
231 out.setMeta(
"algorithm",
"mesh.hexplanet");
232 out.setMeta(
"cells", std::to_string(vertices.size()));
233 out.setMeta(
"pentagons", std::to_string(pentagons));
234 out.setMeta(
"hexagons", std::to_string(hexagons));
235 out.setMeta(
"subdivisions", std::to_string(subdivisions));
240 std::string *
error) {
242 if (
error) *
error =
"marchingCubes: null density";
245 if (nx < 2 || ny < 2 || nz < 2) {
246 if (
error) *
error =
"marchingCubes: volume must be at least 2x2x2";
251 out.
reserve((nx * ny * nz) / 2, (nx * ny * nz) * 3);
253 auto at = [&](
int x,
int y,
int z) ->
float {
254 return density[size_t(
x) + size_t(
y) * size_t(nx) + size_t(
z) * size_t(nx) * size_t(ny)];
258 const float sx = 1.f / float(nx - 1);
259 const float sy = 1.f / float(ny - 1);
260 const float sz = 1.f / float(nz - 1);
262 for (
int z = 0;
z < nz - 1; ++
z) {
263 for (
int y = 0;
y < ny - 1; ++
y) {
264 for (
int x = 0;
x < nx - 1; ++
x) {
267 for (
int i = 0; i < 8; ++i) {
268 const int cx =
x + int(kCornerOffset[i][0]);
269 const int cy =
y + int(kCornerOffset[i][1]);
270 const int cz =
z + int(kCornerOffset[i][2]);
271 val[i] = at(
cx,
cy, cz);
272 if (val[i] < isolevel) cubeIndex |= (1 << i);
274 const int edges = kEdgeTable[cubeIndex];
275 if (edges == 0)
continue;
277 float vertList[12][3];
278 for (
int e = 0; e < 12; ++e) {
279 if (!(edges & (1 << e)))
continue;
280 const int a = kEdgeCorners[e][0];
281 const int b = kEdgeCorners[e][1];
282 const float va = val[
a];
283 const float vb = val[
b];
284 float t = (isolevel - va) / (vb - va + 1e-12f);
285 t = std::clamp(t, 0.f, 1.f);
287 (float(
x) + lerp(kCornerOffset[
a][0], kCornerOffset[
b][0], t)) * sx - 0.5f;
289 (float(
y) + lerp(kCornerOffset[
a][1], kCornerOffset[
b][1], t)) * sy - 0.5f;
291 (float(
z) + lerp(kCornerOffset[
a][2], kCornerOffset[
b][2], t)) * sz - 0.5f;
297 for (
int i = 0; kTriTable[cubeIndex][i] != -1; i += 3) {
298 const int e0 = kTriTable[cubeIndex][i];
299 const int e1 = kTriTable[cubeIndex][i + 1];
300 const int e2 = kTriTable[cubeIndex][i + 2];
301 const float *p0 = vertList[e0];
302 const float *p1 = vertList[e1];
303 const float *p2 = vertList[e2];
305 float ax = p1[0] - p0[0], ay = p1[1] - p0[1], az = p1[2] - p0[2];
306 float bx = p2[0] - p0[0], by = p2[1] - p0[1], bz = p2[2] - p0[2];
307 float nxn = ay * bz - az * by;
308 float nyn = az * bx - ax * bz;
309 float nzn = ax * by - ay * bx;
310 normalize3(nxn, nyn, nzn);
315 const float u0 = p0[0] + 0.5f, v0 = p0[1] + 0.5f;
316 const float u1 = p1[0] + 0.5f, v1 = p1[1] + 0.5f;
317 const float u2 = p2[0] + 0.5f, v2 = p2[1] + 0.5f;
318 out.
addVertex(p0[0], p0[1], p0[2], nxn, nyn, nzn, u0, v0);
319 out.
addVertex(p1[0], p1[1], p1[2], nxn, nyn, nzn, u1, v1);
320 out.
addVertex(p2[0], p2[1], p2[2], nxn, nyn, nzn, u2, v2);
327 out.
setMeta(
"algorithm",
"mesh.marchingcubes");
332 std::string &
error) {
333 const int res = params.
getInt(
"resolution",
335 nx = params.
getInt(
"nx", res);
338 if (nx < 2 || ny < 2 || nz < 2) {
339 error =
"mesh.marchingcubes: resolution must be at least 2 in each axis";
342 if (nx > 128 || ny > 128 || nz > 128) {
343 error =
"mesh.marchingcubes: resolution capped at 128 per axis";
347 const std::string field = params.
getString(
"field",
"sphere");
349 const int octaves = std::max(1, params.
getInt(
"octaves", 3));
352 density.assign(
size_t(nx) *
size_t(ny) *
size_t(nz), 0.f);
353 for (
int z = 0;
z < nz; ++
z) {
354 for (
int y = 0;
y < ny; ++
y) {
355 for (
int x = 0;
x < nx; ++
x) {
356 const float px = (float(
x) / float(nx - 1) - 0.5f) * 2.f;
357 const float py = (float(
y) / float(ny - 1) - 0.5f) * 2.f;
358 const float pz = (float(
z) / float(nz - 1) - 0.5f) * 2.f;
360 if (field ==
"sphere") {
361 const float r = params.
getFloat(
"radius", 0.7f);
362 d = r - std::sqrt(
px *
px + py * py + pz * pz);
363 }
else if (field ==
"rock") {
367 const float radius = params.
getFloat(
"radius", 0.68f);
368 const float flattening =
369 std::clamp(params.
getFloat(
"flattening", 0.22f), 0.f, 0.7f);
370 const float angularity =
371 std::clamp(params.
getFloat(
"angularity", 0.35f), 0.f, 1.f);
372 const float erosion =
373 std::clamp(params.
getFloat(
"erosion", 0.18f), 0.f, 0.45f);
374 const float detailScale = std::max(0.25f,
scale);
375 const float sy = std::max(0.3f, 1.f - flattening);
377 const float ey = py / sy;
379 const float len = std::sqrt(ex * ex + ey * ey + ez * ez);
380 const float invLen = len > 1e-5f ? 1.f / len : 0.f;
381 const float steps = 3.f + angularity * 9.f;
382 const float qx = std::round(ex * invLen * steps) / steps;
383 const float qy = std::round(ey * invLen * steps) / steps;
384 const float qz = std::round(ez * invLen * steps) / steps;
385 const float strata = fbm3((qx + 2.3f) * detailScale,
386 (qy + 4.7f) * detailScale,
387 (qz + 8.1f) * detailScale,
seed, octaves);
388 const float pits = fbm3((
px + 7.2f) * detailScale * 2.7f,
389 (py + 1.9f) * detailScale * 2.7f,
390 (pz + 5.4f) * detailScale * 2.7f,
391 seed + 7919u, std::max(2, octaves - 1));
392 const float displacement = strata * (0.08f + angularity * 0.16f) -
393 std::max(0.f, pits) * erosion;
394 d = radius + displacement - len;
395 }
else if (field ==
"torus") {
396 const float R = params.
getFloat(
"majorRadius", 0.55f);
397 const float r = params.
getFloat(
"minorRadius", 0.22f);
398 const float q = std::sqrt(
px *
px + pz * pz) - R;
399 d = r - std::sqrt(q * q + py * py);
400 }
else if (field ==
"terrain") {
404 }
else if (field ==
"noise") {
409 error =
"mesh.marchingcubes: unknown field '" + field +
410 "' (use sphere|rock|torus|noise|terrain)";
414 const float margin = 0.92f;
415 const float bx = std::max(0.f, std::fabs(
px) -
margin);
416 const float by = std::max(0.f, std::fabs(py) -
margin);
417 const float bz = std::max(0.f, std::fabs(pz) -
margin);
418 d -= (bx * bx + by * by + bz * bz) * 4.f;
419 density[size_t(
x) + size_t(
y) * size_t(nx) + size_t(
z) * size_t(nx) * size_t(ny)] =