Recess NPC eyes and sculpt the jaw and cheeks
Full build / Import + headless tests (push) Successful in 38s
Full build / Full desktop build + optional release (push) Skipped

This commit is contained in:
zwitschi committed 2026-11-02 19:08:26 +01:00
1 parent 15ab8c686d
commit 03c355e7c1
26 files changed
+117 -70

No files matched your search

+2
View File
@@ -10,6 +10,8 @@ Semantic Versioning.
- Character necks are fuller and shoulders slope from the neck instead of - Character necks are fuller and shoulders slope from the neck instead of
reading as a thin post on a hanger. reading as a thin post on a hanger.
- Character eyes sit in the socket behind a thin upper lid, and the chin and
cheekbones are sculpted into the head instead of reading as separate spheres.
- 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
+5
View File
@@ -81,6 +81,11 @@ Session memory for durable decisions, state, and gotchas. Keep concise, remove o
1.37 m. A final triangulate + tiny-face delete in `build_character` removes zero-area triangles 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; 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. `npc_proportions.py` still passes and `sheet_npc_baseline_compare-body.jpg` is the review image.
- **NPC face-depth pass (0.9.4, 2026-11-02):** recessed the sclera to `ey=-0.134` and added a
thin upper lid; removed the separate jaw/cheek spheres and instead sculpt front head vertices
(`_shape_head_mesh`) around a chin plate and cheekbones. This avoids dark AO intersection rings.
Moved lips forward to stay on the reshaped surface. Latest review sheet:
`sheet_npc_baseline_compare-face.jpg`; max character is now 4,754 tris / 4,574 verts.
- **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
+7 -4
View File
@@ -99,10 +99,13 @@ and equipment fit.
### Head, face and hair ### Head, face and hair
- [ ] Refine the head volume and jaw/cheek/chin proportions against the portrait concept; keep the - [x] Refine the head volume and jaw/cheek/chin proportions against the portrait concept; keep the
face front direction unambiguous for the focus cone and head-turn. face front direction unambiguous for the focus cone and head-turn. (Chin plate and cheekbones
- [ ] Improve brow/eye-socket depth, eyelid/iris readability, nose bridge/tip, lips and ear shape sculpted into the head mesh; no separate jaw/cheek spheres or AO intersection rings.)
without turning the low-poly face into a noisy mesh. - [x] Improve brow/eye-socket depth and eyelid/iris readability without turning the low-poly face
into a noisy mesh. (Recessed sclera, thin upper lid; `sheet_npc_baseline_compare-face.jpg`.)
- [ ] Refine the nose bridge/tip, lips and ear shape; keep the five tint slots and face direction
unchanged.
- [ ] Audit hair caps, fringes, side locks, buns and beard silhouettes for gaps, intersections and - [ ] Audit hair caps, fringes, side locks, buns and beard silhouettes for gaps, intersections and
obvious scalp seams; keep hair color in the tintable slot. obvious scalp seams; keep hair color in the tintable slot.
- [ ] Verify hats/hoods/sunglasses/helmets still clear the refined brow, ears and hair; update gear - [ ] Verify hats/hoods/sunglasses/helmets still clear the refined brow, ears and hair; update gear
+16 -16
View File
@@ -487,8 +487,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 3984, "triangles": 3760,
"vertices": 3793 "vertices": 3556
}, },
"npc_coat": { "npc_coat": {
"bounds_godot": { "bounds_godot": {
@@ -528,8 +528,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4978, "triangles": 4754,
"vertices": 4776 "vertices": 4574
}, },
"npc_dress": { "npc_dress": {
"bounds_godot": { "bounds_godot": {
@@ -567,8 +567,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4400, "triangles": 4176,
"vertices": 4075 "vertices": 3863
}, },
"npc_firefighter": { "npc_firefighter": {
"bounds_godot": { "bounds_godot": {
@@ -608,8 +608,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4848, "triangles": 4624,
"vertices": 4831 "vertices": 4487
}, },
"npc_guard": { "npc_guard": {
"bounds_godot": { "bounds_godot": {
@@ -652,8 +652,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4558, "triangles": 4334,
"vertices": 4652 "vertices": 4456
}, },
"npc_hoodie": { "npc_hoodie": {
"bounds_godot": { "bounds_godot": {
@@ -693,8 +693,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4816, "triangles": 4592,
"vertices": 4425 "vertices": 4187
}, },
"npc_jacket": { "npc_jacket": {
"bounds_godot": { "bounds_godot": {
@@ -734,8 +734,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4384, "triangles": 4160,
"vertices": 4130 "vertices": 3930
}, },
"npc_vest": { "npc_vest": {
"bounds_godot": { "bounds_godot": {
@@ -775,8 +775,8 @@
"accessory", "accessory",
"hair" "hair"
], ],
"triangles": 4496, "triangles": 4272,
"vertices": 4271 "vertices": 4186
}, },
"palm_tree": { "palm_tree": {
"bounds_godot": { "bounds_godot": {
Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

After

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 57 KiB

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

After

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.
Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

After

Width:  |  Height:  |  Size: 59 KiB

+2 -2
View File
@@ -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,984–4,978 triangles the 2026-11-01 face/hand pass). The current range is 3,760–4,754 triangles
and 3,793–4,831 vertices. and 3,556–4,574 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,
+1
View File
@@ -46,6 +46,7 @@ Committed contact sheets and local Blender renders for the character, gear and p
| <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_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_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) |
Binary file not shown.

After

Width:  |  Height:  |  Size: 297 KiB

+1
View File
@@ -40,6 +40,7 @@ Implemented so far:
- 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 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 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.
+35 -30
View File
@@ -1,10 +1,12 @@
# NPC visual baseline and acceptance criteria # NPC visual baseline and acceptance criteria
Status: baseline captured 2026-11-02; the first P1 fixes were applied the same Status: baseline captured 2026-11-02. The first P1 fixes (tourist photo-prop
day (tourist photo-prop visibility and backpack strap geometry). The face pass visibility, backpack straps), the neck/shoulder transition and the face-depth
and the remaining body/clothing work are still open. The baseline remains the pass are applied. Remaining body/clothing work is open; the baseline remains the
reference for the 0.9.4 NPC visual optimization; the post-fix comparison is reference for 0.9.4. Comparison sheets:
[`blender_previews/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)
and the latest [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
## What was captured ## What was captured
@@ -98,20 +100,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,984 | 3,793 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 | | `npc_casual` | 3,760 | 3,556 | 0.315 m | 0.191 m (hip) | backpack x −0.041, radial −0.162 |
| `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_jacket` | 4,160 | 3,930 | 0.315 m | 0.191 m (hip) | lanyard x −0.116, radial +0.023 (runtime +4 cm forward) |
| `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_coat` | 4,754 | 4,574 | 0.315 m | 0.225 m (skirt) | sunglasses width ratio 0.641; bag x +0.037, radial −0.130 |
| `npc_vest` | 4,496 | 4,271 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 | | `npc_vest` | 4,272 | 4,186 | 0.315 m | 0.191 m (hip) | cap width ratio 1.067 |
| `npc_dress` | 4,400 | 4,075 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 | | `npc_dress` | 4,176 | 3,863 | 0.315 m | 0.260 m (skirt) | bag x +0.002, radial −0.095 |
| `npc_hoodie` | 4,816 | 4,425 | 0.315 m | 0.190 m (hip) | — | | `npc_hoodie` | 4,592 | 4,187 | 0.315 m | 0.190 m (hip) | — |
| `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_firefighter` | 4,624 | 4,487 | 0.315 m | 0.255 m (skirt) | helmet width ratio 1.651; SCBA x −0.105, radial −0.076 |
| `npc_guard` | 4,558 | 4,652 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 | | `npc_guard` | 4,334 | 4,456 | 0.315 m | 0.194 m (hip) | cap width ratio 1.067 |
The widest character is `npc_firefighter` at 4,848 triangles and 4,831 vertices, so The widest character is `npc_coat` at 4,754 triangles and 4,574 vertices, so the
the current headroom is 1,152 triangles and 169 vertices per character. Any face current headroom is 1,246 triangles and 426 vertices per character. Any hand or
or hand refinement must come out of that budget or replace existing detail. The clothing 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.
table above remains valid because the character meshes did not change.
## Baseline findings, ranked ## Baseline findings, ranked
@@ -124,11 +125,12 @@ table above remains valid because the character meshes did not change.
photo prop is now hidden outside `photo`/`film` poses photo prop is now hidden outside `photo`/`film` poses
(`photo_prop_visible_for_pose()`), so it only appears when the character is (`photo_prop_visible_for_pose()`), so it only appears when the character is
actually taking a picture; the after frame confirms one bag at rest. actually taking a picture; the after frame confirms one bag at rest.
2. **The face is the largest portrait gap.** The eyes, nose and lips are 2. **Face depth. Fixed 2026-11-02.** The sclera was recessed into the socket and
readable but sit on a shallow, faceted head: the eye whites protrude, the wrapped with a thin upper lid; the separate jaw/cheek spheres were replaced
eyelid/cheek transition is missing, and the mouth is a dark slot. This is by a chin plate and cheekbones sculpted into the head mesh. That removes the
intentionally stylized, but the head volume and eye socket are the first dark intersection rings that read as a beard shadow and keeps the mouth on
things a player sees at conversation range. the surface. The mouth bar and low-poly brows remain stylized, but the eye
socket and jaw/cheek transition now read as one head volume.
3. **Backpack straps read as rigid black bars. Refit 2026-11-02.** The straps 3. **Backpack straps read as rigid black bars. Refit 2026-11-02.** The straps
were thin round tubes whose chest/waist points sat inside the torso, so only were thin round tubes whose chest/waist points sat inside the torso, so only
fragments showed from the front. They are now flattened bands whose front path fragments showed from the front. They are now flattened bands whose front path
@@ -182,17 +184,20 @@ 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 committed runs are
[`sheet_npc_baseline_compare-p1.jpg` and `sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg); [`sheet_npc_baseline_compare-p1.jpg`](blender_previews/sheet_npc_baseline_compare-p1.jpg),
keep the baseline sheet as the before artifact and add a new numbered/after [`sheet_npc_baseline_compare-body.jpg`](blender_previews/sheet_npc_baseline_compare-body.jpg)
sheet rather than replacing it. and [`sheet_npc_baseline_compare-face.jpg`](blender_previews/sheet_npc_baseline_compare-face.jpg).
Keep the baseline sheet as the before artifact and add a new comparison sheet
rather than replacing it.
## Next implementation order ## Next implementation order
1. Refine the head/eye socket/jaw volume without changing the face direction or 1. Improve the torso/hip transition and the remaining body-shape variation.
the five tint slots.
2. Add a conversation-distance hand/grip capture and fix hand geometry before 2. Add a conversation-distance hand/grip capture and fix hand geometry before
judging hand items. judging hand items.
3. Audit hems and footwear in walk/sit/block poses. 3. Audit hems and footwear in walk/sit/block poses.
4. Re-run the 48-frame baseline, the 96-frame final-context review, the 24-NPC 4. Refine nose/lip/ear shapes only if the portrait pass still flags them after
the hands and clothing read cleanly.
5. Re-run the 48-frame baseline, the 96-frame final-context review, the 24-NPC
budget and `blender_assets.py audit/fit`; publish the next comparison sheet. budget and `blender_assets.py audit/fit`; publish the next comparison sheet.
+48 -18
View File
@@ -728,23 +728,48 @@ def _hood(name, mat, head_center):
return _finish(_link(name, bm), mat, smooth=True, sharp_angle=60) return _finish(_link(name, bm), mat, smooth=True, sharp_angle=60)
def _shape_head_mesh(head):
"""Sculpt a chin plate and cheekbones into the head sphere itself.
Separate jaw/cheek spheres leave visible intersection rings in the baked AO,
which read as a beard shadow. Moving the head's own front vertices keeps the
surface continuous while still giving the key light something to catch.
"""
bm = bmesh.new()
bm.from_mesh(head.data)
for v in bm.verts:
x, y, z = v.co
if y >= 0.0:
continue
ax = abs(x)
# Chin/jaw plate: forward and slightly wider below the mouth.
jaw = (max(0.0, 1.0 - abs(z - 1.565) / 0.055)
* max(0.0, 1.0 - ax / 0.075))
v.co.y -= 0.012 * jaw
v.co.x *= 1.0 + 0.06 * jaw
# Cheekbone: a shallow, wide lift under each eye.
cheek = (max(0.0, 1.0 - abs(z - 1.610) / 0.040)
* max(0.0, 1.0 - abs(ax - 0.072) / 0.050))
v.co.y -= 0.006 * cheek
bm.normal_update()
bm.to_mesh(head.data)
bm.free()
def _face(mats, spec): def _face(mats, spec):
"""Head with readable eyes, brow ridge, nose, lips and ears. """Head with readable eyes, brow ridge, nose, lips and ears.
At conversation distance the eye region carries the read, so each eye gets a At conversation distance the eye region carries the read, so each eye gets a
white sclera, a dark iris and an upper lid, and the face gets cheekbones and white sclera, a dark iris and an upper lid, and the face gets sculpted
a nose bridge instead of a single cone (realism pass detail work). cheekbones and a nose bridge instead of a single cone (realism pass detail
work).
""" """
skin, acc, hair = mats["skin"], mats["accessory"], mats["hair"] skin, acc, hair = mats["skin"], mats["accessory"], mats["hair"]
white = material("eye_white", textured_solid=True) white = material("eye_white", textured_solid=True)
parts = [sphere("Head", 0.155, (0, 0, 1.635), skin, seg=18, rings=12, head = sphere("Head", 0.155, (0, 0, 1.635), skin, seg=18, rings=12,
scale=(0.92, 1.0, 1.12))] scale=(0.92, 1.0, 1.12))
# jaw / chin and cheekbones soften the egg into a face _shape_head_mesh(head)
parts.append(sphere("Jaw", 0.10, (0, -0.04, 1.556), skin, seg=12, rings=8, parts = [head]
scale=(1.0, 1.0, 0.8)))
for s in (-1.0, 1.0):
parts.append(sphere("Cheek", 0.034, (0.076 * s, -0.104, 1.606), skin, seg=8,
rings=5, scale=(1.0, 0.7, 0.8)))
# nose: bridge and ball (nostrils were cut to stay inside the vertex budget) # nose: bridge and ball (nostrils were cut to stay inside the vertex budget)
parts.append(cyl("NoseBridge", 0.016, 0.022, 0.06, (0, -0.145, 1.652), skin, seg=8, parts.append(cyl("NoseBridge", 0.016, 0.022, 0.06, (0, -0.145, 1.652), skin, seg=8,
rot=(math.radians(-72), 0, 0), smooth=True)) rot=(math.radians(-72), 0, 0), smooth=True))
@@ -756,20 +781,25 @@ def _face(mats, spec):
scale=(0.42, 1.0, 1.35))) scale=(0.42, 1.0, 1.35)))
parts.append(sphere("EarLobe", 0.018, (0.142 * s, -0.005, 1.605), skin, seg=8, parts.append(sphere("EarLobe", 0.018, (0.142 * s, -0.005, 1.605), skin, seg=8,
rings=5, scale=(0.45, 1.0, 0.9))) rings=5, scale=(0.45, 1.0, 0.9)))
# eye: sclera, iris, upper lid and brow # Eye: recess the sclera into the socket, then wrap it with skin lids.
ex, ey, ez = 0.052 * s, -0.138, 1.664 # The old white ball sat ~1 cm proud of the head; this keeps the iris
# readable without the doll-like bulge.
ex, ey, ez = 0.052 * s, -0.134, 1.664
parts.append(sphere("EyeWhite", 0.019, (ex, ey, ez), white, seg=8, rings=5, parts.append(sphere("EyeWhite", 0.019, (ex, ey, ez), white, seg=8, rings=5,
scale=(1.25, 0.7, 0.85))) scale=(1.25, 0.62, 0.85)))
parts.append(sphere("Iris", 0.0105, (ex, ey - 0.013, ez), acc, seg=8, rings=5, parts.append(sphere("Iris", 0.0105, (ex, ey - 0.007, ez), acc, seg=8, rings=5,
scale=(1.0, 0.55, 1.0))) scale=(1.0, 0.55, 1.0)))
parts.append(box("Lid", (0.038, 0.010, 0.008), (ex, ey + 0.004, ez + 0.014), # One thin upper lid; no lower lid, which made the eyes read as
skin, rot=(math.radians(-10), 0, 0))) # half-closed slits at conversation distance.
parts.append(sphere("UpperLid", 0.020, (ex, ey - 0.006, ez + 0.014), skin,
seg=8, rings=4, scale=(1.20, 0.32, 0.24),
rot=(math.radians(-10), 0, 0)))
parts.append(box("Brow", (0.055, 0.014, 0.012), (0.052 * s, -0.143, 1.699), hair, parts.append(box("Brow", (0.055, 0.014, 0.012), (0.052 * s, -0.143, 1.699), hair,
rot=(0, 0.14 * s, 0.0))) rot=(0, 0.14 * s, 0.0)))
# lips: upper and lower instead of a single bar # lips: upper and lower instead of a single bar
parts.append(box("LipUpper", (0.052, 0.012, 0.008), (0, -0.145, 1.588), acc, parts.append(box("LipUpper", (0.052, 0.012, 0.008), (0, -0.156, 1.588), acc,
rot=(math.radians(6), 0, 0))) rot=(math.radians(6), 0, 0)))
parts.append(box("LipLower", (0.046, 0.012, 0.009), (0, -0.143, 1.578), acc)) parts.append(box("LipLower", (0.046, 0.012, 0.009), (0, -0.154, 1.578), acc))
if spec.get("beard"): if spec.get("beard"):
parts.append(sphere("Beard", 0.105, (0, -0.06, 1.55), hair, seg=12, rings=8, parts.append(sphere("Beard", 0.105, (0, -0.06, 1.55), hair, seg=12, rings=8,
scale=(1.0, 0.9, 0.75), cut_below=-0.2)) scale=(1.0, 0.9, 0.75), cut_below=-0.2))