Commit graph

93 commits

Author SHA1 Message Date
Kelsi
13312096ea feat(editor): add --gen-mesh-mortar-pestle alchemy primitive
90th procedural mesh: a wider-than-tall closed cylinder
(the mortar bowl) with a thin tall closed cylinder (the
pestle) rising from its center. Bowl reads as carved stone
or wood; pestle reads as a small grinding rod centered in
the rim.

Distinct from existing kitchenware primitives:
  • --gen-mesh-cauldron — large + has supporting legs
  • --gen-mesh-chalice  — 3-tier goblet, no separate utensil
  • --gen-mesh-mug      — handled drinking cup
  • --gen-mesh-bowl     — does not exist; this primitive
                          covers the small-bowl + tool case

Useful for: alchemy lab counters, kitchen / cooking-stove
dressing, herbalist shop interiors, witchcraft NPCs,
quest-giver desks for "grind these herbs" objectives.

124 verts / 112 tris at default 14 sides — two simple
closed cylinders sharing a single batch with shared
texture coords.
2026-05-09 14:43:21 -07:00
Kelsi
b907b69fa7 feat(editor): add --gen-mesh-mug tankard primitive
87th procedural mesh: drinking mug / tankard built from a
closed cylindrical body plus a thin side handle slab. The
handle is positioned on +X with its inner face flush against
the body and centered vertically.

Without rotation the handle is a solid box rather than a
real C-loop, so silhouette reads as a tankard handle rather
than a delicate ring — appropriate for the chunky-prop
aesthetic favored by the existing primitive set. Distinct
from --gen-mesh-chalice (3-tier goblet, no handle) and
--gen-mesh-well-pail (top horizontal handle bar).

Useful for tavern / banquet hall / kitchen dressing, inn
table props, and the eventual --gen-tavern-pack-2 follow-on
(once enough drinking-vessel primitives exist to justify a
dedicated drinkware composite).

86 verts / 68 tris at default 14 sides — a single batch
with shared body + handle texture coords.
2026-05-09 14:30:18 -07:00
Kelsi
e7695d6e3b feat(editor): add --gen-mesh-well-pail wooden bucket primitive
86th procedural mesh primitive. Wooden well-pail / mop-
bucket:

  • body — closed cylindrical Y-axis cylinder via
    addClosedCylinderY (collision-watertight)
  • handle — thin horizontal box floating handleArc above
    the rim, spanning at least bodyR×2 wide so its ends
    align with the rim's outside edge

Without rotation the handle is a straight bar rather than
a true semicircle, but the bucket-with-handle silhouette
still reads correctly. handleArc parameter controls the
gap between rim and handle (small = handle touches rim;
larger = bucket-being-carried look).

Useful for well scenes (pairs with --gen-mesh-well),
servant-corridor mop scenes, kitchen prep stations,
homestead exteriors, dwarven mine shafts, ocean-ship
swabbing decks. Watertight under weld (verified 102
manifold edges, 0 boundary, 0 non-manifold).
2026-05-09 14:23:23 -07:00
Kelsi
35a5a9d09f feat(editor): add --gen-mesh-stove pot-bellied wood stove
85th procedural mesh primitive. Two-cylinder pot-bellied
stove silhouette:

  • body — wide round Y-axis cylinder (the stove body that
    holds the firebox)
  • chimney — thin tall Y-axis cylinder rising from the top
    of the body (the smoke vent)

Distinct from --gen-mesh-forge (square stone hearth + hood
+ chimney) and --gen-mesh-chimney (just the rectangular
shaft) — stove is the round-cylinder home / workshop
variant for cottages, smithies, alchemist labs, ranger
huts, dwarven mead-hall corner heaters.

Validates that chimneyR < bodyR so the silhouette always
reads as a wider-bottom-with-thinner-stack shape rather
than a flat tube.

Watertight under weld (verified 192 manifold edges, 0
boundary, 0 non-manifold).
2026-05-09 14:15:14 -07:00
Kelsi
2b96863db9 feat(editor): add --gen-mesh-scroll-case library prop
84th procedural mesh primitive. Cylindrical scroll case /
map tube:

  • body — thin tall Y-axis cylinder (the case proper)
  • cap (optional) — shorter wider cylinder on top, set
    capR=0 to skip for a bare stick / rolled-document spine

Distinct from --gen-mesh-chalice (foot + stem + bowl
silhouette) and --gen-mesh-lantern (4-tier base + globe +
neck + cap) — scroll case is the simplest 1-or-2-tier
"tube with lid" prop.

Useful for wizard-tower libraries, archive shelves, mage-
hut detail trim, scribe-table props, scout-courier waist
gear. Validates that capR >= bodyR so the lid always reads
as a proper top closure rather than a degenerate band.

Watertight under weld (verified 168 manifold edges, 0
boundary, 0 non-manifold). Two more clean uses of the
addClosedCylinderY helper.
2026-05-09 13:58:22 -07:00
Kelsi
1ad1977ad6 feat(editor): add --gen-mesh-standing-torch hall-lining torch
83rd procedural mesh primitive. Tall standing floor torch:

  • post — thin Y-axis cylinder rising from the floor
  • bowl — wider shallow Y-axis cylinder (the fire bowl)
    sitting on top of the post

Distinct from --gen-mesh-brazier (squat fire-bowl on a
short stem with a wide-base silhouette) — standing-torch
is the tall thin walking-height variant for lining
hallways, ceremonial paths, dungeon entries, palace
entrance walks, watch-station lighting.

Validates that postR < bowlR so the silhouette always
reads as the wider-bowl-on-thinner-pole shape.

