Rebuild the NPC hands and add a hand capture
No files matched your search
@@ -14,6 +14,8 @@ Semantic Versioning.
|
|||||||
cheekbones are sculpted into the head instead of reading as separate spheres.
|
cheekbones are sculpted into the head instead of reading as separate spheres.
|
||||||
- Character torsos taper from the chest to the waist and flare slightly into
|
- Character torsos taper from the chest to the waist and flare slightly into
|
||||||
the hips, so shirts and jackets sit less like straight tubes.
|
the hips, so shirts and jackets sit less like straight tubes.
|
||||||
|
- Character hands now have a wider palm, bent fingers with rounded tips and a
|
||||||
|
separate thumb instead of flat bars.
|
||||||
- Tourists no longer appear to carry two backpacks: the photo-pose camera bag is
|
- Tourists no longer appear to carry two backpacks: the photo-pose camera bag is
|
||||||
hidden except while they are actually taking a picture.
|
hidden except while they are actually taking a picture.
|
||||||
- Backpack shoulder straps are now flattened bands that sit outside the chest
|
- Backpack shoulder straps are now flattened bands that sit outside the chest
|
||||||
|
|||||||
@@ -90,6 +90,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
0.150→0.146, chest 0.170→0.168, upper chest 0.172→0.170. This is a subtle shirt taper, not a
|
0.150→0.146, chest 0.170→0.168, upper chest 0.172→0.170. This is a subtle shirt taper, not a
|
||||||
skeleton change; all proportions pass and `sheet_npc_baseline_compare-torso.jpg` records it.
|
skeleton change; all proportions pass and `sheet_npc_baseline_compare-torso.jpg` records it.
|
||||||
Max character is 4,754 tris / 4,584 verts.
|
Max character is 4,754 tris / 4,584 verts.
|
||||||
|
- **NPC hand pass (0.9.4, 2026-11-02):** palm radius 0.046 at z .742, scale (.80,.62,.88);
|
||||||
|
fingers are `tube` paths from z .758 to .681–.694 with tip spheres, thumb is a bent tube. The
|
||||||
|
optional hand capture is `capture_npc_baseline.gd -- --view=hand` (8 frames, baseline stays 48).
|
||||||
|
`npc_firefighter` is now at 4,948 verts (52 below the 5,000 cap); do not add hand/face geometry
|
||||||
|
without replacing something. Grip offsets remain open.
|
||||||
- **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and
|
- **UI flow smoke (2026-11-02):** `smoke_ui_flows.gd` boots the real scene and
|
||||||
exercises inspection allow/deny, normal vs emergency radio, Help filtering,
|
exercises inspection allow/deny, normal vs emergency radio, Help filtering,
|
||||||
wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.6
|
wand allow/deny and review return-to-menu. Keep this smoke in the 0.9.6
|
||||||
|
|||||||
@@ -116,9 +116,12 @@ and equipment fit.
|
|||||||
|
|
||||||
### Hands, feet and clothing
|
### Hands, feet and clothing
|
||||||
|
|
||||||
- [ ] Refine hands and fingers enough to read at conversation distance without exceeding the
|
- [x] Refine hands and fingers enough to read at conversation distance without exceeding the
|
||||||
character triangle budget; check grip alignment for phone, cup, clipboard, camera, sign,
|
character triangle budget. (Wider palm, two-segment bent fingers with rounded tips, separate
|
||||||
flashlight, wand and radio.
|
thumb; optional `--view=hand` capture and `sheet_npc_hands_compare.jpg`.)
|
||||||
|
- [ ] Fix grip alignment for phone, cup, clipboard, camera, sign, flashlight, wand and radio
|
||||||
|
against the new palm/finger geometry. (Several props still occlude the palm or float beside
|
||||||
|
it in the hand capture.)
|
||||||
- [ ] Audit shoes/boots for silhouette, grounding and contact with the floor; preserve the
|
- [ ] Audit shoes/boots for silhouette, grounding and contact with the floor; preserve the
|
||||||
responder and firefighter footwear language.
|
responder and firefighter footwear language.
|
||||||
- [ ] Improve clothing layer transitions at collars, cuffs, plackets, waistbands, pockets, badges
|
- [ ] Improve clothing layer transitions at collars, cuffs, plackets, waistbands, pockets, badges
|
||||||
|
|||||||
@@ -487,8 +487,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 3762,
|
"triangles": 4042,
|
||||||
"vertices": 3474
|
"vertices": 3894
|
||||||
},
|
},
|
||||||
"npc_coat": {
|
"npc_coat": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -528,8 +528,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4754,
|
"triangles": 5034,
|
||||||
"vertices": 4584
|
"vertices": 4814
|
||||||
},
|
},
|
||||||
"npc_dress": {
|
"npc_dress": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -567,8 +567,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4180,
|
"triangles": 4460,
|
||||||
"vertices": 3863
|
"vertices": 4191
|
||||||
},
|
},
|
||||||
"npc_firefighter": {
|
"npc_firefighter": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -608,8 +608,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4624,
|
"triangles": 4904,
|
||||||
"vertices": 4583
|
"vertices": 4948
|
||||||
},
|
},
|
||||||
"npc_guard": {
|
"npc_guard": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -652,8 +652,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4326,
|
"triangles": 4606,
|
||||||
"vertices": 4443
|
"vertices": 4679
|
||||||
},
|
},
|
||||||
"npc_hoodie": {
|
"npc_hoodie": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -693,8 +693,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4584,
|
"triangles": 4864,
|
||||||
"vertices": 4294
|
"vertices": 4506
|
||||||
},
|
},
|
||||||
"npc_jacket": {
|
"npc_jacket": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -734,8 +734,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4152,
|
"triangles": 4432,
|
||||||
"vertices": 3900
|
"vertices": 4265
|
||||||
},
|
},
|
||||||
"npc_vest": {
|
"npc_vest": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -775,8 +775,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4260,
|
"triangles": 4540,
|
||||||
"vertices": 4183
|
"vertices": 4503
|
||||||
},
|
},
|
||||||
"palm_tree": {
|
"palm_tree": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 64 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 57 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 63 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 61 KiB |
@@ -94,8 +94,8 @@ All eight use the `npc_base` contract, so each one can replace
|
|||||||
`Materials.npc_slot()` tints them the same way it tints `npc_base`.
|
`Materials.npc_slot()` tints them the same way it tints `npc_base`.
|
||||||
- budget ≤ 6000 triangles and ≤ 5000 vertices (glTF vertex count, after seam
|
- budget ≤ 6000 triangles and ≤ 5000 vertices (glTF vertex count, after seam
|
||||||
splits; `CHAR_MAX_VERTS` in `tools/blender_assets.py`, raised from 4000 for
|
splits; `CHAR_MAX_VERTS` in `tools/blender_assets.py`, raised from 4000 for
|
||||||
the 2026-11-01 face/hand pass). The current range is 3,762–4,754 triangles
|
the 2026-11-01 face/hand pass). The current range is 4,042–5,034 triangles
|
||||||
and 3,474–4,584 vertices.
|
and 3,894–4,948 vertices. The vertex cap is now the tighter budget.
|
||||||
|
|
||||||
Construction: the torso and limbs are one continuous Skin-modifier mesh,
|
Construction: the torso and limbs are one continuous Skin-modifier mesh,
|
||||||
subdivided twice, decimated to 26 % with X symmetry, and cut at the waist, cuffs,
|
subdivided twice, decimated to 26 % with X symmetry, and cut at the waist, cuffs,
|
||||||
|
|||||||
@@ -48,6 +48,7 @@ Committed contact sheets and local Blender renders for the character, gear and p
|
|||||||
| <img src="sheet_npc_baseline_compare-body.jpg" width="280" alt="sheet_npc_baseline_compare-body.jpg"> | [`sheet_npc_baseline_compare-body.jpg`](sheet_npc_baseline_compare-body.jpg) |
|
| <img src="sheet_npc_baseline_compare-body.jpg" width="280" alt="sheet_npc_baseline_compare-body.jpg"> | [`sheet_npc_baseline_compare-body.jpg`](sheet_npc_baseline_compare-body.jpg) |
|
||||||
| <img src="sheet_npc_baseline_compare-face.jpg" width="280" alt="sheet_npc_baseline_compare-face.jpg"> | [`sheet_npc_baseline_compare-face.jpg`](sheet_npc_baseline_compare-face.jpg) |
|
| <img src="sheet_npc_baseline_compare-face.jpg" width="280" alt="sheet_npc_baseline_compare-face.jpg"> | [`sheet_npc_baseline_compare-face.jpg`](sheet_npc_baseline_compare-face.jpg) |
|
||||||
| <img src="sheet_npc_baseline_compare-torso.jpg" width="280" alt="sheet_npc_baseline_compare-torso.jpg"> | [`sheet_npc_baseline_compare-torso.jpg`](sheet_npc_baseline_compare-torso.jpg) |
|
| <img src="sheet_npc_baseline_compare-torso.jpg" width="280" alt="sheet_npc_baseline_compare-torso.jpg"> | [`sheet_npc_baseline_compare-torso.jpg`](sheet_npc_baseline_compare-torso.jpg) |
|
||||||
|
| <img src="sheet_npc_hands_compare.jpg" width="280" alt="sheet_npc_hands_compare.jpg"> | [`sheet_npc_hands_compare.jpg`](sheet_npc_hands_compare.jpg) |
|
||||||
| <img src="sheet_npc_gear.jpg" width="280" alt="sheet_npc_gear.jpg"> | [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) |
|
| <img src="sheet_npc_gear.jpg" width="280" alt="sheet_npc_gear.jpg"> | [`sheet_npc_gear.jpg`](sheet_npc_gear.jpg) |
|
||||||
| <img src="sheet_props.jpg" width="280" alt="sheet_props.jpg"> | [`sheet_props.jpg`](sheet_props.jpg) |
|
| <img src="sheet_props.jpg" width="280" alt="sheet_props.jpg"> | [`sheet_props.jpg`](sheet_props.jpg) |
|
||||||
| <img src="shoulder_bag.png" width="280" alt="shoulder_bag.png"> | [`shoulder_bag.png`](shoulder_bag.png) |
|
| <img src="shoulder_bag.png" width="280" alt="shoulder_bag.png"> | [`shoulder_bag.png`](shoulder_bag.png) |
|
||||||
|
|||||||
|
After Width: | Height: | Size: 352 KiB |
@@ -42,6 +42,7 @@ Implemented so far:
|
|||||||
- NPC neck/shoulder transition (0.9.4 work): the body graph now uses a fuller neck base, a slightly tapered neck top and a lower shoulder root, giving the head/neck/shoulder line a trapezius slope instead of a thin post. The full character library was rebuilt with a final degenerate-face cleanup; the baseline comparison is `sheet_npc_baseline_compare-body.jpg`.
|
- NPC neck/shoulder transition (0.9.4 work): the body graph now uses a fuller neck base, a slightly tapered neck top and a lower shoulder root, giving the head/neck/shoulder line a trapezius slope instead of a thin post. The full character library was rebuilt with a final degenerate-face cleanup; the baseline comparison is `sheet_npc_baseline_compare-body.jpg`.
|
||||||
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
|
- NPC face-depth pass (0.9.4 work): sclera are recessed and capped by a thin upper lid, and the separate jaw/cheek spheres are replaced by a chin plate and cheekbones sculpted into the head mesh. The full library was rebuilt; the comparison is `sheet_npc_baseline_compare-face.jpg`.
|
||||||
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
|
- NPC torso/hip transition (0.9.4 work): the pelvis and chest are slightly wider than the waist, giving shirts and jackets a readable taper into the hips without changing the build multipliers or the skeleton. The comparison is `sheet_npc_baseline_compare-torso.jpg`.
|
||||||
|
- NPC hand pass (0.9.4 work): wider, shallower palms; four two-segment bent fingers with rounded tips and a separate bent thumb; optional `--view=hand` capture at 0.80 m. The hand comparison is `sheet_npc_hands_compare.jpg`; item grip offsets remain open.
|
||||||
- NPC proportion audit (0.9.4 work): `tools/npc_proportions.py` measures all eight GLBs against a stylized height/head/shoulder/hip/torso/leg/arm/foot guide. All variants pass; the head and broad-build silhouettes are the nearest upper bounds. Results are in [`npc-proportion-audit.md`](npc-proportion-audit.md).
|
- NPC proportion audit (0.9.4 work): `tools/npc_proportions.py` measures all eight GLBs against a stylized height/head/shoulder/hip/torso/leg/arm/foot guide. All variants pass; the head and broad-build silhouettes are the nearest upper bounds. Results are in [`npc-proportion-audit.md`](npc-proportion-audit.md).
|
||||||
- NPC P1 portrait fixes (0.9.4 work): tourists now hide their photo-pose camera bag outside `photo`/`film` poses, removing the duplicate-backpack silhouette; the backpack gear was rebuilt with flattened, forward chest straps and a fresh AO map. The before/after sheet is [`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); the face and remaining clothing/hand work stay open.
|
- NPC P1 portrait fixes (0.9.4 work): tourists now hide their photo-pose camera bag outside `photo`/`film` poses, removing the duplicate-backpack silhouette; the backpack gear was rebuilt with flattened, forward chest straps and a fresh AO map. The before/after sheet is [`blender_previews/sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); the face and remaining clothing/hand work stay open.
|
||||||
- NPC visual baseline (0.9.4 work): `src/test/capture_npc_baseline.gd` captures all eight library characters from front, three-quarter, profile, back, full-body and crowd views in a neutral studio (48 frames). The criteria, measurements and ranked gaps are in [`npc-visual-review.md`](npc-visual-review.md); the committed sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg). No model changes were made in this baseline step.
|
- NPC visual baseline (0.9.4 work): `src/test/capture_npc_baseline.gd` captures all eight library characters from front, three-quarter, profile, back, full-body and crowd views in a neutral studio (48 frames). The criteria, measurements and ranked gaps are in [`npc-visual-review.md`](npc-visual-review.md); the committed sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg). No model changes were made in this baseline step.
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
# NPC visual baseline and acceptance criteria
|
# NPC visual baseline and acceptance criteria
|
||||||
|
|
||||||
Status: baseline captured 2026-11-02. The first P1 fixes (tourist photo-prop
|
Status: baseline captured 2026-11-02. The P1 fixes (tourist photo-prop
|
||||||
visibility, backpack straps), the neck/shoulder and torso/hip transitions and
|
visibility, backpack straps), the neck/shoulder and torso/hip transitions, the
|
||||||
the face-depth pass are applied. Remaining hand/clothing work is open; the
|
face-depth pass and the first hand pass are applied. Hand-item grip alignment
|
||||||
baseline remains the reference for 0.9.4. Comparison sheets:
|
and clothing work remain open; the baseline remains the reference for 0.9.4.
|
||||||
|
Comparison sheets:
|
||||||
[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
|
[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
|
||||||
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
|
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
|
||||||
and the latest [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg).
|
the latest [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg),
|
||||||
|
and the conversation-distance hand pass
|
||||||
|
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg).
|
||||||
|
|
||||||
## What was captured
|
## What was captured
|
||||||
|
|
||||||
@@ -37,6 +40,17 @@ python3 tools/npc_baseline.py sheet --dir tmp/npc-baseline
|
|||||||
|
|
||||||
The 48 raw PNGs are local QA under `tmp/` (gitignored). The committed contact
|
The 48 raw PNGs are local QA under `tmp/` (gitignored). The committed contact
|
||||||
sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg).
|
sheet is [`blender_previews/sheet_npc_baseline.jpg`](blender_previews/sheet_npc_baseline.jpg).
|
||||||
|
For the P2 hand/grip check there is an optional eighth view, captured separately
|
||||||
|
so the 48-frame baseline stays stable:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
godot --path . --script res://src/test/capture_npc_baseline.gd -- \
|
||||||
|
--out=res://tmp/npc-hands --view=hand
|
||||||
|
```
|
||||||
|
|
||||||
|
That writes eight `*__hand.png` frames at 0.80 m from the right hand; the
|
||||||
|
before/after review image is
|
||||||
|
[`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg).
|
||||||
The capture is deterministic apart from engine rasterisation; re-running into
|
The capture is deterministic apart from engine rasterisation; re-running into
|
||||||
the same directory keeps the earlier files and writes `-vN` copies rather than
|
the same directory keeps the earlier files and writes `-vN` copies rather than
|
||||||
overwriting them.
|
overwriting them.
|
||||||
@@ -100,19 +114,19 @@ From `python3 tools/blender_assets.py fit` and the manifest:
|
|||||||
|
|
||||||
| Character | Tris | Verts | Head width | Clothing radius | Notable gear clearances |
|
| Character | Tris | Verts | Head width | Clothing radius | Notable gear clearances |
|
||||||
| --- | ---: | ---: | ---: | ---: | --- |
|
| --- | ---: | ---: | ---: | ---: | --- |
|
||||||
| `npc_casual` | 3,762 | 3,474 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
|
| `npc_casual` | 4,042 | 3,894 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
|
||||||
| `npc_jacket` | 4,152 | 3,900 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
|
| `npc_jacket` | 4,432 | 4,265 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
|
||||||
| `npc_coat` | 4,754 | 4,584 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
|
| `npc_coat` | 5,034 | 4,814 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
|
||||||
| `npc_vest` | 4,260 | 4,183 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
|
| `npc_vest` | 4,540 | 4,503 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
|
||||||
| `npc_dress` | 4,180 | 3,863 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
|
| `npc_dress` | 4,460 | 4,191 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
|
||||||
| `npc_hoodie` | 4,584 | 4,294 | 0.315 m | 0.190 m (hip) | — |
|
| `npc_hoodie` | 4,864 | 4,506 | 0.315 m | 0.190 m (hip) | — |
|
||||||
| `npc_firefighter` | 4,624 | 4,583 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
|
| `npc_firefighter` | 4,904 | 4,948 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
|
||||||
| `npc_guard` | 4,326 | 4,443 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
|
| `npc_guard` | 4,606 | 4,679 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
|
||||||
|
|
||||||
The widest character is `npc_coat` at 4,754 triangles and 4,584 vertices, so the
|
`npc_coat` is widest at 5,034 triangles (966 below the cap); `npc_firefighter` is
|
||||||
current headroom is 1,246 triangles and 416 vertices per character. Any hand or
|
closest on vertices at 4,948 (only 52 below the 5,000 cap). Any further hand or
|
||||||
clothing refinement must come out of that budget or replace existing detail. The
|
clothing detail must replace existing geometry rather than adding it. The refit
|
||||||
refit backpack measures 380 triangles / 406 vertices.
|
backpack measures 380 triangles / 406 vertices.
|
||||||
|
|
||||||
## Baseline findings, ranked
|
## Baseline findings, ranked
|
||||||
|
|
||||||
@@ -140,9 +154,12 @@ refit backpack measures 380 triangles / 406 vertices.
|
|||||||
|
|
||||||
### P2 — fix after the first P1 pass
|
### P2 — fix after the first P1 pass
|
||||||
|
|
||||||
4. **Hands and grips are not proven at conversation distance.** The full-body
|
4. **Hands: first pass fixed 2026-11-02; grip alignment still open.** The palm
|
||||||
frame shows small block hands; the camera, phone and clipboard need a
|
is wider and shallower, and each finger is now a two-segment bent tube with a
|
||||||
close-up grip check before the hand mesh is accepted.
|
rounded tip and a separate bent thumb. The hand capture shows a readable
|
||||||
|
four-finger hand instead of flat bars. The camera, phone, cup, clipboard and
|
||||||
|
sign still need their attach offsets checked: several props occlude the palm
|
||||||
|
or read as floating beside it.
|
||||||
5. **Clothing detail is tile-driven and transitional seams are visible.** The
|
5. **Clothing detail is tile-driven and transitional seams are visible.** The
|
||||||
fabric pattern repeats strongly on the casual, coat and dress variants, and
|
fabric pattern repeats strongly on the casual, coat and dress variants, and
|
||||||
collar/cuff/waist edges are abrupt. This is more visible than the shoe/hem
|
collar/cuff/waist edges are abrupt. This is more visible than the shoe/hem
|
||||||
@@ -189,15 +206,16 @@ The committed runs are
|
|||||||
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
|
[`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
|
||||||
and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
|
and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
|
||||||
The transition pass is [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg).
|
The transition pass is [`sheet_npc_baseline_compare-torso.jpg`](blender_previews/sheet_npc_baseline_compare-torso.jpg).
|
||||||
|
The hand pass is [`sheet_npc_hands_compare.jpg`](blender_previews/sheet_npc_hands_compare.jpg).
|
||||||
Keep the baseline sheet as the before artifact and add a new comparison sheet
|
Keep the baseline sheet as the before artifact and add a new comparison sheet
|
||||||
rather than replacing it.
|
rather than replacing it.
|
||||||
|
|
||||||
## Next implementation order
|
## Next implementation order
|
||||||
|
|
||||||
1. Add the remaining body-shape variation between variants and re-measure the
|
1. Fix hand-item grip offsets (phone, cup, clipboard, camera, sign, flashlight,
|
||||||
|
wand and radio) against the new palm/finger geometry.
|
||||||
|
2. Add the remaining body-shape variation between variants and re-measure the
|
||||||
LOD/impostor silhouettes.
|
LOD/impostor silhouettes.
|
||||||
2. Add a conversation-distance hand/grip capture and fix hand geometry before
|
|
||||||
judging hand items.
|
|
||||||
3. Audit hems and footwear in walk/sit/block poses.
|
3. Audit hems and footwear in walk/sit/block poses.
|
||||||
4. Refine nose/lip/ear shapes only if the portrait pass still flags them after
|
4. Refine nose/lip/ear shapes only if the portrait pass still flags them after
|
||||||
the hands and clothing read cleanly.
|
the hands and clothing read cleanly.
|
||||||
|
|||||||
@@ -30,6 +30,8 @@ const VARIANTS := [
|
|||||||
]
|
]
|
||||||
## Camera orbits the subject at yaw 0 (front = Godot +Z). Portrait views keep
|
## Camera orbits the subject at yaw 0 (front = Godot +Z). Portrait views keep
|
||||||
## the eye/head in frame; the last two show the full body and crowd silhouette.
|
## the eye/head in frame; the last two show the full body and crowd silhouette.
|
||||||
|
const HAND_VIEW := {"name": "hand", "yaw": 15.0, "distance": 0.80, "height": 0.72,
|
||||||
|
"target_x": 0.27, "target_y": 0.72, "fov": 32.0}
|
||||||
const VIEWS := [
|
const VIEWS := [
|
||||||
{"name": "front", "yaw": 0.0, "distance": 1.85, "height": 1.55,
|
{"name": "front", "yaw": 0.0, "distance": 1.85, "height": 1.55,
|
||||||
"target_y": 1.52, "fov": 32.0},
|
"target_y": 1.52, "fov": 32.0},
|
||||||
@@ -48,6 +50,8 @@ const SETTLE_FRAMES := 4
|
|||||||
const MAX_FRAMES := 4000
|
const MAX_FRAMES := 4000
|
||||||
|
|
||||||
var out_dir := "user://screenshots/npc_baseline"
|
var out_dir := "user://screenshots/npc_baseline"
|
||||||
|
var only_view := ""
|
||||||
|
var _views: Array = VIEWS
|
||||||
var frames := 0
|
var frames := 0
|
||||||
var _phase := "stage_variant"
|
var _phase := "stage_variant"
|
||||||
var _variant_i := 0
|
var _variant_i := 0
|
||||||
@@ -61,6 +65,10 @@ func _initialize() -> void:
|
|||||||
for arg in OS.get_cmdline_user_args():
|
for arg in OS.get_cmdline_user_args():
|
||||||
if str(arg).begins_with("--out="):
|
if str(arg).begins_with("--out="):
|
||||||
out_dir = str(arg).substr(6)
|
out_dir = str(arg).substr(6)
|
||||||
|
elif str(arg).begins_with("--view="):
|
||||||
|
only_view = str(arg).substr(7)
|
||||||
|
if only_view == "hand":
|
||||||
|
_views = [HAND_VIEW]
|
||||||
root.size = Vector2i(1280, 720)
|
root.size = Vector2i(1280, 720)
|
||||||
_build_stage()
|
_build_stage()
|
||||||
|
|
||||||
@@ -83,7 +91,7 @@ func _process(_delta: float) -> bool:
|
|||||||
"shoot":
|
"shoot":
|
||||||
_shoot()
|
_shoot()
|
||||||
_view_i += 1
|
_view_i += 1
|
||||||
if _view_i >= VIEWS.size():
|
if _view_i >= _views.size():
|
||||||
_view_i = 0
|
_view_i = 0
|
||||||
_variant_i += 1
|
_variant_i += 1
|
||||||
if _variant_i >= VARIANTS.size():
|
if _variant_i >= VARIANTS.size():
|
||||||
@@ -169,10 +177,10 @@ func _stage_variant() -> void:
|
|||||||
func _frame_view() -> void:
|
func _frame_view() -> void:
|
||||||
if _avatar == null or not is_instance_valid(_avatar):
|
if _avatar == null or not is_instance_valid(_avatar):
|
||||||
return
|
return
|
||||||
var view: Dictionary = VIEWS[_view_i]
|
var view: Dictionary = _views[_view_i]
|
||||||
var yaw := deg_to_rad(float(view["yaw"]))
|
var yaw := deg_to_rad(float(view["yaw"]))
|
||||||
var distance := float(view["distance"])
|
var distance := float(view["distance"])
|
||||||
var target := Vector3(0.0, float(view["target_y"]), 0.0)
|
var target := Vector3(float(view.get("target_x", 0.0)), float(view["target_y"]), 0.0)
|
||||||
_camera.position = target + Vector3(sin(yaw) * distance, float(view["height"]) - target.y,
|
_camera.position = target + Vector3(sin(yaw) * distance, float(view["height"]) - target.y,
|
||||||
cos(yaw) * distance)
|
cos(yaw) * distance)
|
||||||
_camera.fov = float(view["fov"])
|
_camera.fov = float(view["fov"])
|
||||||
@@ -180,7 +188,7 @@ func _frame_view() -> void:
|
|||||||
|
|
||||||
func _shoot() -> void:
|
func _shoot() -> void:
|
||||||
var variant: Dictionary = VARIANTS[_variant_i]
|
var variant: Dictionary = VARIANTS[_variant_i]
|
||||||
var view: Dictionary = VIEWS[_view_i]
|
var view: Dictionary = _views[_view_i]
|
||||||
var dir := ProjectSettings.globalize_path(out_dir)
|
var dir := ProjectSettings.globalize_path(out_dir)
|
||||||
DirAccess.make_dir_recursive_absolute(dir)
|
DirAccess.make_dir_recursive_absolute(dir)
|
||||||
var base := "%s__%s" % [variant["name"], view["name"]]
|
var base := "%s__%s" % [variant["name"], view["name"]]
|
||||||
|
|||||||
@@ -813,16 +813,28 @@ def _hands_and_shoes(mats, spec):
|
|||||||
w = BUILDS[spec.get("build", "average")][0]
|
w = BUILDS[spec.get("build", "average")][0]
|
||||||
for s in (-1.0, 1.0):
|
for s in (-1.0, 1.0):
|
||||||
x = 0.275 * s * w
|
x = 0.275 * s * w
|
||||||
parts.append(sphere("Palm", 0.045, (x, -0.01, 0.735), skin, seg=10, rings=6,
|
# Wider and shallower than the old ball: a palm, not a wrist knob.
|
||||||
scale=(0.55, 0.95, 1.25)))
|
parts.append(sphere("Palm", 0.046, (x, -0.012, 0.742), skin, seg=10, rings=6,
|
||||||
parts.append(sphere("Thumb", 0.018, (x - 0.014 * s, -0.048, 0.752), skin,
|
scale=(0.80, 0.62, 0.88)))
|
||||||
seg=8, rings=6, scale=(1.0, 1.0, 1.7), rot=(0.4, 0, 0)))
|
# Four bent, tapering fingers with rounded tips. The two segments keep
|
||||||
# four fingers: relaxed, slightly curled forward, outer ones shorter
|
# the hand readable at conversation distance without adding a full
|
||||||
|
# finger skeleton; the outer fingers are slightly shorter.
|
||||||
for index, offset in enumerate((-0.024, -0.008, 0.008, 0.024)):
|
for index, offset in enumerate((-0.024, -0.008, 0.008, 0.024)):
|
||||||
length = (0.046, 0.052, 0.050, 0.040)[index]
|
fx = x + offset * s
|
||||||
parts.append(cyl("Finger", 0.011, 0.009, length,
|
tip_z = (0.692, 0.681, 0.684, 0.694)[index]
|
||||||
(x + offset * s, -0.030, 0.714 - length * 0.5), skin,
|
radius = 0.0115 if index in (1, 2) else 0.0105
|
||||||
seg=6, rot=(math.radians(14), 0, 0)))
|
parts.append(tube("Finger", [(fx, -0.018, 0.758),
|
||||||
|
(fx, -0.030, 0.726),
|
||||||
|
(fx, -0.042, tip_z)],
|
||||||
|
radius, skin, seg=6, caps=False))
|
||||||
|
parts.append(sphere("Fingertip", radius, (fx, -0.042, tip_z), skin,
|
||||||
|
seg=6, rings=4))
|
||||||
|
parts.append(tube("Thumb", [(x - 0.010 * s, -0.018, 0.758),
|
||||||
|
(x - 0.028 * s, -0.040, 0.740),
|
||||||
|
(x - 0.034 * s, -0.055, 0.718)],
|
||||||
|
0.012, skin, seg=6, caps=False))
|
||||||
|
parts.append(sphere("ThumbTip", 0.012, (x - 0.034 * s, -0.055, 0.718), skin,
|
||||||
|
seg=6, rings=4))
|
||||||
parts.append(box("Shoe", (0.10, 0.25, 0.075), (0.12 * s, -0.045, 0.04), shoe,
|
parts.append(box("Shoe", (0.10, 0.25, 0.075), (0.12 * s, -0.045, 0.04), shoe,
|
||||||
bevel=0.028, segs=1))
|
bevel=0.028, segs=1))
|
||||||
parts.append(box("Sole", (0.106, 0.262, 0.02), (0.12 * s, -0.045, 0.01),
|
parts.append(box("Sole", (0.106, 0.262, 0.02), (0.12 * s, -0.045, 0.01),
|
||||||
|
|||||||