diff --git a/tools/assetcheck/README.md b/tools/assetcheck/README.md new file mode 100644 index 0000000..0f194e5 --- /dev/null +++ b/tools/assetcheck/README.md @@ -0,0 +1,50 @@ +# 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: + +```sh +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" +``` diff --git a/tools/assetcheck/assets_in_three.html b/tools/assetcheck/assets_in_three.html new file mode 100644 index 0000000..3afa682 --- /dev/null +++ b/tools/assetcheck/assets_in_three.html @@ -0,0 +1,82 @@ + + +
loading…+ + + diff --git a/tools/blender/build_yard_assets.py b/tools/blender/build_yard_assets.py index 93f36e9..16c950e 100644 --- a/tools/blender/build_yard_assets.py +++ b/tools/blender/build_yard_assets.py @@ -280,6 +280,17 @@ def join_group(objs, name, parent=None): print(f" ! join failed for {name}: {e}") res = _active() res.name = name + # Bake the rotation objs[0] contributed into the vertices, so the node's + # LOCAL box is world-axis-aligned and therefore tight. + # + # This is not cosmetic. THREE.Box3.setFromObject() (and Blender's + # obj.bound_box, and three's frustum culling) expand the LOCAL bounding box + # by the world matrix. A joined node inherits objs[0]'s rotation — for an + # arc, that's half a segment step off-axis — so every consumer computing + # bounds the normal way would over-report these assets by ~11% and cull + # them late. Verified against three.js r175: without this, Box3 reports + # tramp_01 as 3.29 x 1.27 instead of its true 2.96 x 0.78. + _apply_transform(res, rotation=True, scale=True) if parent is not None: res.parent = parent return res diff --git a/tools/blender/contact_sheet.png b/tools/blender/contact_sheet.png index 0e31d12..b603db2 100644 Binary files a/tools/blender/contact_sheet.png and b/tools/blender/contact_sheet.png differ diff --git a/web/world/models/carabiner_v1.glb b/web/world/models/carabiner_v1.glb index 3f32677..a4ff4f2 100644 Binary files a/web/world/models/carabiner_v1.glb and b/web/world/models/carabiner_v1.glb differ diff --git a/web/world/models/debris/tramp_01_v1.glb b/web/world/models/debris/tramp_01_v1.glb index 5faec4b..1794d76 100644 Binary files a/web/world/models/debris/tramp_01_v1.glb and b/web/world/models/debris/tramp_01_v1.glb differ diff --git a/web/world/models/garden_bed_v1.glb b/web/world/models/garden_bed_v1.glb index c76d046..accfc9d 100644 Binary files a/web/world/models/garden_bed_v1.glb and b/web/world/models/garden_bed_v1.glb differ diff --git a/web/world/models/gate_v1.glb b/web/world/models/gate_v1.glb index 2efb33c..2c84e44 100644 Binary files a/web/world/models/gate_v1.glb and b/web/world/models/gate_v1.glb differ diff --git a/web/world/models/house_yardside_v1.glb b/web/world/models/house_yardside_v1.glb index b41a64d..547a98c 100644 Binary files a/web/world/models/house_yardside_v1.glb and b/web/world/models/house_yardside_v1.glb differ diff --git a/web/world/models/shackle_v1.glb b/web/world/models/shackle_v1.glb index f55952f..08e0739 100644 Binary files a/web/world/models/shackle_v1.glb and b/web/world/models/shackle_v1.glb differ diff --git a/web/world/models/shed_01_v1.glb b/web/world/models/shed_01_v1.glb index ceb0e14..8cceea5 100644 Binary files a/web/world/models/shed_01_v1.glb and b/web/world/models/shed_01_v1.glb differ diff --git a/web/world/models/tree_gum_01_v1.glb b/web/world/models/tree_gum_01_v1.glb index 980831a..f88b844 100644 Binary files a/web/world/models/tree_gum_01_v1.glb and b/web/world/models/tree_gum_01_v1.glb differ diff --git a/web/world/models/tree_gum_02_v1.glb b/web/world/models/tree_gum_02_v1.glb index 4130523..e0705a7 100644 Binary files a/web/world/models/tree_gum_02_v1.glb and b/web/world/models/tree_gum_02_v1.glb differ