Watertight under weld (verified 168 manifold edges, 0
boundary, 0 non-manifold). Two clean addClosedCylinderY
calls — pattern is now well-established for cylindrical
prop primitives.
2026-05-09 13:47:37 -07:00
Kelsi
574369bd97 feat(editor): add --gen-arena-pack composite
Tenth themed mesh pack after camp / blacksmith / village /
temple / graveyard / garden / dock / tavern / mining.
Combat-training / gladiator-pit / militia-drill-yard scene
emitting a 7-primitive arena layout:

  • training-dummy — practice target
  • archery-target — range station
  • workbench — weapons-rack stand
  • crate-stack — gear storage
  • bench — audience seating
  • water-trough — between-bout refresh
  • hitching-rail — mount parking

Composite-pack catalogue: camp + blacksmith + village +
temple + graveyard + garden + dock + tavern + mining +
arena = 10 themed mesh packs. --list-packs surfaces 15
total composites including the pre-existing zone/project
starter packs.

Milestone: ten themed mesh packs covering the full life
cycle of a typical role-playing-game town from outdoor
camp to blacksmith forge to gladiator arena.
2026-05-09 13:44:48 -07:00
Kelsi
8a73324a5a feat(editor): add --gen-mesh-chalice ceremonial goblet
82nd procedural mesh primitive. 3-tier ceremonial chalice:

  • foot — wide flat base disc (the goblet's footprint on
    the table)
  • stem — thin tall connecting column (the holding-grip)
  • bowl — wider shallow cup at the top

Validates that stemR < both footR and bowlR so the
silhouette always reads as the classic wide-narrow-wide
goblet shape rather than a degenerate cylinder stack.

Three more primitives that consume the addClosedCylinderY
helper this batch (lantern + chalice from the previous
two commits + this), pattern is now well-established.

Useful for chapel altars, treasure rooms, royal-court
banquet tables, ritual / cult scenes, dwarven mead halls,
fairy-tale storybook props. Watertight under weld
(verified 252 manifold edges, 0 boundary, 0 non-manifold).

Milestone: kArgRequired now reaches 420 documented flags.
2026-05-09 13:42:58 -07:00
Kelsi
2e973c0f00 feat(editor): add --gen-mining-pack composite
Ninth themed mesh pack after camp / blacksmith / village /
temple / graveyard / garden / dock / tavern. Mining-shaft
/ quarry-floor scene emitting a 7-primitive working-mine
layout:

  • gravel-pile — loose rubble from the dig
  • crate-stack — raw-ore cargo waiting for transport
  • mine-cart — underground transport
  • pillar-row — roof support pillars (the colonnade
    pattern reads as mine-shaft supports underground)
  • lantern — light source
  • workbench — assay / sorting station
  • hitching-post — mule / cart tie

Together these form a complete working-mine entrance /
quarry-floor layout. Uses 3 primitives newly added in
recent batches (mine-cart, pillar-row, lantern).

Composite-pack catalogue: camp + blacksmith + village +
temple + graveyard + garden + dock + tavern + mining =
9 themed mesh packs. --list-packs surfaces 14 total
including the pre-existing zone/project starters.
2026-05-09 13:40:18 -07:00
Kelsi
c0ea12d2cb feat(editor): add --gen-mesh-lantern hand-lantern primitive
81st procedural mesh primitive. Hand-held oil lantern via
4 stacked Y-axis cylinders:

  • base — short narrow disc (the foot the lantern sits on)
  • globe — wider taller cylinder (the glass enclosure
    holding the wick)
  • neck — narrow constriction above the globe (where the
    cap meets the body)
  • cap — wider short top (the metal hood that lets vent
    smoke escape)

Distinct from --gen-mesh-candle (just wax + saucer) and
--gen-mesh-urn (4-tier pottery shape) — lantern's
wide-narrow-wide tier sequence reads as a glass-enclosed
wick under a metal hood.

Useful for night-watch stations, ranger camps, dwarven
mine tunnels, mausoleum interiors, vigil scenes,
witch-hut shelves. Watertight under weld (verified 336
manifold edges, 0 boundary, 0 non-manifold).
2026-05-09 13:38:06 -07:00
Kelsi
67e1a7246e refactor(editor): adopt addClosedCylinderZ in bedroll + archery-target
Two more handlers retire their open-coded Z-axis cylinder
geometry and call addClosedCylinderZ instead:

  • --gen-mesh-bedroll: 60-line inline cylinder (side wall +
    end-cap fans) → 1 helper call.
  • --gen-mesh-archery-target: same 60-line block for the
    target face → 1 helper call. The unused local pi
    constant drops out as a side benefit.

Output bytes verified identical: bedroll surface area
1.6746 m² unchanged; archery-target 1.9504 m² unchanged.

Cylinder-helper rollout now complete on the Z-axis side:
woodpile (last batch) + bedroll + archery-target all use
addClosedCylinderZ. Future Z-axis tubes (e.g. mine-cart
axles, well-pail handles, scroll cases) opt in by
including cli_box_emitter.hpp.
2026-05-09 13:35:06 -07:00
Kelsi
b47649b078 feat(editor): add --gen-mesh-candle wax-pillar primitive
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.
2026-05-09 13:32:43 -07:00
Kelsi
4565ce29e9 refactor(editor): extract addClosedCylinderZ + adopt in woodpile
Z-axis sibling of addClosedCylinderY: emits a watertight tube
lying horizontally along the Z axis, centered at (cx, cy) on
the XY plane and spanning z=z0..z=z1. Mirrors the Y helper's
side-wall + ±Z cap-fan layout.

Refactor --gen-mesh-woodpile to use the new helper. Its
addLog lambda collapses from 60 lines of inline cylinder
geometry to a 3-line wrapper:

    auto addLog = [&](float cx, float cy) {
        addClosedCylinderZ(wom, cx, cy, logR, -halfL, +halfL, sides);
    };

