Smooth the NPC neck and shoulder transition
No files matched your search
@@ -8,6 +8,8 @@ Semantic Versioning.
|
|||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
|
|
||||||
|
- Character necks are fuller and shoulders slope from the neck instead of
|
||||||
|
reading as a thin post on a hanger.
|
||||||
- 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
|
||||||
|
|||||||
@@ -76,6 +76,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
|
|||||||
stylized guide in `docs/npc-proportion-audit.md`; head ~0.190 (upper 0.22) and broad-build
|
stylized guide in `docs/npc-proportion-audit.md`; head ~0.190 (upper 0.22) and broad-build
|
||||||
shoulders ~0.370/hip ~0.225 are near the upper bounds, not outliers. No skeleton/mesh rescale was
|
shoulders ~0.370/hip ~0.225 are near the upper bounds, not outliers. No skeleton/mesh rescale was
|
||||||
needed; run the report after any body change.
|
needed; run the report after any body change.
|
||||||
|
- **NPC neck/shoulder transition (0.9.4, 2026-11-02):** the body graph uses a fuller neck base
|
||||||
|
(1.43 m, radius 0.074/0.072), a taller/thicker neck top and a shoulder root lowered from 1.39 to
|
||||||
|
1.37 m. A final triangulate + tiny-face delete in `build_character` removes zero-area triangles
|
||||||
|
that the new neck branch exposed in jacket/hoodie. All eight GLBs and AO maps were rebuilt;
|
||||||
|
`npc_proportions.py` still passes and `sheet_npc_baseline_compare-body.jpg` is the review image.
|
||||||
- **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
|
||||||
|
|||||||
@@ -86,8 +86,11 @@ and equipment fit.
|
|||||||
torso length, hip width, limb length, foot length) and correct the worst outliers.
|
torso length, hip width, limb length, foot length) and correct the worst outliers.
|
||||||
(`tools/npc_proportions.py` + `docs/npc-proportion-audit.md`: all eight pass; no skeleton or
|
(`tools/npc_proportions.py` + `docs/npc-proportion-audit.md`: all eight pass; no skeleton or
|
||||||
mesh rescue was needed. Head and broad-build silhouettes are nearest their upper bounds.)
|
mesh rescue was needed. Head and broad-build silhouettes are nearest their upper bounds.)
|
||||||
- [ ] Improve neck, shoulder, torso and hip transitions so the silhouette reads as one body rather
|
- [x] Improve the neck and shoulder transition so the silhouette reads as one body rather than a
|
||||||
than stacked primitives; preserve the existing broad/slim/average build variations.
|
thin post on a hanger. (Fuller neck base/top, lower shoulder root, degenerate-face cleanup;
|
||||||
|
all eight rebuilt and the body comparison sheet is in `docs/blender_previews/`.)
|
||||||
|
- [ ] Improve the torso and hip transition; preserve the existing broad/slim/average build
|
||||||
|
variations.
|
||||||
- [ ] Add subtle body-shape variation between variants beyond uniform scaling (shoulder slope,
|
- [ ] Add subtle body-shape variation between variants beyond uniform scaling (shoulder slope,
|
||||||
torso taper, hip width, limb thickness) while staying inside the triangle/vertex budget.
|
torso taper, hip width, limb thickness) while staying inside the triangle/vertex budget.
|
||||||
- [ ] Check the procedural fallback and `npc_base` silhouette against the library so the fallback
|
- [ ] Check the procedural fallback and `npc_base` silhouette against the library so the fallback
|
||||||
|
|||||||
@@ -28,12 +28,12 @@
|
|||||||
"attach": "back",
|
"attach": "back",
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
"max": [
|
"max": [
|
||||||
0.15,
|
0.161,
|
||||||
0.297,
|
0.296,
|
||||||
0.214
|
0.212
|
||||||
],
|
],
|
||||||
"min": [
|
"min": [
|
||||||
-0.15,
|
-0.161,
|
||||||
-0.12,
|
-0.12,
|
||||||
-0.319
|
-0.319
|
||||||
]
|
]
|
||||||
@@ -42,11 +42,11 @@
|
|||||||
"materials": [
|
"materials": [
|
||||||
"pack_nylon",
|
"pack_nylon",
|
||||||
"steel",
|
"steel",
|
||||||
"plastic_black"
|
"denim_bag"
|
||||||
],
|
],
|
||||||
"path": "assets/models/blender/npc_gear/backpack.glb",
|
"path": "assets/models/blender/npc_gear/backpack.glb",
|
||||||
"triangles": 380,
|
"triangles": 380,
|
||||||
"vertices": 406
|
"vertices": 408
|
||||||
},
|
},
|
||||||
"beanie": {
|
"beanie": {
|
||||||
"attach": "head",
|
"attach": "head",
|
||||||
@@ -487,8 +487,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 3974,
|
"triangles": 3984,
|
||||||
"vertices": 3744
|
"vertices": 3793
|
||||||
},
|
},
|
||||||
"npc_coat": {
|
"npc_coat": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -528,8 +528,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 5026,
|
"triangles": 4978,
|
||||||
"vertices": 4807
|
"vertices": 4776
|
||||||
},
|
},
|
||||||
"npc_dress": {
|
"npc_dress": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -567,8 +567,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4428,
|
"triangles": 4400,
|
||||||
"vertices": 4026
|
"vertices": 4075
|
||||||
},
|
},
|
||||||
"npc_firefighter": {
|
"npc_firefighter": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -609,7 +609,7 @@
|
|||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4848,
|
"triangles": 4848,
|
||||||
"vertices": 4735
|
"vertices": 4831
|
||||||
},
|
},
|
||||||
"npc_guard": {
|
"npc_guard": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -652,8 +652,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4560,
|
"triangles": 4558,
|
||||||
"vertices": 4529
|
"vertices": 4652
|
||||||
},
|
},
|
||||||
"npc_hoodie": {
|
"npc_hoodie": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -693,8 +693,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4826,
|
"triangles": 4816,
|
||||||
"vertices": 4504
|
"vertices": 4425
|
||||||
},
|
},
|
||||||
"npc_jacket": {
|
"npc_jacket": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -734,8 +734,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4394,
|
"triangles": 4384,
|
||||||
"vertices": 4175
|
"vertices": 4130
|
||||||
},
|
},
|
||||||
"npc_vest": {
|
"npc_vest": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
@@ -775,8 +775,8 @@
|
|||||||
"accessory",
|
"accessory",
|
||||||
"hair"
|
"hair"
|
||||||
],
|
],
|
||||||
"triangles": 4474,
|
"triangles": 4496,
|
||||||
"vertices": 4340
|
"vertices": 4271
|
||||||
},
|
},
|
||||||
"palm_tree": {
|
"palm_tree": {
|
||||||
"bounds_godot": {
|
"bounds_godot": {
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 61 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 67 KiB After Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 69 KiB |
|
Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 66 KiB |
|
Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 57 KiB |
|
Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 63 KiB |
|
Before Width: | Height: | Size: 61 KiB After Width: | Height: | Size: 60 KiB |
|
Before Width: | Height: | Size: 35 KiB After Width: | Height: | Size: 34 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,974–5,026 triangles
|
the 2026-11-01 face/hand pass). The current range is 3,984–4,978 triangles
|
||||||
and 3,744–4,807 vertices.
|
and 3,793–4,831 vertices.
|
||||||
|
|
||||||
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,
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ Committed contact sheets and local Blender renders for the character, gear and p
|
|||||||
| <img src="sheet_npc.jpg" width="280" alt="sheet_npc.jpg"> | [`sheet_npc.jpg`](sheet_npc.jpg) |
|
| <img src="sheet_npc.jpg" width="280" alt="sheet_npc.jpg"> | [`sheet_npc.jpg`](sheet_npc.jpg) |
|
||||||
| <img src="sheet_npc_baseline.jpg" width="280" alt="sheet_npc_baseline.jpg"> | [`sheet_npc_baseline.jpg`](sheet_npc_baseline.jpg) |
|
| <img src="sheet_npc_baseline.jpg" width="280" alt="sheet_npc_baseline.jpg"> | [`sheet_npc_baseline.jpg`](sheet_npc_baseline.jpg) |
|
||||||
| <img src="sheet_npc_baseline_compare-p1.jpg" width="280" alt="sheet_npc_baseline_compare-p1.jpg"> | [`sheet_npc_baseline_compare-p1.jpg`](sheet_npc_baseline_compare-p1.jpg) |
|
| <img src="sheet_npc_baseline_compare-p1.jpg" width="280" alt="sheet_npc_baseline_compare-p1.jpg"> | [`sheet_npc_baseline_compare-p1.jpg`](sheet_npc_baseline_compare-p1.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_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: 298 KiB |
@@ -39,6 +39,7 @@ Implemented so far:
|
|||||||
- Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
|
- Aim-free selection pass (v0.3.0 work, [ADR-0011](adr/0011-aim-free-selection-and-precision-walk.md)): the focus cone ranks candidates by angle then distance, `Tab` and the mouse wheel cycle nearby people nearest-first and pin the selection, right click clears focus with a short re-acquire grace, the HUD reports how many people are in the ring, and holding `Shift` walks at half speed for close work
|
||||||
|
|
||||||
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks.
|
- Visual alignment: start/venue menu, pause, settings, shift review, radio, wand, inspection, timeline, HUD target chip/escalation bar, and radial menu now use or match their AI references in `assets/images/ui/`. The runtime art loader (`src/game/ui/art.gd`) falls back cleanly when a file is missing. The wand panel lists the guest's carried items, and multiline HUD/wand strings render real line breaks.
|
||||||
|
- 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 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.
|
||||||
|
|||||||
@@ -48,14 +48,14 @@ than the slim build.
|
|||||||
|
|
||||||
| Character | Height | Head | Aspect | Shoulder | Hip | Torso | Leg | Arm | Foot | Result |
|
| Character | Height | Head | Aspect | Shoulder | Hip | Torso | Leg | Arm | Foot | Result |
|
||||||
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
|
| --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | --- |
|
||||||
| `npc_casual` | 1.824 m | 0.190 | 0.907 | 0.320 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
| `npc_casual` | 1.824 m | 0.190 | 0.907 | 0.321 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
| `npc_jacket` | 1.824 m | 0.190 | 0.907 | 0.325 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
| `npc_jacket` | 1.824 m | 0.190 | 0.907 | 0.333 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
| `npc_coat` | 1.824 m | 0.190 | 0.907 | 0.329 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
| `npc_coat` | 1.824 m | 0.190 | 0.907 | 0.334 | 0.209 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
| `npc_vest` | 1.819 m | 0.191 | 0.907 | 0.321 | 0.210 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
|
| `npc_vest` | 1.819 m | 0.191 | 0.907 | 0.322 | 0.210 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
|
||||||
| `npc_dress` | 1.825 m | 0.190 | 0.907 | 0.287 | 0.191 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
| `npc_dress` | 1.825 m | 0.190 | 0.907 | 0.289 | 0.191 | 0.307 | 0.449 | 0.329 | 0.144 | PASS |
|
||||||
| `npc_hoodie` | 1.856 m | 0.187 | 0.907 | 0.320 | 0.205 | 0.302 | 0.442 | 0.323 | 0.141 | PASS |
|
| `npc_hoodie` | 1.856 m | 0.187 | 0.907 | 0.328 | 0.205 | 0.302 | 0.442 | 0.323 | 0.141 | PASS |
|
||||||
| `npc_firefighter` | 1.819 m | 0.191 | 0.907 | 0.356 | 0.225 | 0.308 | 0.451 | 0.330 | 0.148 | PASS |
|
| `npc_firefighter` | 1.819 m | 0.191 | 0.907 | 0.358 | 0.225 | 0.308 | 0.451 | 0.330 | 0.148 | PASS |
|
||||||
| `npc_guard` | 1.819 m | 0.191 | 0.907 | 0.370 | 0.219 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
|
| `npc_guard` | 1.819 m | 0.191 | 0.907 | 0.369 | 0.219 | 0.308 | 0.451 | 0.330 | 0.144 | PASS |
|
||||||
|
|
||||||
No variant is outside a band, so the body pass does not need a global skeleton
|
No variant is outside a band, so the body pass does not need a global skeleton
|
||||||
or mesh rescale. The values closest to a boundary are:
|
or mesh rescale. The values closest to a boundary are:
|
||||||
@@ -63,7 +63,7 @@ or mesh rescale. The values closest to a boundary are:
|
|||||||
- **head height 0.190** against the 0.22 upper bound, and **head aspect 0.907**
|
- **head height 0.190** against the 0.22 upper bound, and **head aspect 0.907**
|
||||||
against 1.00 — the head is deliberately large. Any head-size change must be
|
against 1.00 — the head is deliberately large. Any head-size change must be
|
||||||
coordinated with the face/headgear pass and its gear-fit measurements.
|
coordinated with the face/headgear pass and its gear-fit measurements.
|
||||||
- **guard shoulder 0.370** and **firefighter hip 0.225** against the broad-build
|
- **guard shoulder 0.369** and **firefighter hip 0.225** against the broad-build
|
||||||
upper bounds — these are the intended uniform/turnout silhouettes, not
|
upper bounds — these are the intended uniform/turnout silhouettes, not
|
||||||
outliers.
|
outliers.
|
||||||
- **leg length 0.449** and **torso 0.307** sit inside the guide for all variants;
|
- **leg length 0.449** and **torso 0.307** sit inside the guide for all variants;
|
||||||
|
|||||||
@@ -98,17 +98,17 @@ 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,974 | 3,744 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
|
| `npc_casual` | 3,984 | 3,793 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
|
||||||
| `npc_jacket` | 4,394 | 4,175 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
|
| `npc_jacket` | 4,384 | 4,130 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
|
||||||
| `npc_coat` | 5,026 | 4,807 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
|
| `npc_coat` | 4,978 | 4,776 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
|
||||||
| `npc_vest` | 4,474 | 4,340 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
|
| `npc_vest` | 4,496 | 4,271 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
|
||||||
| `npc_dress` | 4,428 | 4,026 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
|
| `npc_dress` | 4,400 | 4,075 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
|
||||||
| `npc_hoodie` | 4,826 | 4,504 | 0.315 m | 0.190 m (hip) | — |
|
| `npc_hoodie` | 4,816 | 4,425 | 0.315 m | 0.190 m (hip) | — |
|
||||||
| `npc_firefighter` | 4,848 | 4,735 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
|
| `npc_firefighter` | 4,848 | 4,831 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
|
||||||
| `npc_guard` | 4,560 | 4,529 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
|
| `npc_guard` | 4,558 | 4,652 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
|
||||||
|
|
||||||
The widest character is `npc_coat` at 5,026 triangles and 4,807 vertices, so
|
The widest character is `npc_firefighter` at 4,848 triangles and 4,831 vertices, so
|
||||||
the current headroom is 974 triangles and 193 vertices per character. Any face
|
the current headroom is 1,152 triangles and 169 vertices per character. Any face
|
||||||
or hand refinement must come out of that budget or replace existing detail. The
|
or hand refinement must come out of that budget or replace existing detail. The
|
||||||
refit backpack measures 380 triangles / 406 vertices; the character and gear
|
refit backpack measures 380 triangles / 406 vertices; the character and gear
|
||||||
table above remains valid because the character meshes did not change.
|
table above remains valid because the character meshes did not change.
|
||||||
@@ -183,7 +183,7 @@ python3 tools/npc_baseline.py compare \
|
|||||||
|
|
||||||
The comparison sheet places the before/after pair for each view side by side.
|
The comparison sheet places the before/after pair for each view side by side.
|
||||||
The first run is committed as
|
The first run is committed as
|
||||||
[`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg);
|
[`sheet_npc_baseline_compare-p1.jpg` and `sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg);
|
||||||
keep the baseline sheet as the before artifact and add a new numbered/after
|
keep the baseline sheet as the before artifact and add a new numbered/after
|
||||||
sheet rather than replacing it.
|
sheet rather than replacing it.
|
||||||
|
|
||||||
|
|||||||
@@ -535,15 +535,18 @@ def _skin_body(spec):
|
|||||||
((0, 0, 0.96), 0.150 * w * bulk, 0.105 * d * bulk), # 1 waist
|
((0, 0, 0.96), 0.150 * w * bulk, 0.105 * d * bulk), # 1 waist
|
||||||
((0, 0.0, 1.18), 0.170 * w * bulk, 0.115 * d * bulk), # 2 chest
|
((0, 0.0, 1.18), 0.170 * w * bulk, 0.115 * d * bulk), # 2 chest
|
||||||
((0, 0.0, 1.355), 0.172 * w * bulk, 0.108 * d * bulk), # 3 upper chest
|
((0, 0.0, 1.355), 0.172 * w * bulk, 0.108 * d * bulk), # 3 upper chest
|
||||||
((0, 0, 1.44), 0.058, 0.058), # 4 neck base
|
# A slightly fuller neck base and a lower shoulder root give the
|
||||||
((0, -0.005, 1.53), 0.052, 0.052), # 5 neck top
|
# head/neck/shoulder line a trapezius slope instead of a thin post on
|
||||||
|
# a hanger. The head and collar still sit above this band.
|
||||||
|
((0, 0, 1.43), 0.074, 0.072), # 4 neck base
|
||||||
|
((0, -0.005, 1.53), 0.060, 0.060), # 5 neck top
|
||||||
]
|
]
|
||||||
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
|
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
|
||||||
for s in (-1.0, 1.0):
|
for s in (-1.0, 1.0):
|
||||||
base = len(nodes)
|
base = len(nodes)
|
||||||
nodes += [
|
nodes += [
|
||||||
((0.20 * s * w, 0, 1.39), 0.075 * t * sleeve, 0.07 * t * sleeve), # shoulder
|
((0.20 * s * w, 0, 1.37), 0.080 * t * sleeve, 0.074 * t * sleeve), # shoulder
|
||||||
((0.27 * s * w, 0, 1.24), 0.058 * t * sleeve, 0.058 * t * sleeve), # upper arm
|
((0.27 * s * w, 0, 1.23), 0.058 * t * sleeve, 0.058 * t * sleeve), # upper arm
|
||||||
((0.275 * s * w, 0.005, 1.06), 0.046 * t * sleeve, 0.048 * t * sleeve), # elbow
|
((0.275 * s * w, 0.005, 1.06), 0.046 * t * sleeve, 0.048 * t * sleeve), # elbow
|
||||||
((0.275 * s * w, -0.01, 0.80), 0.034 * t, 0.030 * t), # wrist
|
((0.275 * s * w, -0.01, 0.80), 0.034 * t, 0.030 * t), # wrist
|
||||||
]
|
]
|
||||||
@@ -929,6 +932,23 @@ def build_character(name):
|
|||||||
rest.append(cl)
|
rest.append(cl)
|
||||||
|
|
||||||
mesh = join(rest, "npc_base")
|
mesh = join(rest, "npc_base")
|
||||||
|
# The fuller neck/shoulder skin graph can leave a few collinear triangles
|
||||||
|
# after the decimate step. Remove them before UV/export; Godot warns about
|
||||||
|
# zero-area faces during LOD generation and `audit` rejects them.
|
||||||
|
bm = bmesh.new()
|
||||||
|
bm.from_mesh(mesh.data)
|
||||||
|
bmesh.ops.dissolve_degenerate(bm, dist=2e-4, edges=bm.edges)
|
||||||
|
# Skin-modifier branch cleanup can still leave a handful of zero-area
|
||||||
|
# faces; Godot's LOD generator treats them as malformed. Triangulate first
|
||||||
|
# because a degenerate quad can become a zero-area triangle on export, then
|
||||||
|
# drop the tiny faces explicitly.
|
||||||
|
bmesh.ops.triangulate(bm, faces=bm.faces[:], quad_method="BEAUTY",
|
||||||
|
ngon_method="BEAUTY")
|
||||||
|
bad = [face for face in bm.faces if face.calc_area() < 1e-10]
|
||||||
|
if bad:
|
||||||
|
bmesh.ops.delete(bm, geom=bad, context="FACES_ONLY")
|
||||||
|
bm.to_mesh(mesh.data)
|
||||||
|
bm.free()
|
||||||
# Every contract slot must exist even if a variant does not paint with it.
|
# Every contract slot must exist even if a variant does not paint with it.
|
||||||
present = {m.name for m in mesh.data.materials if m}
|
present = {m.name for m in mesh.data.materials if m}
|
||||||
for slot in SLOTS:
|
for slot in SLOTS:
|
||||||
@@ -1019,10 +1039,10 @@ def build_gear(name):
|
|||||||
# torso front (Godot z≈0.21). The old chest/waist points sat at
|
# torso front (Godot z≈0.21). The old chest/waist points sat at
|
||||||
# z≈0.13 inside the body, so only thin tube fragments read as two
|
# z≈0.13 inside the body, so only thin tube fragments read as two
|
||||||
# rigid black bars from the front.
|
# rigid black bars from the front.
|
||||||
p.append(tube("Strap", [(0.09 * s, 0.13, 0.27), (0.10 * s, -0.02, 0.29),
|
p.append(tube("Strap", [(0.09 * s, 0.13, 0.27), (0.11 * s, -0.02, 0.29),
|
||||||
(0.105 * s, -0.06, 0.25),
|
(0.12 * s, -0.06, 0.25),
|
||||||
(0.11 * s, -0.19, 0.18), (0.12 * s, -0.19, -0.08)],
|
(0.14 * s, -0.19, 0.18), (0.145 * s, -0.19, -0.08)],
|
||||||
0.018, M("plastic_black"), 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":
|
||||||
# Keep the bag's inner face outside the widest skirt flare (dress = 0.26):
|
# Keep the bag's inner face outside the widest skirt flare (dress = 0.26):
|
||||||
|
|||||||