HardYards/tools/assetcheck/README.md
m3ultra 78c98aed64 Bake joined-node rotations so bounding boxes are tight
Verifying the GLBs in three.js (not just Blender) showed four assets reporting
inflated bounds: tramp_01 came back 3.29 x 1.27 m against a true 2.96 x 0.78.

Box3.setFromObject expands each mesh's LOCAL box by the world matrix, so a node
carrying a rotation over-reports — the same trap Blender's obj.bound_box sets,
and what three uses for frustum culling. Joined nodes inherited parts[0]'s
rotation, which for an arc is half a segment step off-axis.

Applying rotation at join time makes every local box axis-aligned, so the
default Box3 path is now correct for consumers and culling is tight. World
geometry is unchanged; the exported dims are identical.

Adds tools/assetcheck/, the three.js harness that caught this. It's the only
check that can: a Blender round-trip exports Z-up->Y-up and imports Y-up->Z-up,
so a broken export_yup flips back and passes green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 21:43:06 +10:00

2.3 KiB
Raw Permalink Blame History

assets_in_three.html — verify the GLBs in the real consumer

build_yard_assets.py already re-imports every GLB into Blender and asserts dims, tri budget, and node names. That is necessary but structurally cannot catch an axis error: Blender exports Z-up→Y-up and imports Y-up→Z-up, so a broken export_yup flips back on the way in and round-trips green. Only something that reads glTF natively can prove the file is right.

This page is that check. It loads each GLB with three.js r175's GLTFLoader and asserts:

  • Y-up: a Blender asset measuring (dx, dy, dz) must arrive as (dx, dz, dy).
  • node survival: branch_anchor_*, fascia_anchor_*, plants_* etc. still exist after the export — glTF has no "empty", anchors arrive as bare Object3D, and exporters have been known to prune childless nodes.
  • anchors are usable: branch_anchor_01 resolves to a sane world position with height on +Y.

Expectations are read from tools/blender/asset_report.json, so this stays in sync with the factory automatically.

Why it uses the default (non-precise) Box3

THREE.Box3.setFromObject(obj) expands each mesh's local bounding box by the world matrix, so a node carrying a rotation reports an inflated box. That is the same trap Blender's obj.bound_box sets, and it is what three uses for frustum culling. join_group() therefore bakes rotation into the vertices so the local box is axis-aligned and tight. Passing precise: true here would hide exactly the regression this page exists to catch — so don't.

Before that fix, three reported tramp_01 as 3.29 × 1.27 m against a true 2.96 × 0.78 m.

Running it

Needs three.js on /world/vendor/ and the models on /models/. Once Lane A's server.py lands, serve web/ and this can move next to selftest.html (Lane A: happy to fold it in — see THREADS).

Until then, the standalone recipe:

D=$(mktemp -d) && mkdir -p "$D/world"
ln -s ~/Documents/90sDJsim/web/world/vendor        "$D/world/vendor"
ln -s "$PWD/web/world/models"                      "$D/models"
ln -s "$PWD/tools/blender/asset_report.json"       "$D/asset_report.json"
cp tools/assetcheck/assets_in_three.html           "$D/index.html"
python3 -m http.server 8805 --directory "$D"
# open http://127.0.0.1:8805 — look for "SUMMARY: ALL PASS IN THREE.JS"