Output bytes verified identical: woodpile surface area
3.3416 m² unchanged.

Establishes the helper for future Z-axis cylindrical
primitives — bedroll and archery-target (which currently
inline similar code) can adopt it next.
2026-05-09 13:29:07 -07:00
Kelsi
60470eaa35 feat(editor): add --gen-mesh-urn 4-tier pottery vessel
79th procedural mesh primitive. Vertical urn built from 4
stacked tiers using the new addClosedCylinderY helper:

  • foot — wide short cylinder (the ground-meeting base)
  • body — tall main cylinder (the storage volume)
  • neck — narrow constriction
  • lip — slightly wider rim cap

Each tier is an independent watertight Y-axis cylinder with
its own ±Y end caps; they're stacked vertically with each
sitting on the previous tier's top.

Useful for temple offerings, mausoleum interiors, kitchen
storage, alchemist labs, funerary scenes, witch-hut detail.
First handler to consume the new addClosedCylinderY helper —
demonstrates the pattern for future cylindrical primitives
(candle, lantern, scroll case, well-pail, etc.).

Watertight under weld (verified 432 manifold edges, 0
boundary, 0 non-manifold).
2026-05-09 13:27:06 -07:00
Kelsi
81f605617a 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.
2026-05-09 13:24:12 -07:00
Kelsi
8adbeb237d feat(editor): add --gen-mesh-planter-box garden flowerbox
78th procedural mesh primitive. Long open-top wooden planter
with a visible soil-fill block:

  • 5-piece basin construction (bottom slab + 4 perimeter
    walls), same arrangement as --gen-mesh-water-trough
  • soil block filling the inner cavity from floor up to
    soilTopFrac × height (default 85%), slightly inset from
    walls so it visually sits "inside" the wood

Distinct from --gen-mesh-water-trough (square basin without
a soil-fill block) — planter-box is the long elongated
garden variant. Useful for window sills, kitchen herb
gardens, balcony planters, monastery cloister-walk
container plants, market-stall produce display trays.

The 6 non-manifold edges (--info-mesh-stats) are at the
4-wall corners — same expected pattern as water-trough,
where the inner walls share corner positions with each
other.

Milestone: kArgRequired now reaches 411 documented flags.
2026-05-09 13:21:03 -07:00
Kelsi
e95e983988 feat(editor): add --gen-tavern-pack composite
Eighth themed mesh pack after camp / blacksmith / village /
temple / graveyard / garden / dock. Inn/tavern scene
emitting a 7-primitive watering-hole layout:

  • house — the inn building
  • chimney — above the kitchen hearth
  • table — the common-room centerpiece
  • bench — seating
  • barrel — ale / cider stock
  • bookshelf — back-wall record / register / library
  • signpost — the inn-sign post out front

Together these form a minimum-viable tavern setup for any
roadside inn, town watering-hole, road-house mid-route stop.

Composite-pack catalogue: camp + blacksmith + village +
temple + graveyard + garden + dock + tavern = 8 themed mesh
packs. --list-packs surfaces 13 composites total
(including pre-existing zone/project starter packs).
2026-05-09 13:17:49 -07:00
Kelsi
234cfd0663 feat(editor): add --gen-mesh-bird-bath garden water-feature
77th procedural mesh primitive. Garden bird-bath:

  • stem: thin Y-axis closed cylinder (the column)
  • basin: wider shallow Y-axis closed cylinder on top

Both are full closed cylinders (side wall + ±Y cap fans) so
the model is a watertight solid. Local addYCylinder lambda
emits each tier — keeps the per-handler code self-contained
without forcing yet another module-level helper.

Distinct from --gen-mesh-fountain (basin + spout column,
larger water feature) — bird-bath is the small ornamental
garden version. Useful for monastery courtyards, druid
sanctuaries, noble-house pleasure gardens, sanctuary
clearings, ranger huts.

