diff --git a/THREADS.md b/THREADS.md index b562495..5efd768 100644 --- a/THREADS.md +++ b/THREADS.md @@ -15,5 +15,7 @@ Format: `[lane letter] YYYY-MM-DD — note` [E] 2026-07-16 — contract, garden_bed: all 3 damage states ship in ONE glb as sibling nodes `plants_full` / `plants_tattered` / `plants_dead` (full visible, rest hide_render). Lane A: toggle .visible, don't reload — instant swap, no pop-in. Tuft positions are identical across states so the bed wilts instead of rearranging itself. [E] 2026-07-16 — debris in web/world/models/debris/, copied verbatim from the library and scale-checked: BlueCrate_v2 0.36×0.36×0.29, BlackTub_v2 + WhiteTub_v2 0.36×0.54×0.20, WoodenBin_v2 0.35×0.36×0.31 m — all plausible real-world sizes. Plus tramp_01_v1.glb (2.96×2.96×0.78, mass_hint 45) because every Australian storm produces one airborne trampoline. NOTE: §0's *_v1.glb rule beats §5-E's "tramp_01.glb" spelling — Lane C, glob the dir rather than hardcoding names. [E] 2026-07-16 — grass is a texture, not geometry (§5-E item 9): web/world/models/textures/grass_atlas.png, 512², 2×2 tufts, alpha. Lane A instances billboards from it. +[E] 2026-07-16 — ⚠️ FOR LANE A + LANE C, bounding boxes: `THREE.Box3.setFromObject(obj)` expands each mesh's LOCAL box by the world matrix, so a node carrying a rotation reports an inflated box — and that's what three uses for frustum culling. Blender's obj.bound_box has the identical trap (it cost me an hour of chasing phantom "failures"). Fixed at source: join_group() now bakes rotation into the vertices, so every node's local box is axis-aligned and tight. Before the fix three reported tramp_01 as 3.29×1.27 m vs its true 2.96×0.78. You can use the default Box3 safely now — but if you ever measure geometry yourself, measure VERTICES, not bound_box corners. +[E] 2026-07-16 — landed tools/assetcheck/ (README + assets_in_three.html): loads every GLB through three.js r175 GLTFLoader and asserts Y-up, node survival, and that branch_anchor_01 lands at a sane +Y world position. This exists because the Blender round-trip CANNOT catch an axis bug — it exports Z-up→Y-up and imports Y-up→Z-up, so a broken export_yup flips back and passes. Currently green: 16/16 + anchors, `branch_anchor_01` at (-0.96, 3.64, -1.46). Lane A: once server.py + vendor/ land, this wants to live next to selftest.html — happy for you to fold it in, it reads expectations from asset_report.json so it stays in sync by itself. [E] 2026-07-16 — added a root .gitignore (thumbs/, *.blend, *.fbx per §0). Unowned file — Lane A, fold it into yours if you'd rather own it. [E] 2026-07-16 — open q for Lane A: does house_yardside at 9.20 m wide sit right on the north edge of a 30×20 m yard, or do you want a wider/second variant? Cheap to re-cut — it's one function in the script. 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 @@ + + +Lane E — GLB check in the real consumer (three.js r175) + +

GLB verification — loaded by three.js GLTFLoader, not Blender

+
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