Commit graph

6 commits

Author SHA1 Message Date
Kelsi
869124fa96 refactor(editor): extract vertex weld into shared cli_weld utility
Three callers were each open-coding the same quantize-and-
bucket pass over vertex positions: --info-mesh-stats,
--info-wob-stats, and --bake-wom-collision. Move the
implementation to cli_weld.{hpp,cpp} as buildWeldMap() and
have each caller pass a flat positions array.

Identical std::map-based exact-equality keying preserved
(unbalanced-hash collisions remain absent). All three call
sites verified to produce byte-identical output:

  • info-mesh-stats firepit: 240→80 verts, 0 boundary
  • info-wob-stats cube:     8→8 verts, watertight YES
  • bake-wom-collision tent: 18→6 verts, 8-tri WOC

About 75 lines of duplication removed; future weld-using
commands now opt in by including one header.
2026-05-09 11:05:54 -07:00
Kelsi
4112b6d257 feat(editor): --info-wob-stats + fix weld hash collision bug
Add --info-wob-stats reporting per-group + aggregate
triangle counts, surface area, edge analysis, and watertight
check for WOB buildings. Same flag surface as --info-mesh-stats
including --weld <eps> for true topological closure check
on per-face-vertex meshes.

Also fixes a correctness bug in the weld implementation
of --info-mesh-stats: the previous code used a 64-bit hash
of the quantized position as the equality key, which gave
false-positive collisions that incorrectly merged distinct
vertices. A unit cube's 8 corners collapsed to 2 positions
under the buggy hash. Replace with std::map keyed on the
actual quantized (qx, qy, qz) tuple so equality is exact.

Re-verified: cube 8→8 watertight YES; firepit 240→80
watertight YES (was wrongly reporting 56 unique with 48
non-manifold edges); tent_solid 18→6 watertight YES;
tent_fixed 21→9 with 5 boundary edges at the door perimeter
(correct — door is intentionally open).
2026-05-09 10:57:22 -07:00
Kelsi
4455a4eb6f feat(editor): --info-mesh-stats --weld <eps> for true topology check
Procedural primitives use per-face vertex layouts so flat
shading works (each box face gets its own 4 corners with a
unique normal). That makes them read as "not watertight" at
the index level even when the geometry is visually closed.

The new --weld <eps> flag quantizes vertex positions onto a
1/eps grid, hashes each cell, and remaps every duplicate to
the canonical (lowest-index) representative before edge
analysis. Edges that cross weld boundaries are then unified
and the manifold check measures topological closure of the
underlying surface.

Validates the full plumbing: tent_solid welds 18 verts → 6
positions and reports YES watertight; tent (with door) welds
21 → 9 and reports the 5-edge door seam as open boundary;
firepit welds 240 → 56 with 0 boundary but 48 non-manifold
edges — the 4-stone corners where adjacent ring stones share
a position, making the surface branchy.
2026-05-09 10:49:28 -07:00
Kelsi
5e404a9fe6 feat(editor): add --info-mesh-stats geometric audit
Reports total surface area, per-triangle area histogram
(min/max/mean/median), edge analysis (boundary / manifold /
non-manifold counts), watertight check, and degenerate
triangle count for a single WOM.

Watertightness here is the topological notion: every edge
must be shared by exactly 2 triangles via shared vertex
indices. This is what collision bakes and physics queries
actually need — visually-closed primitives whose adjacent
faces don't weld vertices will (correctly) report as
non-watertight.

Already caught a real defect in handleTent's door-fan
triangulation: the fan covers the door cutout area with
a stray triangle and leaves a vertex unreferenced.
Edge analysis is gated by triCount <= 2M to keep the
unordered_map bounded for huge baked terrain meshes.
2026-05-09 10:41:58 -07:00
Kelsi
25660a0d8b refactor(editor): move 2 more mesh-info handlers into cli_mesh_info.cpp
Adds --info-mesh-storage-budget and --info-project-models-total
to the existing cli_mesh_info module so all 5 mesh-aggregate
info handlers live together. The first computes per-category
byte breakdowns for a single WOM (positions / normals / UVs /
indices / bones / animations); the second walks every zone in
a project for an aggregate WOM/WOB rollup with per-zone rows.

main.cpp shrinks by 250 lines (1,437 to 1,187). All five
handlers preserve --json output for capacity-planning pipelines.
2026-05-09 09:49:39 -07:00
Kelsi
710fdf9b35 refactor(editor): extract WOM/WOB info handlers into cli_mesh_info.cpp
Moves three mesh-aggregate info handlers (--info-zone-models-total,
--list-zone-meshes-detail, --info-mesh) out of main.cpp into a
new cli_mesh_info.{hpp,cpp} module. The first aggregates
WOM/WOB stats across a zone; the second tabulates per-mesh
metrics sorted by triangle count; the third dumps single-WOM
detail (bounds, version, batches, bones, animations, textures).

main.cpp shrinks by 285 lines (1,826 to 1,541). All three
preserve --json output for capacity-planning pipelines.
2026-05-09 09:40:03 -07:00