Watertight under weld (verified 192 manifold edges, 0
boundary, 0 non-manifold).
2026-05-09 13:16:06 -07:00
Kelsi
f167e1d2cf feat(editor): add --gen-mesh-statue-base monument pedestal
76th procedural mesh primitive. Classic 3-tier square
pedestal for statues / monuments / hero memorials:

  • plinth — bottom block, plinthExtra wider per side than
    the body (the foundation that meets the ground)
  • body — tall central pedestal sitting on the plinth
    (where the inscription would go)
  • capital — small wider cap on top of the body (where
    the statue's feet would rest)

Distinct from --gen-mesh-podium (stepped pyramid with
lectern at back) and --gen-mesh-altar (stacked round
discs) — this is the classic single-statue square
pedestal shape.

Useful for town-square monuments, hero-of-the-revolution
memorials, plaza centerpieces, mausoleum interiors,
saint-statue stands. Watertight under weld (verified 54
manifold edges, 0 boundary, 0 non-manifold).
2026-05-09 13:11:08 -07:00
Kelsi
bdeb93bedd feat(editor): add --gen-dock-pack composite
Seventh themed mesh pack after camp / blacksmith / village /
temple / graveyard / garden. Harbor / dockyard scene
emitting a 7-primitive port-side layout:

  • dock — the pier itself
  • crate-stack — cargo waiting to be loaded
  • barrel — sailor stash
  • canopy — shade for the dockmaster's station
  • bench — passenger waiting area
  • signpost — port marker
  • hitching-post — for tying up small boats / mounts

Together these form a recognizable harbor / dockside
pickup-and-drop area when arranged in a zone.

Composite-pack catalogue: camp + blacksmith + village +
temple + graveyard + garden + dock = 7 themed mesh packs.
2026-05-09 13:06:37 -07:00
Kelsi
074f299c8b feat(editor): add --gen-mesh-pillar-row colonnade primitive
75th procedural mesh primitive. Row of N stone pillars
evenly spaced along the X axis. Each pillar is a single
tall rectangular box optionally crowned by a slightly-wider
square cap (capExtra wider per side).

End pillars inset by pillarW so they sit at exactly the
±span/2 ends. Set capH=0 for bare un-crowned columns.

Useful for ruined temples, palace colonnades, dungeon
hallways, dwarven mead-hall arcades, ceremonial entry
walks. Distinct from --gen-mesh-pillar (single column) —
this is the regular-multi-column composite, the third
"scene" primitive after --gen-mesh-crate-stack and
--gen-mesh-gravel-pile.

Watertight under weld (verified 144 manifold edges, 0
boundary, 0 non-manifold). Default 4-pillar / 4 m / 2.5 m
spacing reads as a small temple front porch.
2026-05-09 13:04:55 -07:00
Kelsi
b3755f8342 feat(editor): add --gen-garden-pack composite
Sixth themed mesh pack after camp / blacksmith / village /
temple / graveyard. Garden-courtyard scene emitting a
7-primitive monastery / kitchen-garden / small park layout:

  • pergola — vine-arbor frame
  • fountain — centerpiece
  • stone-bench — seating
  • shrine — decorative focal
  • beehive — productive prop
  • scarecrow — rural touch
  • well — water source

Together these form a recognizable contemplative-garden /
small park / monastery courtyard when arranged in a zone.

Composite-pack catalogue now: camp + blacksmith + village
+ temple + graveyard + garden = 6 themed mesh packs.
--list-packs surfaces 11 total composite flags.
2026-05-09 13:01:21 -07:00
Kelsi
b74c369efd feat(editor): add --gen-mesh-hitching-rail multi-post variant
74th procedural mesh primitive. Long horizontal hitching
bar held up by N evenly-spaced vertical posts. Distinct
from --gen-mesh-hitching-post (just 2 posts + bar) — this
is the longer multi-post variant for taverns, stockyards,
racecourse parking, market days, festival hitching lines.

Posts auto-spaced from -L/2 to +L/2, inset by postW so the
end posts align with the rail tips. Bar spans the full
length on top of the post tier.

Watertight under weld (verified 90 manifold edges, 0
boundary, 0 non-manifold). Default 4-post / 4 m rail at
1.2 m mount height fits typical mount silhouettes.

Milestone: kArgRequired now reaches 400 documented flags.
2026-05-09 12:59:16 -07:00
Kelsi
9bd9b50bdb feat(editor): add --gen-graveyard-pack composite
Fifth themed mesh pack after camp / blacksmith / village /
temple. Cemetery scene emitting a 7-primitive burial yard:

  • grave — filled plot mound
  • tombstone — marker stone
  • coffin — above-ground or open
  • statue — mourning angel / patron deity
  • stone-bench — for visitors and mourners
  • gravel-pile — loose earth from a fresh dig
  • cage — fence section / mausoleum gate

Together these form the standard town-edge burial yard
when arranged in a zone. Composite-pack catalogue now:
camp + blacksmith + village + temple + graveyard.

All 7 outputs validate clean. --list-packs surfaces 10
composite flags total (5 mesh packs + the pre-existing
zone/project starter packs).
2026-05-09 12:55:50 -07:00
Kelsi
85e02a6132 feat(editor): add --gen-mesh-mine-cart underground transport
73rd procedural mesh primitive. Open-top mining cart on
4 wheel boxes:

  • bin: 5-piece basin construction (bottom slab + 4
    perimeter walls), same arrangement as
    --gen-mesh-water-trough
  • wheels: 4 cube boxes at the corners, inset by
    wheelInset and sitting on the ground (y from 0 to
    2*wheelR)
  • bin sits on top of the wheels at y = 2*wheelR

All axis-aligned — exercises every shared helper. Useful
for mines, dwarven forges, gnomish junk-yards, abandoned-
tunnel set dressing, salvage scenes.

Default 0.9 × 0.5 m bin with 0.08-radius wheels gives the
classic narrow-gauge ore-cart silhouette. The 4-wall basin
construction (verified via --info-mesh-stats: 6 non-manifold
edges at the wall corners — same as --gen-mesh-water-trough's
expected branchy edges) means the cart can hold collision-
visible ore inside if a content author drops in another
primitive.
2026-05-09 12:54:17 -07:00
Kelsi
4e64f833a7 feat(editor): add --list-packs introspection + --gen-temple-pack
Two related changes:

  • --list-packs walks kArgRequired and surfaces every flag
    matching --gen-*-pack (excluding --gen-mesh-* and
    --gen-texture-* which are individual primitives). Sister
    command to --list-primitives. Auto-tracks new packs as
    they're added — no parallel registry.

  • --gen-temple-pack composite — temple/shrine ritual hall
    (altar + shrine + brazier + pillar + statue + portal +
    podium). Fourth themed mesh pack after camp / blacksmith /
    village.

list-packs now surfaces 9 composites including the pre-
existing zone-* / project-starter packs that match the
naming convention. All 7 temple-pack outputs validate clean.
2026-05-09 12:51:10 -07:00
Kelsi
c18d88015a feat(editor): add --gen-mesh-stone-bench heavy seating
72nd procedural mesh primitive. Long stone bench from 3
axis-aligned boxes:

  • horizontal seat slab spanning the full length on top
  • 2 vertical block supports near the ends, inset by
    supportInset from each end so the seat slab overhangs
    them slightly (the standard "seat rests on legs" look)

Distinct from --gen-mesh-bench (wooden 4-leg construction
with thinner pieces) — this is the heavier stone variant
for parks, temple courtyards, ruined cities, dwarven mead
halls, library-courtyard reading nooks.

Watertight under weld (verified 54 manifold edges, 0
boundary, 0 non-manifold). Default 2.0 m × 0.4 m bench at
0.45 m sit height reads at adult-human proportions.

Uses every shared helper added in recent batches —
new gen-mesh handlers now consistently land at ~50 lines
including parse + validate + geometry + finalization.
2026-05-09 12:48:55 -07:00
Kelsi
790fe64824 feat(editor): add --gen-village-pack composite
Third composite pack (after --gen-camp-pack and
--gen-blacksmith-pack), emitting a 7-primitive village-square
scene into <outDir>:

  • house — main dwelling
  • outhouse — privy beside it
  • chimney — for the house roof piece
  • hitching-post — at the hitching rail
  • well — village square centerpiece
  • signpost — pointing the way out of town
  • haystack — from the nearby farm

Together these form a recognizable rural-village hub when
arranged in a zone. Uses the existing emitMeshPack helper —
the new pack is just an 8-line declarative table.

All 7 outputs validate clean (--validate-wom PASSED across
the board). Composite-pack catalogue now: camp + blacksmith
+ village.
2026-05-09 12:45:25 -07:00
Kelsi
ca38f77fd0 feat(editor): add --gen-mesh-gravel-pile rubble heap
71st procedural mesh primitive. Hash-distributed pile of
stone cubes in a roughly conical heap. Each stone gets:

  • polar position (radial, theta) with sqrt(rand) on radial
    so stones aren't bunched at center
  • height limited by yMax = pileH * (1 - radial/baseR), so
    larger / more numerous stones land near the base and
    smaller ones perch on top — natural gravel-pile profile
  • size in the 40-100% range of maxStoneSize

The second multi-box "scene" composite primitive (after
--gen-mesh-crate-stack), but using irregular hashed
placement instead of a regular N×M×K grid. Deterministic
from seed: re-running with same args reproduces the
identical pile.

Useful for mine entrances, construction sites, quarries,
ruined walls, abandoned-fort rubble, pirate-cove stash
mounds. Default 24 stones at 0.6 m base radius gives a
reasonable medium-pile.

Uses every shared helper introduced this batch (printWomWrote,
printWomMeshStats, setCenteredBoundsXZ, addFlatBox,
saveWomOrError, parseOpt*, stripExt).
2026-05-09 12:43:03 -07:00
Kelsi
967cb0d12d refactor(editor): extract printWomWrote + printWomMeshStats
Two more print-pattern extractions for the gen-mesh side:

  • printWomWrote(base) — the "Wrote <base>.wom" success line
    every handler emits at the start of its stat report. 71
    sites collapsed.

  • printWomMeshStats(wom) — the canonical final 2-line pair
    "vertices : N" / "triangles : T" emitted at the end of
    every standard primitive's stat report. 49 sites collapsed.

Output bytes verified identical: firepit and tent surface
area / vertex / triangle counts unchanged.

The one remaining open-coded "Wrote" line is in
--gen-mesh-textured which writes both a .wom and a .png in
the same line — different signature, left unchanged.

These complete the print-helper trio (printPngWrote on the
texture side, printWomWrote + printWomMeshStats on the mesh
side). New primitives now end with one helper call instead
of three printfs.
2026-05-09 12:40:58 -07:00
Kelsi
26af6d9df6 feat(editor): add --gen-mesh-archery-target training prop
70th procedural mesh primitive. Round-faced archery target on
a 2-post stand:

  • face: closed cylinder along the Z axis (flat ±Z caps face
    the archer line, side wall is the rim) of radius faceR
    centered at height postH
  • stand: 2 vertical posts at the bottom, sized to reach
    from ground to the bottom of the face, with a horizontal
    cross-beam joining them just below the face for rigidity

Concentric scoring rings live in texture space, not geometry —
the cap discs are the natural canvas for a separate target-
ring texture (or use --gen-texture-rings / --gen-texture-
starburst on top).

Pairs naturally with --gen-mesh-training-dummy /
--gen-mesh-fence for sparring grounds, training yards,
militia drill squares, mid-summer fair scenes.

Watertight under weld (verified 198 manifold edges, 0
boundary, 0 non-manifold).
2026-05-09 12:35:15 -07:00
Kelsi
facaacd0c6 feat(editor): add --gen-blacksmith-pack + extract emitMeshPack
Two changes in one commit because they're co-designed:

  • Extract emitMeshPack(outDir, packName, items) as a shared
    helper that takes a vector of PackItem {flag, fn, leaf}
    and runs each handler with a synthetic argv. Keeps the
    "build a 2-element argv per sub-handler" wiring in one
    place instead of copy-pasted per pack.

  • Add --gen-blacksmith-pack on top: a smithy-themed
    composite emitting forge + anvil + workbench +
    water-trough + crate-stack + hitching-post into outDir.
    The classic forge-floor layout in 6 .wom files.

Refactors --gen-camp-pack to use emitMeshPack (verified
unchanged: 6 .wom files still land in the camp dir).
Pattern is now established for future packs (--gen-village-
pack, --gen-temple-pack, --gen-dock-pack…) — each is just a
list of primitives in 8 lines of code.
2026-05-09 12:31:34 -07:00
Kelsi
006beffb83 feat(editor): add --gen-mesh-forge blacksmith hearth
69th procedural mesh primitive. Built from up to 3 axis-
aligned boxes:

  • stone hearth: rectangular base spanning width × depth ×
    baseH, where the smith's fire would sit
  • hood: smaller smoke-collector box on top of the hearth,
    inset by hoodInset on each side so it reads as the
    chimney's flared base
  • optional thin chimney rising from the hood — set
    chimneyH=0 for an open-vented forge

Pairs naturally with --gen-mesh-anvil and
--gen-mesh-workbench for blacksmith/armorer/farrier scenes,
and with --gen-mesh-bench / --gen-mesh-crate-stack for
the storage corner of a smithy.

Watertight under weld (verified 54 manifold edges, 0
boundary, 0 non-manifold). Default 1.4×1.0×0.9 m hearth +
0.5 m hood + 1.2 m chimney reads as a normal village smithy.
2026-05-09 12:29:09 -07:00
Kelsi
68db1be97a feat(editor): add --gen-mesh-outhouse small-shed primitive
68th procedural mesh primitive. Built from 3 axis-aligned
boxes:

  • solid body slab spanning width × depth × (height-roofT)
  • thin door slab on the +Z face (pushed slightly outward
    so it visually sits on the wall — gives doorframe-like
    relief without a real cutout)
  • roof slab slightly larger than the body footprint and
    sitting on top, controlled by roofOverhang

Distinct from --gen-mesh-house (multi-walled, peaked-roof
dwelling). An outhouse is the single-room privy / tool shed
variant — useful for villages, frontier outposts, small
farms, refuse-shed corners.

Watertight under weld (verified 54 manifold edges, 0
boundary, 0 non-manifold). First handler to use the new
setCenteredBoundsXZ helper from this batch.
2026-05-09 12:24:00 -07:00
Kelsi
0b1cf65854 feat(editor): add --gen-camp-pack convenience composite
Convenience composite command: emits a complete outdoor-camp
scene (tent + firepit + bedroll + canopy + woodpile +
haystack) into <outDir>/ as 6 .wom files in one invocation.

Internally builds a synthetic argv array per primitive and
calls each existing handler — so the camp pack always
matches the per-primitive defaults exactly. Users wanting
custom dimensions should call the individual --gen-mesh-*
commands.

Smoke tested:
  • Creates outDir if needed
  • All 6 .wom files validate clean (--validate-wom PASSED)
  • Vertex / triangle counts match per-primitive defaults
    (firepit 240/120, woodpile 324/288, etc.)

The first composite command in the editor — establishes the
pattern for future packs (--gen-village-pack, --gen-dock-pack,
etc.) by showing how synthetic argv arrays let one handler
delegate to many others without spawning subprocesses.
2026-05-09 12:22:37 -07:00
Kelsi
4573ff6c9f refactor(editor): extract setCenteredBoundsXZ helper
21 procedural mesh handlers used the same two-line bounds
stanza for primitives whose footprint is symmetric in X+Z
and rises from y=0:

    wom.boundMin = glm::vec3(-halfX, 0, -halfZ);
    wom.boundMax = glm::vec3( halfX, maxY,  halfZ);

Hoist into cli_box_emitter.hpp as inline setCenteredBoundsXZ
(WoweeModel&, halfX, halfZ, maxY). Each call site collapses
to one line.

Output bytes verified identical: firepit bbox 1.200 × 0.200
× 1.200 unchanged, haystack bbox 1.200 × 0.900 × 1.200
unchanged. Asymmetric bounds (e.g. handlePodium with
different X and Z extents on the min/max lines) are
deliberately left untouched — the helper only matches the
common symmetric case.
2026-05-09 12:18:26 -07:00
Kelsi
38d9575eb2 feat(editor): add --gen-mesh-hitching-post stable fixture
67th procedural mesh primitive. Standard town/stable
hitching post:

  • two vertical posts separated by `span`
  • horizontal cross-bar at upper-post height (length =
    span - postW so it tucks INSIDE the post inner faces,
    matching real fence joinery)
  • optional decorative caps on each post — set capH=0
    for a bare working-yard post

All axis-aligned boxes, exercises every shared helper
(stripExt, initWomDefaults, addFlatBox,
finalizeAsSingleBatch, saveWomOrError). Watertight under
weld (verified 90 manifold edges, 0 boundary, 0
non-manifold).

Useful for stables, taverns with mount parking, town
squares, frontier outposts, ranger camps, post-and-rail
fence segments.
2026-05-09 12:15:41 -07:00
Kelsi
b311916c6c feat(editor): add --gen-mesh-training-dummy sparring prop
66th procedural mesh primitive. Combat training dummy from
axis-aligned boxes:

  • vertical post from ground to baseH (the stand)
  • cubic torso block stacked on top of the post
  • horizontal cross-bar arms at upper-third torso height
    spanning armSpan along the X axis
  • optional head cube above the torso (set headSize=0
    for a headless training-block style)

Pairs with --gen-mesh-anvil / --gen-mesh-workbench /
--gen-mesh-fence for sparring grounds, militia drill
squares, training yards, weapon-master compounds. Useful
for any "things-to-hit" set dressing where animated NPCs
would target a static prop.

Watertight under weld (verified 72 manifold edges, 0
boundary, 0 non-manifold). Default 1.0 m post + 0.4 m
torso + 0.18 m head reads at adult-human height.
2026-05-09 12:09:36 -07:00
Kelsi
4cb1c33335 feat(editor): add --gen-mesh-water-trough basin primitive
65th procedural mesh primitive. Open-top rectangular basin
built from 5 axis-aligned boxes (uses every shared helper
from cli_box_emitter):

  • bottom slab spanning full footprint at wallT thickness
  • +X / -X side walls spanning full length
  • +Z / -Z front/back walls spanning the inner length so
    they tuck inside the side walls without overlap

Inner cavity (length-2*wallT × height-wallT × width-2*wallT)
is the open water/feed volume. Useful for stables, farmsteads,
taverns, stockyards, alchemist mixing basins.

Default 1.4 × 0.5 × 0.5 m is a reasonable horse-trough size;
configurable for smaller hand-basins or larger livestock
troughs.
2026-05-09 12:02:35 -07:00
Kelsi
c10b1b0fb1 feat(editor): add --gen-mesh-watchpost sentry tower primitive
64th procedural mesh primitive. A simple scout/sentry
watchpost from axis-aligned boxes:

  • tall central pole rising from ground to postH
  • square platform slab on top of the pole, wider than
    the pole so it overhangs as the lookout deck
  • 4 corner railing posts on the platform — set
    railingH=0 for a bare deck

Distinct from --gen-mesh-tower (round castle tower with
crenellated battlements). A watchpost is the rough scout/
forward-outpost variant — just wood and rope, no masonry.

Pairs naturally with --gen-mesh-tent / --gen-mesh-firepit
for camp + lookout scenes, and --gen-mesh-fence for
perimeter setups. Watertight under weld (verified
108 manifold edges, 0 boundary, 0 non-manifold).
2026-05-09 11:57:38 -07:00
Kelsi
783b0f167f refactor(editor): extract initWomDefaults + saveWomOrError
Two more boilerplate patterns repeated across cli_gen_mesh.cpp:

  • init: 64 sites set wom.name = path-stem and wom.version = 3
    in 3 lines. Hoisted to initWomDefaults(wom, base) — 1 line.

  • save: 65 sites had identical 5-line "if save fails, fprintf
    stderr and return 1" blocks. Hoisted to saveWomOrError(wom,
    base, cmdName) returning bool, used as
    `if (!saveWomOrError(...)) return 1;` — also 1 line.

