refactor(editor): extract addClosedCylinderY into cli_box_emitter

Hoist the local addYCylinder lambda from --gen-mesh-bird-bath
into cli_box_emitter.hpp as inline addClosedCylinderY(wom, R,
y0, y1, sides). Closed Y-axis cylinder (side wall + ±Y end
cap fans) — the standard watertight tube primitive.

bird-bath now reads as two helper calls:

    addClosedCylinderY(wom, stemR, 0.0f, stemH, sides);
    addClosedCylinderY(wom, basinR, stemH, stemH + basinH, sides);

vs the previous 60-line inline lambda. Output bytes verified
identical: bird-bath surface area 1.0788 m² unchanged.

Establishes the helper for future Y-axis cylindrical
primitives (urn, candle, lantern body, scroll case, well
pail, etc.) — they can stack tubes with one call each.
This commit is contained in:
Kelsi 2026-05-09 13:24:12 -07:00
parent 8adbeb237d
commit 81f605617a
2 changed files with 64 additions and 60 deletions

View file

@ -2,6 +2,7 @@
#include "pipeline/wowee_model.hpp"
#include <glm/glm.hpp>
#include <cmath>
#include <cstdint>
#include <cstdio>
#include <filesystem>
@ -119,6 +120,67 @@ inline uint32_t addVertex(wowee::pipeline::WoweeModel& wom,
glm::vec2(u, v));
}
// Append a closed Y-axis cylinder (side wall + ±Y end caps) to a
// WoweeModel. The cylinder spans from y=y0 to y=y1 with radius R
// and `sides` segments around the circumference. Side wall faces
// outward radially; cap fans face -Y / +Y. Used by --gen-mesh-
// bird-bath and any future cylindrical garden / well / ornament
// primitive that needs a watertight Y-axis tube.
inline void addClosedCylinderY(wowee::pipeline::WoweeModel& wom,
float R, float y0, float y1, int sides) {
constexpr float pi = 3.14159265358979f;
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
glm::vec3 dir(std::cos(ang), 0.0f, std::sin(ang));
addVertex(wom, {R * dir.x, y0, R * dir.z}, dir, {u, 0});
}
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
glm::vec3 dir(std::cos(ang), 0.0f, std::sin(ang));
addVertex(wom, {R * dir.x, y1, R * dir.z}, dir, {u, 1});
}
for (int s = 0; s < sides; ++s) {
wom.indices.insert(wom.indices.end(), {
bot + s, top + s, bot + s + 1,
bot + s + 1, top + s, top + s + 1
});
}
uint32_t botCenter = addVertex(wom, {0.0f, y0, 0.0f},
{0.0f, -1.0f, 0.0f}, {0.5f, 0.5f});
uint32_t botRing = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
addVertex(wom, {R * std::cos(ang), y0, R * std::sin(ang)},
{0.0f, -1.0f, 0.0f},
{0.5f + 0.5f * std::cos(ang),
0.5f + 0.5f * std::sin(ang)});
}
for (int s = 0; s < sides; ++s) {
wom.indices.insert(wom.indices.end(),
{botCenter, botRing + s + 1, botRing + s});
}
uint32_t topCenter = addVertex(wom, {0.0f, y1, 0.0f},
{0.0f, 1.0f, 0.0f}, {0.5f, 0.5f});
uint32_t topRing = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
addVertex(wom, {R * std::cos(ang), y1, R * std::sin(ang)},
{0.0f, 1.0f, 0.0f},
{0.5f + 0.5f * std::cos(ang),
0.5f + 0.5f * std::sin(ang)});
}
for (int s = 0; s < sides; ++s) {
wom.indices.insert(wom.indices.end(),
{topCenter, topRing + s, topRing + s + 1});
}
}
// Append a flat-shaded axis-aligned box to a WoweeModel. The box
// is centered at (cx, cy, cz) with half-extents (hx, hy, hz). Each
// of the 6 faces emits its own 4 vertices with the face's outward

View file

@ -5257,66 +5257,8 @@ int handleBirdBath(int& i, int argc, char** argv) {
stripExt(womBase, ".wom");
wowee::pipeline::WoweeModel wom;
initWomDefaults(wom, womBase);
const float pi = 3.14159265358979f;
// Helper: emit a Y-axis closed cylinder of radius R from
// y = y0 to y = y1.
auto addYCylinder = [&](float R, float y0, float y1) {
// Side wall: ring at y0, ring at y1.
uint32_t bot = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
glm::vec3 dir(std::cos(ang), 0.0f, std::sin(ang));
addVertex(wom, {R * dir.x, y0, R * dir.z}, dir, {u, 0});
}
uint32_t top = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
glm::vec3 dir(std::cos(ang), 0.0f, std::sin(ang));
addVertex(wom, {R * dir.x, y1, R * dir.z}, dir, {u, 1});
}
for (int s = 0; s < sides; ++s) {
wom.indices.insert(wom.indices.end(), {
bot + s, top + s, bot + s + 1,
bot + s + 1, top + s, top + s + 1
});
}
// Bottom cap (-Y) fan.
uint32_t botCenter = addVertex(wom, {0, y0, 0}, {0, -1, 0},
{0.5f, 0.5f});
uint32_t botRing = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
addVertex(wom, {R * std::cos(ang), y0, R * std::sin(ang)},
{0, -1, 0},
{0.5f + 0.5f * std::cos(ang),
0.5f + 0.5f * std::sin(ang)});
}
for (int s = 0; s < sides; ++s) {
wom.indices.insert(wom.indices.end(),
{botCenter, botRing + s + 1, botRing + s});
}
// Top cap (+Y) fan.
uint32_t topCenter = addVertex(wom, {0, y1, 0}, {0, 1, 0},
{0.5f, 0.5f});
uint32_t topRing = static_cast<uint32_t>(wom.vertices.size());
for (int s = 0; s <= sides; ++s) {
float u = static_cast<float>(s) / sides;
float ang = u * 2.0f * pi;
addVertex(wom, {R * std::cos(ang), y1, R * std::sin(ang)},
{0, 1, 0},
{0.5f + 0.5f * std::cos(ang),
0.5f + 0.5f * std::sin(ang)});
}
for (int s = 0; s < sides; ++s) {
wom.indices.insert(wom.indices.end(),
{topCenter, topRing + s, topRing + s + 1});
}
};
addYCylinder(stemR, 0.0f, stemH);
addYCylinder(basinR, stemH, stemH + basinH);
addClosedCylinderY(wom, stemR, 0.0f, stemH, sides);
addClosedCylinderY(wom, basinR, stemH, stemH + basinH, sides);
finalizeAsSingleBatch(wom);
setCenteredBoundsXZ(wom, basinR, basinR, stemH + basinH);
if (!saveWomOrError(wom, womBase, "gen-mesh-bird-bath")) return 1;