From b47649b0784a1dab73eb94db4f00acde03394b75 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 9 May 2026 13:32:43 -0700 Subject: [PATCH] feat(editor): add --gen-mesh-candle wax-pillar primitive MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 80th procedural mesh primitive. Wax pillar candle: • saucer (optional) — wider shallow disc base, the drip catcher that sits on the table • wax — thin tall pillar standing on the saucer (or directly on the ground if saucerR=0) Uses the addClosedCylinderY helper for both pieces — same two-cylinder pattern as --gen-mesh-bird-bath but with a much skinnier upper cylinder. The 81-line handler is mostly arg parsing + validation; geometry is just two helper calls. Useful for chapels / shrines, vigil scenes, witch-hut ritual surfaces, alchemist labs, séance tables, festival- of-lights ground decoration. Set saucerR=0 for a plain candle without the saucer (e.g. for stack-them-on-a-cake contexts). Watertight under weld (verified — wax + saucer are two independent closed cylinders). Milestone: now at 80 procedural mesh primitives. --- tools/editor/cli_arg_required.cpp | 2 +- tools/editor/cli_gen_mesh.cpp | 50 +++++++++++++++++++++++++++++++ tools/editor/cli_help.cpp | 2 ++ 3 files changed, 53 insertions(+), 1 deletion(-) diff --git a/tools/editor/cli_arg_required.cpp b/tools/editor/cli_arg_required.cpp index 3f855e9d..6a17289e 100644 --- a/tools/editor/cli_arg_required.cpp +++ b/tools/editor/cli_arg_required.cpp @@ -60,7 +60,7 @@ const char* const kArgRequired[] = { "--gen-mesh-stone-bench", "--gen-mesh-mine-cart", "--gen-mesh-hitching-rail", "--gen-mesh-pillar-row", "--gen-mesh-statue-base", "--gen-mesh-bird-bath", - "--gen-mesh-planter-box", "--gen-mesh-urn", + "--gen-mesh-planter-box", "--gen-mesh-urn", "--gen-mesh-candle", "--gen-camp-pack", "--gen-blacksmith-pack", "--gen-village-pack", "--gen-temple-pack", "--gen-graveyard-pack", "--gen-garden-pack", "--gen-dock-pack", "--gen-tavern-pack", diff --git a/tools/editor/cli_gen_mesh.cpp b/tools/editor/cli_gen_mesh.cpp index 4870e91e..8013afba 100644 --- a/tools/editor/cli_gen_mesh.cpp +++ b/tools/editor/cli_gen_mesh.cpp @@ -5161,6 +5161,55 @@ int handleTent(int& i, int argc, char** argv) { return 0; } +int handleCandle(int& i, int argc, char** argv) { + // Wax pillar candle: thin tall wax cylinder optionally + // standing on a wider shallow saucer base (the drip catcher). + // Both pieces use addClosedCylinderY — same pattern as + // --gen-mesh-bird-bath but with a much skinnier upper + // cylinder. The 80th procedural mesh primitive. + std::string womBase = argv[++i]; + float waxR = 0.04f; + float waxH = 0.30f; + float saucerR = 0.08f; // 0 → no saucer + float saucerH = 0.015f; + int sides = 14; + parseOptFloat(i, argc, argv, waxR); + parseOptFloat(i, argc, argv, waxH); + parseOptFloat(i, argc, argv, saucerR); + parseOptFloat(i, argc, argv, saucerH); + parseOptInt(i, argc, argv, sides); + if (waxR <= 0 || waxH <= 0 || saucerR < 0 || + (saucerR > 0 && saucerR <= waxR) || + (saucerR > 0 && saucerH <= 0) || + sides < 6 || sides > 64) { + std::fprintf(stderr, + "gen-mesh-candle: dims > 0; saucerR > waxR (or 0); sides 6..64\n"); + return 1; + } + stripExt(womBase, ".wom"); + wowee::pipeline::WoweeModel wom; + initWomDefaults(wom, womBase); + float yBase = 0.0f; + if (saucerR > 0.0f) { + addClosedCylinderY(wom, saucerR, 0.0f, saucerH, sides); + yBase = saucerH; + } + addClosedCylinderY(wom, waxR, yBase, yBase + waxH, sides); + finalizeAsSingleBatch(wom); + float maxR = std::max(waxR, saucerR); + setCenteredBoundsXZ(wom, maxR, maxR, yBase + waxH); + if (!saveWomOrError(wom, womBase, "gen-mesh-candle")) return 1; + printWomWrote(womBase); + std::printf(" wax : R=%.3f x %.3f tall\n", waxR, waxH); + if (saucerR > 0.0f) + std::printf(" saucer : R=%.3f x %.3f thick\n", saucerR, saucerH); + else + std::printf(" saucer : (none)\n"); + std::printf(" sides : %d\n", sides); + printWomMeshStats(wom); + return 0; +} + int handleUrn(int& i, int argc, char** argv) { // Multi-tier vertical urn: foot (wide short cylinder) → // body (tall main cylinder) → neck (narrow constriction) → @@ -7264,6 +7313,7 @@ constexpr MeshEntry kMeshTable[] = { {"--gen-mesh-bird-bath", 1, handleBirdBath}, {"--gen-mesh-planter-box", 1, handlePlanterBox}, {"--gen-mesh-urn", 1, handleUrn}, + {"--gen-mesh-candle", 1, handleCandle}, {"--gen-camp-pack", 1, handleGenCampPack}, {"--gen-blacksmith-pack", 1, handleGenBlacksmithPack}, {"--gen-village-pack", 1, handleGenVillagePack}, diff --git a/tools/editor/cli_help.cpp b/tools/editor/cli_help.cpp index d736739c..12a541cc 100644 --- a/tools/editor/cli_help.cpp +++ b/tools/editor/cli_help.cpp @@ -308,6 +308,8 @@ void printUsage(const char* argv0) { std::printf(" Planter box: long open-top wood basin + visible soil-fill block (window sills / kitchen / balcony)\n"); std::printf(" --gen-mesh-urn [bodyR] [bodyH] [footR] [footH] [neckR] [neckH] [lipR] [lipH] [sides]\n"); std::printf(" Urn: 4-tier vertical pottery vessel (foot + body + neck + lip) — temple / mausoleum / kitchen storage\n"); + std::printf(" --gen-mesh-candle [waxR] [waxH] [saucerR] [saucerH] [sides]\n"); + std::printf(" Candle: thin wax pillar on optional saucer base (set saucerR=0 to skip) — chapels / vigil scenes\n"); std::printf(" --gen-camp-pack \n"); std::printf(" Convenience: emit tent + firepit + bedroll + canopy + woodpile + haystack into outDir as 6 .wom files\n"); std::printf(" --gen-blacksmith-pack \n");