Refit the backpack straps for both torsos (ADR-0018)
Full build / Import + headless tests (push) Successful in 44s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-06 13:43:52 +01:00
1 parent cd285b3a39
commit fb3e75aba3
5 files changed
+53 -34

No files matched your search

+40 -22
View File
@@ -775,29 +775,44 @@ blocked rig can no longer block the whole release.
(pose-rotate → apply modifier → reset pose → edit-mode rotate arms → re-add modifier → (pose-rotate → apply modifier → reset pose → edit-mode rotate arms → re-add modifier →
export `export_apply=False`) now yields a correct arm-down base: Blender deformed x ±0.221 export `export_apply=False`) now yields a correct arm-down base: Blender deformed x ±0.221
and the rest bone tail points down. Still local (`tmp/`), not shipped. and the rest bone tail points down. Still local (`tmp/`), not shipped.
- [x] Re-derive the pose composition for a non-180° rest. Godot's `set_bone_pose_rotation` is - [x] Re-derive the pose composition for a non-180° rest. Superseded by the rig-agnostic
rest-relative, so `rest * Quaternion(axis, angle)` only reproduces the old rig by accident `_swing_bone()` below: the rest-relative `set_bone_pose_rotation` form was never exact,
(the old rest is 180°, so `rest² = identity`); the Quaternius ~76° rest breaks it. Fixed in because `get_bone_rest()` is parent-relative and already carries the importer's axis
commit `8b1bf73`: `rest * Quaternion(rest.inverse() * axis, angle)` — backward-compatible conversion. Kept in `MEMORY.md` as a rejected approach.
(the old 180° rest maps the axis to itself). Godot 277/277. - [x] Fix the skinning/rest bake end to end (commit `cd285b3`):
- [ ] Godot skinning import: root causes found and narrowed. (1) The canonical base build (1) the canonical base is **join-only** (`tmp/npc-research/build_base_minimal.py`: no
(`scale/ground transform` + `remove_doubles` weld + modifier removal before join) corrupted `remove_doubles` weld, no scale/ground transform, keep the Armature modifiers) — the weld
the skin weights; a **join-only** base (no transform, no weld, keep modifiers) poses correctly and the transform corrupted the Quaternius weights;
to x −0.228/0.228. Use `tmp/npc-research/build_base_minimal.py`. (2) the arm-down bake (`tmp/npc-research/bake_armdown_v5.py` — pose, read pose matrices,
(2) Fixed: the arm-down bake (`bake_armdown_v5.py` — pose, read pose matrices, apply modifier, apply modifier, **reset pose to identity**, set rest matrices, re-add modifier) bakes the
**reset pose to identity**, set rest matrices, re-add modifier) now bakes correctly rest correctly (DEFORMED x −0.228/0.228, Godot bind pose arm-down);
(DEFORMED x −0.228/0.228, Godot bind_pose down). `NpcAvatar` now also has the side swap, (3) `NpcAvatar._swing_bone()` now writes the **global** bone pose from
the arm swing axis (LEFT) and the hand-proxy position fix (`2a6f29f`). Remaining: the `get_bone_global_rest()`, so one formula drives both the 62-joint Quaternius rig and the
handheld gear (camera) sits at the shoulder/chest instead of the wrist — re-derive the gear 13-core fallback (measured: posed bone axis matches the proxy pivot at dot 1.000 on the
attach offsets against the Quaternius skeleton, then the face/silhouette gate. Quaternius, and the procedural rigs are unchanged);
- [ ] Add regression tests: a bone with a ~76° rest reaches a known global orientation; the (4) `RIG_BONES` + `NpcAvatar.bone_name()` hold the only side mapping (the Quaternius labels
Quaternius joint map resolves; the 13-core fallback poses exactly as it does today. `L`/`R` opposite the project), and the hand proxy is `global_wrist − global_elbow`.
Verified in Godot: idle/sit/walk captures on npc_casual plus the unchanged procedural rigs.
- [x] Add regression tests: `test_shipped_rig_resolves_the_animation_joints` covers the joint map,
the baked arm-down rest, the down-pointing arm axis and the proxy side mapping; the Blender
audit/`inspect` and `npc_proportions` now understand both rigs. Godot 278/278, Python
167/167.
- [x] Give the Quaternius base the five tint slots: `build_base_minimal.py` renames the upstream
materials to `skin`/`cloth`/`pants`/`hair`/`accessory` and keeps `Eye`/`Eyebrows` as
fixed-colour detail. The source UV map is degenerate (every loop at 0,0), so the build now
Smart-UV-unwraps and repacks the layout — texel density went from an unmeasurable 0 to
73–74 px/m on the 256² maps and the 256² AO bake works again.
- [ ] Verify Idle and Walk on the 62-joint rig: loop seam, foot contact, root motion and frame - [ ] Verify Idle and Walk on the 62-joint rig: loop seam, foot contact, root motion and frame
rate. Record which of the 24 clips are in scope and which stay unused. rate. Record which of the 24 clips are in scope and which stay unused.
- [ ] Decide and record how poses the clips do not cover (seated, raise-hand, head turn) are - [ ] Decide and record how poses the clips do not cover (seated, raise-hand, head turn) are
driven at runtime. driven at runtime.
- [ ] Keep the fallback tested: `npc_base`, the MakeHuman head and `_build_body()` remain - [ ] Keep the fallback tested: `npc_base`, the MakeHuman head and `_build_body()` remain
selectable; no MPFB work is reopened and no Stage-1 proportion warp returns. selectable; no MPFB work is reopened and no Stage-1 proportion warp returns. (The procedural
library is still the majority of `CHARACTER_POOLS` and captures clean, but the fallback path
itself has no dedicated test yet.)
- [ ] Re-fit the remaining gear against the Quaternius torso: the `backpack` strap geometry was
authored for the old body width and now hangs in front of the chest. The camera was fixed
with a wrist strap (a neck strap floats when the item is hand-attached).
### Silhouette and face gate (owner) ### Silhouette and face gate (owner)
@@ -814,10 +829,13 @@ blocked rig can no longer block the whole release.
### Pilot rollout ### Pilot rollout
- [ ] Map the five tint slots onto the Quaternius materials; unwrap per slot; bake 256² - [x] Map the five tint slots onto the Quaternius materials and unwrap the degenerate source
AO/albedo/normal/ORM and check texel density. UV map; bake the 256² AO and check texel density (73–74 px/m).
- [ ] Re-fit gear attach points against the Quaternius skeleton and test cap, helmet, backpack, - [ ] Bake the per-slot albedo/normal/ORM maps and a per-slot atlas (the five slots currently
shoulder bag, lanyard, clipboard and firefighter tank. share one whole-body UV set).
- [-] Re-fit gear attach points against the Quaternius skeleton and test cap, helmet, backpack,
shoulder bag, lanyard, clipboard and firefighter tank. (Camera done: wrist strap instead of
the floating neck strap. Backpack straps still cut across the Quaternius chest.)
- [ ] Wire the approved base into one role (or the crowd roles if option b is chosen) with the - [ ] Wire the approved base into one role (or the crowd roles if option b is chosen) with the
procedural model as an instant fallback; no runtime download or optional dependency. procedural model as an instant fallback; no runtime download or optional dependency.
- [ ] Run `blender_assets.py inspect`/`audit`/`fit`, `npc_proportions.py`, the 24-NPC budget smoke - [ ] Run `blender_assets.py inspect`/`audit`/`fit`, `npc_proportions.py`, the 24-NPC budget smoke
+5 -5
View File
@@ -28,12 +28,12 @@
"attach": "back", "attach": "back",
"bounds_godot": { "bounds_godot": {
"max": [ "max": [
0.161, 0.15,
0.296, 0.296,
0.212 0.172
], ],
"min": [ "min": [
-0.161, -0.15,
-0.12, -0.12,
-0.319 -0.319
] ]
@@ -46,7 +46,7 @@
], ],
"path": "assets/models/blender/npc_gear/backpack.glb", "path": "assets/models/blender/npc_gear/backpack.glb",
"triangles": 380, "triangles": 380,
"vertices": 408 "vertices": 410
}, },
"bench": { "bench": {
"bounds_godot": { "bounds_godot": {
@@ -467,7 +467,7 @@
"hair" "hair"
], ],
"triangles": 6206, "triangles": 6206,
"vertices": 12692 "vertices": 12988
}, },
"npc_coat": { "npc_coat": {
"bounds_godot": { "bounds_godot": {
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

After

Width:  |  Height:  |  Size: 36 KiB

+8 -7
View File
@@ -1492,13 +1492,14 @@ def build_gear(name):
bevel=0.02, segs=2)) bevel=0.02, segs=2))
p.append(box("Zip", (0.24, 0.005, 0.01), (0, 0.317, 0.05), M("steel"))) p.append(box("Zip", (0.24, 0.005, 0.01), (0, 0.317, 0.05), M("steel")))
for s in (-1.0, 1.0): for s in (-1.0, 1.0):
# Strap path: over the shoulder, then down the chest outside the # Strap path: over the shoulder, then down the chest. The chest points
# torso front (Godot z≈0.21). The old chest/waist points sat at # are pulled in to x 0.12 / Godot z 0.15 so the straps hug the slimmer
# z≈0.13 inside the body, so only thin tube fragments read as two # Quaternius torso (ADR-0018) as well as the wider procedural one;
# rigid black bars from the front. # the earlier x 0.145 / z 0.19 path floated ~5 cm proud of the
p.append(tube("Strap", [(0.09 * s, 0.13, 0.27), (0.11 * s, -0.02, 0.29), # Quaternius chest and read as two detached blue bars.
(0.12 * s, -0.06, 0.25), p.append(tube("Strap", [(0.075 * s, 0.13, 0.27), (0.095 * s, -0.02, 0.29),
(0.14 * s, -0.19, 0.18), (0.145 * s, -0.19, -0.08)], (0.105 * s, -0.06, 0.25),
(0.12 * s, -0.15, 0.18), (0.125 * s, -0.15, -0.08)],
0.016, M("denim_bag"), seg=8, width_scale=1.35, 0.016, M("denim_bag"), seg=8, width_scale=1.35,
thickness_scale=0.42)) thickness_scale=0.42))
elif name == "shoulder_bag": elif name == "shoulder_bag":