Net cli_gen_mesh.cpp drops by ~254 lines (from 6424 to 6170).
Output bytes verified identical: firepit surface area 2.1100
m² unchanged, cube vertex/index/bounds counts unchanged.

After this batch's prior helpers (addFlatBox, addVertex,
stripExt, finalizeAsSingleBatch, parseOpt*), a typical new
gen-mesh handler now needs zero copy-paste boilerplate to
get a working primitive — every shared bit lives in
cli_box_emitter.hpp and cli_arg_parse.hpp.
2026-05-09 11:54:35 -07:00
Kelsi
a10efed8f5 feat(editor): add --gen-mesh-crate-stack scene primitive
63rd procedural mesh primitive. The first to explicitly
compose a *scene* of multiple objects rather than a single
structure: an N×M×K arrangement of cube crates with a small
gap between each so they read as discrete shipping boxes
rather than one merged solid.

Default 2×2×2 = 8 crates with 0.40 m sides on a 0.02 m gap
gives a tight pyramidal pile. Larger configurations (e.g.
4×3×2 = 24 crates) read as warehouse pallets.

Uses every shared helper added this batch — addFlatBox for
each crate, finalizeAsSingleBatch for the trailing batch,
stripExt for the .wom suffix, parseOptInt/Float for the
optional dimension args. Each crate emits the standard
24-vert / 12-tri box, so an N×M×K stack is a clean
N*M*K-multiple in vertices and triangles.

