From 6f0a60ce83f3f6f6a010ad9bb4f5af47707035d3 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 7 May 2026 03:48:58 -0700 Subject: [PATCH] feat(editor): extend --gen-mesh with cylinder shape MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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. --- tools/editor/main.cpp | 70 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 67 insertions(+), 3 deletions(-) diff --git a/tools/editor/main.cpp b/tools/editor/main.cpp index 582d7bd6..f3ef98cb 100644 --- a/tools/editor/main.cpp +++ b/tools/editor/main.cpp @@ -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 [W H]\n"); std::printf(" Synthesize a placeholder texture (solid hex color or 'checker'/'grid'); default 256x256\n"); - std::printf(" --gen-mesh [size]\n"); + std::printf(" --gen-mesh [size]\n"); std::printf(" Synthesize a procedural WOM primitive with proper normals, UVs, and bounds\n"); - std::printf(" --gen-mesh-textured [size]\n"); + std::printf(" --gen-mesh-textured [size]\n"); std::printf(" Compose a procedural mesh + matching PNG texture wired into the WOM's batch\n"); std::printf(" --add-texture-to-mesh [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(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(wom.vertices.size()); + addVertex(0, h, 0, 0, 1, 0, 0.5f, 0.5f); + uint32_t topRingStart = static_cast(wom.vertices.size()); + for (int sg = 0; sg <= segments; ++sg) { + float u = static_cast(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(wom.vertices.size()); + addVertex(0, -h, 0, 0, -1, 0, 0.5f, 0.5f); + uint32_t botRingStart = static_cast(wom.vertices.size()); + for (int sg = 0; sg <= segments; ++sg) { + float u = static_cast(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; }