feat(editor): extend --gen-mesh with cylinder shape

Adds a capped cylinder primitive along the Y axis: radius=size/2,
height=size, 24 side segments. Smooth enough for pillars and
torches without exploding the vertex count (102 verts / 96 tris).

Per-face normals on the caps point ±Y; side faces get smooth
per-vertex radial normals so the cylinder shades as a curved
surface. UVs: side wraps the texture once around (u=0..1 around the
ring, v=0..1 top-to-bottom); caps map [0..1] from a square sampled
at the disc.

The handler's "shape must be" error message updated to include
cylinder. Help text on both --gen-mesh and --gen-mesh-textured
updated to advertise the new shape.

Verified: pillar.wom written with 102 verts / 96 tris / correct
bounds / single opaque batch; --export-obj round-trip preserves
both counts; bad shape ('triangle') rejected with updated error.
This commit is contained in:
Kelsi 2026-05-07 03:48:58 -07:00
parent f3578a14cc
commit 6f0a60ce83

View file

@ -526,9 +526,9 @@ static void printUsage(const char* argv0) {
std::printf(" Markdown migration-progress report (per-pair table, share %%, recommended next steps)\n");
std::printf(" --gen-texture <out.png> <colorHex|pattern> [W H]\n");
std::printf(" Synthesize a placeholder texture (solid hex color or 'checker'/'grid'); default 256x256\n");
std::printf(" --gen-mesh <wom-base> <cube|plane|sphere> [size]\n");
std::printf(" --gen-mesh <wom-base> <cube|plane|sphere|cylinder> [size]\n");
std::printf(" Synthesize a procedural WOM primitive with proper normals, UVs, and bounds\n");
std::printf(" --gen-mesh-textured <wom-base> <cube|plane|sphere> <colorHex|pattern> [size]\n");
std::printf(" --gen-mesh-textured <wom-base> <cube|plane|sphere|cylinder> <colorHex|pattern> [size]\n");
std::printf(" Compose a procedural mesh + matching PNG texture wired into the WOM's batch\n");
std::printf(" --add-texture-to-mesh <wom-base> <png-path> [batchIdx]\n");
std::printf(" Bind an existing PNG into a WOM's texturePaths and point batchIdx (default 0) at it\n");
@ -15097,9 +15097,73 @@ int main(int argc, char* argv[]) {
wom.indices.push_back(d);
}
}
} else if (s == "cylinder") {
// Capped cylinder along the Y axis. radius=size/2,
// height=size. 24 side segments — smooth enough for
// pillars and torches without exploding the vertex
// count. UVs: side wraps the texture once around;
// caps map [0..1] from a square sampled at the disc.
const int segments = 24;
float r = h;
// Side ring: 2 vertex rows (top, bottom), each with
// (segments+1) verts so UV-seam doesn't share verts.
for (int sg = 0; sg <= segments; ++sg) {
float u = static_cast<float>(sg) / segments;
float ang = u * 2.0f * 3.14159265358979f;
float ca = std::cos(ang), sa = std::sin(ang);
// Bottom ring (Y = -h).
addVertex(r * ca, -h, r * sa, ca, 0, sa, u, 0);
// Top ring (Y = +h).
addVertex(r * ca, h, r * sa, ca, 0, sa, u, 1);
}
// Side quad indices.
for (int sg = 0; sg < segments; ++sg) {
uint32_t a = sg * 2;
uint32_t b = a + 1;
uint32_t c = a + 2;
uint32_t d = a + 3;
wom.indices.push_back(a);
wom.indices.push_back(c);
wom.indices.push_back(b);
wom.indices.push_back(b);
wom.indices.push_back(c);
wom.indices.push_back(d);
}
// Top cap fan.
uint32_t topCenter = static_cast<uint32_t>(wom.vertices.size());
addVertex(0, h, 0, 0, 1, 0, 0.5f, 0.5f);
uint32_t topRingStart = static_cast<uint32_t>(wom.vertices.size());
for (int sg = 0; sg <= segments; ++sg) {
float u = static_cast<float>(sg) / segments;
float ang = u * 2.0f * 3.14159265358979f;
float ca = std::cos(ang), sa = std::sin(ang);
addVertex(r * ca, h, r * sa, 0, 1, 0,
0.5f + 0.5f * ca, 0.5f + 0.5f * sa);
}
for (int sg = 0; sg < segments; ++sg) {
wom.indices.push_back(topCenter);
wom.indices.push_back(topRingStart + sg);
wom.indices.push_back(topRingStart + sg + 1);
}
// Bottom cap fan (winding flipped so normal points -Y).
uint32_t botCenter = static_cast<uint32_t>(wom.vertices.size());
addVertex(0, -h, 0, 0, -1, 0, 0.5f, 0.5f);
uint32_t botRingStart = static_cast<uint32_t>(wom.vertices.size());
for (int sg = 0; sg <= segments; ++sg) {
float u = static_cast<float>(sg) / segments;
float ang = u * 2.0f * 3.14159265358979f;
float ca = std::cos(ang), sa = std::sin(ang);
addVertex(r * ca, -h, r * sa, 0, -1, 0,
0.5f + 0.5f * ca, 0.5f - 0.5f * sa);
}
for (int sg = 0; sg < segments; ++sg) {
wom.indices.push_back(botCenter);
wom.indices.push_back(botRingStart + sg + 1);
wom.indices.push_back(botRingStart + sg);
}
} else {
std::fprintf(stderr,
"gen-mesh: shape must be cube, plane, or sphere (got '%s')\n",
"gen-mesh: shape must be cube, plane, sphere, or cylinder (got '%s')\n",
shape.c_str());
return 1;
}