Useful for warehouses, cargo holds, dock loading bays,
market stalls, dwarven mining caches, pirate stash piles.
Watertight under weld — the gap keeps adjacent crates
from sharing corner positions.
2026-05-09 11:52:20 -07:00
Kelsi
02503e87df refactor(editor): extract finalizeAsSingleBatch helper
53 procedural mesh primitives ended with the same 5-line
batch-finalization boilerplate:

    wowee::pipeline::WoweeModel::Batch batch;
    batch.indexStart = 0;
    batch.indexCount = static_cast<uint32_t>(wom.indices.size());
    batch.textureIndex = 0;
    wom.batches.push_back(batch);

Hoist into cli_box_emitter.hpp as inline finalizeAsSingleBatch
(WoweeModel&). Each call site collapses to:

    finalizeAsSingleBatch(wom);

cli_gen_mesh.cpp drops by ~210 lines. Output bytes verified
identical: firepit batch idx 0 / iStart 0 / iCount 360 /
opaque blend, surface area 2.1100 m² unchanged.

Procedural builders only ever emit one batch per primitive;
multi-batch primitives (none currently exist) would still
be free to construct batches manually.
2026-05-09 11:50:29 -07:00
Kelsi
9347290335 refactor(editor): extract stripExt into cli_box_emitter
64 sites in cli_gen_mesh.cpp open-coded the same 4-line
"strip .wom suffix from base path if user typed it" pattern:

  if (womBase.size() >= 4 &&
      womBase.substr(womBase.size() - 4) == ".wom") {
      womBase = womBase.substr(0, womBase.size() - 4);
  }

Hoist into cli_box_emitter.hpp as inline stripExt(string&,
const char*). Generic on the suffix string so future callers
can use it for ".wob" / ".woc" / ".png" too — uses
string::compare instead of substr to avoid the temporary
allocation the original did per check.

Each call site collapses to a single line:
  stripExt(womBase, ".wom");

cli_gen_mesh.cpp drops by ~96 lines. Output bytes verified
identical: firepit surface area 2.1100 m² unchanged.
2026-05-09 11:48:03 -07:00
Kelsi
47641bf0b4 feat(editor): add --gen-mesh-workbench crafter table
62nd procedural mesh primitive. A blacksmith / crafter
workbench built from axis-aligned boxes (uses the new
cli_box_emitter helper plus the new cli_arg_parse helpers
for all 7 optional dimension args):

  • flat top slab spanning length × depth at topT thickness
  • 4 corner legs sized so their outer faces line up with
    the top edges (post-style, no overhang)
  • optional vise box at the +X end of the top — set
    viseSize=0 for a bare bench
  • optional raised tool tray along the +Z back edge —
    set trayH=0 to omit

Pairs naturally with --gen-mesh-anvil (existing) for
blacksmith forge scenes, and --gen-mesh-cauldron for
alchemist labs. Default 1.6×0.7×0.85 m bench reads at
realistic adult-human working height.

Watertight under weld (verified via --info-mesh-stats:
126 manifold edges, 0 boundary, 0 non-manifold).
2026-05-09 11:46:05 -07:00
Kelsi
56c12bc252 refactor(editor): extract optional-arg parse helpers
Some checks are pending
Build / Build (arm64) (push) Waiting to run
Build / Build (x86-64) (push) Waiting to run
Build / Build (macOS arm64) (push) Waiting to run
Build / Build (windows-arm64) (push) Waiting to run
Build / Build (windows-x86-64) (push) Waiting to run
Security / CodeQL (C/C++) (push) Waiting to run
Security / Semgrep (push) Waiting to run
Security / Sanitizer Build (ASan/UBSan) (push) Waiting to run
Every --gen-texture-* and --gen-mesh-* handler had its own
copy of the same 3-line "if there's another arg AND it
doesn't look like a switch, parse it; otherwise keep the
default" block. 458 sites across cli_gen_texture.cpp and
cli_gen_mesh.cpp duplicated this pattern.

Hoist into cli_arg_parse.hpp as inline parseOpt{Int,Float,Uint}
(int& i, int argc, char** argv, T& value). Each call site
collapses from 3 lines to 1:

  if (i + 1 < argc && argv[i + 1][0] != '-') {
      try { width = std::stof(argv[++i]); } catch (...) {}
  }

becomes

  parseOptFloat(i, argc, argv, width);

cli_gen_mesh.cpp drops by ~250 lines, cli_gen_texture.cpp
by ~430 lines. Output bytes verified identical: firepit
default-arg surface area 2.1100 m² unchanged.

Future texture/mesh primitives now opt in by including one
header instead of pasting the lambda.
2026-05-09 11:42:55 -07:00
Kelsi
e23b3faa1c feat(editor): add --gen-mesh-bedroll camp sleeping prop
61st procedural mesh primitive. Builds a bedroll prop:

  • horizontal closed cylinder along the Z axis sitting at
    y = R so it rests on the ground (radius defaults to
    0.16 m, length 1.4 m — adult-human-shaped)
  • optional pillow box at the +Z end (squashed cube,
    pillowSize controls extent)
  • per-segment N-sided cylinder uses the new addVertex
    helper; pillow uses addFlatBox

Set pillowSize=0 for a bare rolled mat. Watertight under
weld (verified via --info-mesh-stats: 90 manifold edges,
0 boundary, 0 non-manifold).

Pairs naturally with --gen-mesh-tent / --gen-mesh-firepit /
--gen-mesh-canopy for outdoor camp scenes — completes the
tent-side camping kit.
2026-05-09 11:39:33 -07:00
Kelsi
836fab7f2d feat(editor): add --gen-mesh-chimney brick-stack primitive
60th procedural mesh primitive. Builds a chimney as two
axis-aligned boxes (uses the shared addFlatBox helper):

  • main shaft: rectangular brick stack of width × depth ×
    (height - capH)
  • protective cap: a slightly wider box on top (capExtra
    wider than shaft on every side) — the rain-throw crown
    that sits on real masonry chimneys

Set capH=0 for a bare unfinished stack. Default 0.45×0.45×
1.8 m shaft with a 0.10-tall cap reads at human scale for
rooftop placement.

Useful for cottage rooflines, blacksmith forges, alchemist
labs, dwarven longhouses — any structure with a fireplace
that needs visible vent. Watertight under weld (verified
via --info-mesh-stats: 36 manifold edges, 0 boundary, 0
non-manifold).
2026-05-09 11:34:24 -07:00
Kelsi
849aaeb7e0 refactor(editor): extract addVertex helper from 21 cli_gen_mesh sites
After the addBox extraction, 21 procedural mesh primitives
each still open-coded the same 5-line addV vertex-emit lambda.
Add inline addVertex(WoweeModel&, vec3, vec3, vec2) to
cli_box_emitter.hpp (header so the per-vertex hot loop stays
inlineable), plus a per-float overload for the four handlers
(--gen-mesh-stairs, --gen-mesh-tube, --gen-mesh-capsule,
--gen-mesh-arch) that compute pos/normal/uv components inline
rather than building intermediate glm vectors.

Each lambda site collapses from a 5-line body to a 3-line
forwarding wrapper. Output bytes verified identical via
--info-mesh-stats: firepit surface area 2.1100 m² unchanged
across all variants tested (stairs/tube/capsule/arch/
firepit/mushroom).

Sets up the pattern so the next "shared mesh helper" is one
line of include, not another paste-in.
2026-05-09 11:29:52 -07:00
Kelsi
6ff5df41eb feat(editor): add --gen-mesh-pergola garden-arbor primitive
59th procedural mesh primitive. Builds an open-top pergola
from axis-aligned boxes (uses the new addFlatBox helper):

  • 4 corner posts sized so their outer faces line up with
    the footprint perimeter
  • 2 long perimeter beams resting on top of the posts
    along the long edges
  • N cross beams running between the perimeter beams
    (slightly thinner so the lattice has visible crossings)

Distinct from --gen-mesh-canopy because there's no closed
overhead panel — the open lattice top reads as a sun-trellis
or vine-arbor instead of a market-stall awning. Useful for
palace courtyards, druid groves, garden mazes, viewing
arbors, harvest-festival entrance pieces.

Set crossbeams=0 for a bare 4-post + 2-beam frame; default
5 crossbeams gives a pleasing alternating pattern.
2026-05-09 11:26:15 -07:00