Compare commits
11
Commits
d1898b3221
...
f0d4b81239
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f0d4b81239 | ||
|
|
48a9795248 | ||
|
|
a5e62534ce | ||
|
|
cd7697cba4 | ||
|
|
d2f773c7ad | ||
|
|
a3c3a37270 | ||
|
|
8dd4f77901 | ||
|
|
ba444152c9 | ||
|
|
d56f14c6dd | ||
|
|
b297c4543f | ||
|
|
958d3ae0ff |
No files matched your search
+8
-4
@@ -15,6 +15,10 @@ been cleared for public distribution.
|
||||
|
||||
## 3D characters, gear and props
|
||||
|
||||
The local MakeHuman head prototype uses the CC0 MakeHuman/MPFB base mesh
|
||||
(see [`docs/makehuman-head-review.md`](docs/makehuman-head-review.md)); it is a
|
||||
development prototype, not a shipped character.
|
||||
|
||||
The character, gear and prop library is generated by project-owned scripts
|
||||
(`tools/blender_assets.py`, `tools/npc_pipeline.py`, `tools/model_pipeline.py`)
|
||||
using Blender. The `.blend` working sources are developer artifacts and are not
|
||||
@@ -42,10 +46,10 @@ shipped derivative of CesiumMan or RiggedFigure must keep the attribution above.
|
||||
|
||||
### MakeHuman head source
|
||||
|
||||
The decimated head used by the character head prototype is derived from the
|
||||
MakeHuman / MPFB base mesh, **CC0** (`assets/models/blender_src/makehuman_head.glb`;
|
||||
source `tmp/npc-research/makehuman/base.obj` with its bundled
|
||||
`LICENSE.md`/`LICENSE.ASSETS.md`). It is retargeted, decimated and re-weighted by
|
||||
The standalone MakeHuman head prototype is derived from the MakeHuman / MPFB base
|
||||
mesh, **CC0** (`assets/models/blender_src/makehuman_head.glb`; source
|
||||
`tmp/npc-research/makehuman/base.obj` with its bundled
|
||||
`LICENSE.md`/`LICENSE.ASSETS.md`). It is cut, retopologised and re-weighted by
|
||||
project scripts; the exported character GLBs remain project assets. See ADR-0015
|
||||
and `docs/npc-model-sourcing.md`.
|
||||
|
||||
|
||||
@@ -10,6 +10,58 @@ canonical implemented-features list in `docs/development-status.md`.
|
||||
the licence gate. Downloads live in `tmp/research/` (gitignored): DDG image search, street-view
|
||||
frames, neighbouring buildings — never committed, never shipped. Promotion into
|
||||
`docs/screens/refs/` always passes the sidecar licence + privacy gate and `CREDITS.md`.
|
||||
- **MakeHuman head accepted (2026-11-04, owner):** the basic human head is a pass.
|
||||
`assets/models/blender_src/makehuman_head.glb` is the CC0 MakeHuman-derived head
|
||||
(12,642 tris / 7,690 split verts). The automated vision gate still records REJECT on
|
||||
the eye/lid region (globe depth, lid overlap, iris at three-quarter) as deferred
|
||||
polish, not a blocker. The assembled path (`build_character(head="mh")`) adds a
|
||||
skull-conforming hair cap derived from the closed head shell, hides the scalp under
|
||||
that cap, and narrows the neck/shoulder body. Default head source stays `face` until
|
||||
the owner reviews the hair/body and the 12-variant rollout starts.
|
||||
- **AI-assisted asset review loop (2026-11-04):** head/face shape decisions use a concept +
|
||||
judge loop — generate a target concept (`generate_textures.generate_image`, Seedream) and use
|
||||
`tools/describe_image.py` as the vision judge, iterating on the mesh until the judge's specific
|
||||
fixes converge. This is what picked the MakeHuman head over the procedural displacement head
|
||||
(`_head_proto` oscillates between "decorated egg" and "spiky wedges" at ~3/5 on the coarse
|
||||
sphere). Review page: `docs/screens/head-review/README.md`; methodology note:
|
||||
`docs/3d-model-review.md` §AI-driven head rework.
|
||||
- **MakeHuman hair/body prototype (2026-11-04):** `npc_casual --head mh` is
|
||||
14,753 tris / 12,822 raw / 12,823 seam-split verts / 14 bones / 9 materials,
|
||||
`inspect` clean. All 12 variants were counted at 14,693–15,987 tris. Hair uses flat
|
||||
shading normals because the old hair normal map caused dark tangent patches on the
|
||||
new cap. Scalp faces fully covered by the hair cap are deleted (saves ~950 tris);
|
||||
the accepted face geometry is untouched. The procedural body was narrowed at the
|
||||
neck/shoulders (shoulder root z 1.345, chest 0.158, neck 0.060/0.054) and its skin
|
||||
decimate dropped to 0.21. The local renders are in
|
||||
`tmp/npc-parts/prototype_mh/renders/`.
|
||||
- **MPFB full-body prototype (2026-11-04):** the current body candidate is the local MPFB 2.0.18
|
||||
pipeline at `tmp/npc-research/build_mpfb_npc.py` (dev-only; no `.mhclo` clothing installed).
|
||||
It creates a macro-driven human, bakes targets, adds the `game_engine` rig, captures arm
|
||||
points/weights, removes the rig, applies the helper MASK, scales to 1.81 m, retargets the A-pose
|
||||
arms to the contract wrist, cuts the neck at 1.505, decimates, bisects the clothing boundaries,
|
||||
paints the five slots, adds a short neck flare, project hands, rounded shoes and cuffs, the
|
||||
accepted head and the 14-bone contract armature. Current output: **15,317 tris / 13,690 glTF
|
||||
verts / 13,702 seam-split verts / 1.810 m / 14 bones / 10 materials**; `inspect` clean and feet
|
||||
grounded. The torso is inflated radially by 12% through z 0.72–1.40 to loosen the shirt. The
|
||||
neck is a radial cut of the decimated column bridged by a four-ring, 20-segment lathe (lower
|
||||
rings painted cloth, reads as a crew neck); the ankles are hidden by a bevelled 0.11 m shoe
|
||||
shell, an inset 0.111 × 0.286 m sole, a tongue and a 0.08 m pant cuff. Sleeve cuffs use the
|
||||
`cloth` slot and the `accessory` slot is carried by the sole. A light smoothing pass on the
|
||||
closed thigh/hip region softens the decimated silhouette. Earlier torus collar, cloth cylinder
|
||||
collar, skin neck-blend sphere and destructive neck `smooth_vert` pass failed: the shells read as a
|
||||
brace, shelf or donut, and vertex smoothing tore the open collar boundary into a dark hole. An
|
||||
untucked shirt-hem band at the hips also failed: it read as a hanging white flap at the crotch.
|
||||
Protruding flat sole plates read as skates/bricks and were replaced by an inset sole under a
|
||||
rounded shell; laces were dropped for triangle budget. The neck skin rule needed a `z > 1.35`
|
||||
guard: without it the `radius < 0.08` test painted skin at the crotch. Lowering the hem to
|
||||
z 0.80 (unitard), tilting it up at the front (high-cut swimwear) and a 13% hem flare (peplum)
|
||||
were all rejected; the remaining crotch V is the dark hollow under a body-painted shirt. A quick
|
||||
open, flared solidified tube (0.150→0.196 m, z 1.05→0.80, 24 segments, ~288 tris) also failed:
|
||||
it read as a hanging apron/tabard. The real fix is a shaped shirt shell with armholes and a
|
||||
curved hem, not a tube. Remaining polish: crotch V, partly loosened shirt,
|
||||
angular thigh facets. The prototype uses flat slot colours because the one-shot MPFB UV unwrap
|
||||
does not match the per-slot NPC atlases. Renders:
|
||||
`tmp/npc-parts/mpfb_proto/renders84/`; body-only: `body_only.glb`.
|
||||
- **Project agent skills (2026-11-03):** two tracked skills live in `.agents/skills/`:
|
||||
`gig-line-blender-asset-contract` and `gig-line-release-qa`. They are guidance only,
|
||||
add no runtime dependency and are referenced from `tools/README.md`. The third-party
|
||||
@@ -105,6 +157,27 @@ canonical implemented-features list in `docs/development-status.md`.
|
||||
out of a portrait frame (park the rest of the crowd away instead).
|
||||
- Blender: `bisect_plane` and thin bevels leave zero-area triangles (Godot "non-finite normal in
|
||||
LOD generation"); `_clean()` + a weld after bisect fixes them and the audit rejects them.
|
||||
- MakeHuman head/hair gotchas: the imported head is broad and flat, so a separate hair
|
||||
ellipsoid floats or leaves a bald patch; derive the cap from the closed head shell and
|
||||
boolean-cut the hairline. Do not reuse the skull's UVs with the old `hair_normal.png`
|
||||
(dark tangent patches) — hair skips the normal map. Generate the cap before deleting
|
||||
the hidden scalp, or the cap inherits the hole. The flatpak sandbox cannot read `/tmp`;
|
||||
helper scripts must live under `$PWD`.
|
||||
- MakeHuman OBJ intake transform (`base.obj`): the source is Y-up, 16.946 units tall. To reach
|
||||
the project Blender Z-up / metres / front -Y space: scale 0.10739, translate y +0.907 (ground
|
||||
the feet), then rotate +90° around X (maps +Y→+Z up and +Z→−Y front). The full-res head carries
|
||||
internal geometry (eyes/teeth/mouth shells) and is non-manifold; voxel-remesh and Catmull-Clark
|
||||
subdivision both fragment it into shards. On the decimated head, weld first
|
||||
(`remove_doubles` ~0.0002) then Laplacian `smooth_vert`; subdivision without the weld splits
|
||||
along the GLB seam vertices.
|
||||
- MPFB gotchas: call `TargetService.bake_targets(body)` before any decimate (shape-key
|
||||
modifier error); add the built-in rig and capture arm weights before applying the MASK
|
||||
modifier, or `build_basemesh_position_info` divides by zero on an emptied bone group;
|
||||
decimate before bisecting the clothing loops, or the decimator collapses the bisect
|
||||
into a sawtooth. Clear custom split normals after joining (imported MPFB normals leave
|
||||
flat-shaded thigh facets). Imported GLBs may include helper `Cube`/`Icosphere` meshes at
|
||||
±1.0, so select `npc_base` explicitly. Delete the MPFB hands by hand/finger vertex-group
|
||||
weights, not only by `|x|`, or leftover fingers become pants-coloured fins.
|
||||
- Image models have per-model size floors/caps (Seedream needs 1920+; `GEN_SIZE=2048`, do not
|
||||
lower; gpt-5-image-mini caps lower). `generate_image` applies model-specific sizing; concept
|
||||
generation defaults to `--request-size 1536`.
|
||||
@@ -169,6 +242,8 @@ canonical implemented-features list in `docs/development-status.md`.
|
||||
- Character checks: `python3 tools/blender_assets.py inspect|audit|fit`,
|
||||
`python3 tools/npc_proportions.py`
|
||||
- Docs: `python3 tools/check_docs.py` (every tracked image must be linked from its folder README)
|
||||
- Head/part pipeline: `python3 tools/npc_parts.py build|extract|assemble|inspect|sheet|compare`
|
||||
(Blender subcommands run under flatpak). Head sources: `build_character(head="face"|"proto"|"mh")`.
|
||||
- Reference research: `python3 tools/refcollect.py search <q> --slug <block> --limit N`
|
||||
(local-only), `tools/refboard.py` (Commons), `tools/three_up.py compose/rows`,
|
||||
`tools/scene_concepts.py generate/anchor/realism`
|
||||
@@ -188,6 +263,6 @@ Comedy Store map rebuild are complete. **Priority (2026-11-04, owner):** 1) the
|
||||
model overhaul** (head/face/hands, local part pipeline), 2) finish the MakeHuman character + the
|
||||
3D model and map improvements, 3) ship it all together in the **0.10.0 minor**. The 0.9.7/0.9.8
|
||||
character patches are published; `TODO.md` is the priority source. Test counts as of 2026-11-04:
|
||||
Godot 277/277, Python 162/162, `check_docs` OK.
|
||||
Godot 277/277, Python 167/167, `check_docs` OK.
|
||||
|
||||
<!-- b64: RWFzdGVyIGVnZzogYSByYXJlIHJhZGlvIHRyYW5zbWlzc2lvbiAiwr9Ew7NuZGUgZXN0w6EgbGEgbW90YT8iIGZpcmVzIHZpYSBHYW1lTWFuYWdlci5fbWF5YmVfcmFkaW9fZWFzdGVyX2VnZyAoMi41JSBjaGFuY2UgYWZ0ZXIgYSBzdWNjZXNzZnVsIHJhZGlvIGNhbGwpLiBTdHJpbmcga2V5IHJhZGlvLmVhc3Rlcl9lZ2cgaW4gY29udGVudC9zdHJpbmdzLmVuLmpzb24uIERvIG5vdCByZW1vdmUu -->
|
||||
@@ -160,6 +160,7 @@ Full setup, every run/test/smoke command, and the controls table are in
|
||||
- [`TODO.md`](TODO.md) — Priority-ordered tasks: character and local-map development, release milestones, and future work.
|
||||
- [`docs/README.md`](docs/README.md) — Documentation index, current priorities and visual previews.
|
||||
- [`docs/npc-model-sourcing.md`](docs/npc-model-sourcing.md) — Character-source evaluation, licence rules and the character-development plan.
|
||||
- [`docs/makehuman-head-review.md`](docs/makehuman-head-review.md) — Local MakeHuman head prototype, provenance and review verdict.
|
||||
- [`AGENTS.md`](AGENTS.md) — Development, commit, build and collaboration workflow.
|
||||
- [`DONE.md`](DONE.md) — Completed task sections.
|
||||
- [`MEMORY.md`](MEMORY.md) — Persistent session notes.
|
||||
|
||||
@@ -26,10 +26,12 @@ five tint slots, 16,000/16,000 character cap, optional extra bones beyond the re
|
||||
**Current state:** `assets/models/npc/` contains the tracked frozen `npc_base.blend` and its
|
||||
gitignored `.blend1`; the 12 production characters are assembled procedurally by
|
||||
`tools/blender_assets.py` from `_shape_head_mesh`, `_face`, `_hair` and `_hands_and_shoes`.
|
||||
There is no per-part source asset library yet. The MakeHuman body prototype and its temporary
|
||||
head/hand swap live under `tmp/npc-research/` and are not the shipped path. This overhaul supplies
|
||||
the reusable head/face and hand parts that both the procedural characters and the MakeHuman body
|
||||
can consume.
|
||||
The MakeHuman head is now a second head source (`build_character(head="mh")`,
|
||||
`build --head mh`, `npc_parts.py assemble --head mh`) and all 12 variants assemble inside the
|
||||
16,000-triangle cap with it. The procedural body, hands and clothing remain unchanged. The
|
||||
MakeHuman body prototype and its temporary head/hand swap live under `tmp/npc-research/` and are
|
||||
not the shipped path. This overhaul supplies the reusable head/face and hand parts that both the
|
||||
procedural characters and the MakeHuman body can consume.
|
||||
|
||||
This workstream supersedes the face/head and hands items in §C until the prototypes are approved;
|
||||
the rest of §C remains the broad review checklist.
|
||||
@@ -80,8 +82,6 @@ the rest of §C remains the broad review checklist.
|
||||
|
||||
### 3. Head and face prototype
|
||||
|
||||
### 3. Head and face prototype
|
||||
|
||||
- [x] Build one `npc_casual` head/face prototype as a connected head shell plus intentional feature
|
||||
geometry — not a sphere with floating facial balls. Use loop-based eye sockets/lids, a
|
||||
readable nose bridge/tip/ala, a mouth with upper/lower lip volumes, ears with concha depth,
|
||||
@@ -95,6 +95,19 @@ the rest of §C remains the broad review checklist.
|
||||
- [x] Compare two or three subdivision/loop variants at 1.85 m front/three-quarter/profile and a
|
||||
1.5 m portrait. Pick the variant that reads best before changing all 12 characters; keep the
|
||||
rejected variants local.
|
||||
- [x] Rebuild a standalone MakeHuman-derived head from the researched CC0 base and save it to
|
||||
`assets/models/blender_src/makehuman_head.glb`; record provenance/review in
|
||||
`docs/makehuman-head-review.md` and the preview sheet. Final build: 12,642 tris /
|
||||
7,690 split verts, bounds x −0.094..0.094, y −0.180..0.042, z 1.530..1.803. The owner
|
||||
accepted the **basic human head** on 2026-11-04; the automated vision gate still records
|
||||
the eye-region REJECT as deferred polish (globe depth, lid overlap, iris at three-quarter).
|
||||
The assembled path now adds a skull-conforming hair cap, a hidden-scalp trim and a narrowed
|
||||
neck/shoulder body. Assembled `npc_casual --head mh`: 14,753 tris / 12,822 raw / 12,823
|
||||
seam-split verts / 14 bones / 9 materials, `inspect` clean.
|
||||
- [ ] Build true upper/lower eyelid masses that overlap the globe in front, three-quarter and
|
||||
profile views (not a socket hole), then re-run the vision gate. Deferred: the owner accepted
|
||||
the basic head, so this is polish, not a rollout blocker.
|
||||
|
||||
Compared `head_proto` at seg 24 (670 verts), 48 (1,558 verts) and 64 (2,470 verts) at 1.85 m
|
||||
and 1.5 m, front/three-quarter/profile. Vision-judged: **s48 wins** — s24 is too faceted
|
||||
(nose/lips melt, jagged silhouette), s64 is visually identical to s48 at both distances
|
||||
@@ -108,12 +121,11 @@ the rest of §C remains the broad review checklist.
|
||||
imported GLB came out black and is superseded by the proper `.blend`-source bake). Flag: the
|
||||
prototype ears reach x ±0.162 vs the 0.1575 head-radius reference, so headgear may clip and
|
||||
must be re-measured at rollout.
|
||||
- [ ] Get owner sign-off on the one-variant prototype, then roll the approved head/face into the
|
||||
other 11 variants one asset family at a time, with a before/after sheet per batch.
|
||||
**Blocked on owner sign-off.** Sign-off material is ready: the s48 prototype vs the current
|
||||
`_face` at 1.85 m front/three-quarter/profile
|
||||
(`docs/blender_previews/sheet_head_proto_before_after.jpg`); the s24/s48/s64 comparison table
|
||||
is in `docs/3d-model-review.md`. Rollout waits for the owner's go/no-go.
|
||||
- [-] Owner review of the assembled one-variant prototype (`npc_casual --head mh`) before the
|
||||
12 shipped GLBs change. The basic human head is accepted (2026-11-04); the review now covers
|
||||
the skull-conforming hair cap and the narrowed neck/shoulder body silhouette. Local renders:
|
||||
`tmp/npc-parts/prototype_mh/renders/` (portrait front/three-quarter/profile + full body).
|
||||
Rollout starts only after this review.
|
||||
|
||||
### 4. Hands and grip prototype
|
||||
|
||||
@@ -142,17 +154,105 @@ the rest of §C remains the broad review checklist.
|
||||
|
||||
### 5. Assembly and rollout
|
||||
|
||||
**MakeHuman 12-variant rollout.** - [x] Make the head source selectable from the main builder:
|
||||
`blender_assets.py build --head mh --only npc_casual,...` and
|
||||
`npc_parts.py assemble --variant <name> --head mh --out <stem>`. The default stays `face`
|
||||
until the hair/body review passes.
|
||||
- [x] Verify every variant assembles with the MakeHuman head inside the 16,000/16,000 cap
|
||||
(pre-export triangle counts, `inspect` clean; `npc_casual` seam-split verts 12,823):
|
||||
`npc_casual 14,753`, `npc_jacket 15,153`, `npc_coat 15,817`, `npc_vest 15,579`,
|
||||
`npc_dress 15,215`, `npc_hoodie 15,837`, `npc_firefighter 15,987`, `npc_guard 15,729`,
|
||||
`npc_crowd_a 14,947`, `npc_crowd_b 14,693`, `npc_crowd_c 15,433`, `npc_crowd_d 15,833`.
|
||||
- [x] Recover head budget without touching the accepted face: remove head faces fully covered by
|
||||
the hair cap, keep boundary faces as a safety band, and trim the hidden neck plug to 12
|
||||
segments. This saves ~950 tris on every variant.
|
||||
- [ ] Owner approves the hair cap and full-body silhouette on the local `npc_casual` prototype.
|
||||
**Status (2026-11-04):** the current body candidate is the MPFB full-body prototype below,
|
||||
not the narrowed procedural body; the 12-variant rebuild is blocked on that review.
|
||||
- [ ] Rebuild the 12 committed character GLBs with `--head mh`, in asset-family batches, then
|
||||
regenerate their AO maps and manifest entries. Never run a no-op full-library rebuild.
|
||||
- [ ] Run `blender_assets.py audit`, `fit`, `inspect` and `texels`, plus
|
||||
`python3 tools/npc_proportions.py report`; record the 12-variant proportion table.
|
||||
- [ ] Re-run the Godot NPC tests and capture the 12 × 6 baseline
|
||||
(`src/test/capture_npc_baseline.gd` → `python3 tools/npc_baseline.py sheet`); inspect the
|
||||
sheet for outliers before committing.
|
||||
- [ ] Update `docs/makehuman-head-review.md`, `docs/3d-model-review.md`,
|
||||
`docs/blender_previews/README.md`, `docs/npc-model-sourcing.md`, `CREDITS.md` and
|
||||
`docs/development-status.md` with the accepted head/hair/body state and final counts.
|
||||
|
||||
- [ ] Integrate the approved parts into `build_character` with deterministic joins, weights, UVs and
|
||||
AO. The final output remains one `npc_base` skinned mesh under the existing 13-core armature;
|
||||
optional extra bones are allowed but the core rest positions stay fixed;
|
||||
no second rig, no material-slot rename.
|
||||
- [ ] Keep the MakeHuman body prototype consumable with the same approved head/hands; do not let it
|
||||
grow a second face/hand implementation while the part pipeline is being proven.
|
||||
|
||||
- [ ] Rebuild only the changed character GLBs, AO maps and manifest entries; never run a no-op full
|
||||
library rebuild. Commit one asset family per change.
|
||||
- [ ] Re-run `blender_assets.py audit`/`fit`/`inspect`, `npc_proportions.py`, the 24-NPC budget,
|
||||
Godot and Python tests, the 48-frame baseline and the 96-frame final-context review.
|
||||
|
||||
### Full-body MPFB prototype
|
||||
|
||||
Goal: assemble the accepted MakeHuman head onto a CC0 MPFB body with project hands, shoes and
|
||||
14-bone contract rig, inside the 16,000/16,000 character budget. Local development only until the
|
||||
owner reviews the silhouette and a production integration decision is recorded.
|
||||
|
||||
- [x] Build the dev pipeline at `tmp/npc-research/build_mpfb_npc.py`: create a macro-driven MPFB
|
||||
human, bake targets, add the `game_engine` rig, capture arm points/weights, remove the rig,
|
||||
apply the helper MASK, scale to 1.81 m, retarget the A-pose arms to the contract wrist
|
||||
(`x=±0.27`, `z=0.78`), cut the neck at 1.505, decimate, bisect the clothing boundaries, paint
|
||||
the five slots, add a short neck flare, project hands, rounded shoe shells, cuffs, the
|
||||
accepted head and the 14-bone armature. Current output: **15,317 tris / 13,690 glTF verts /
|
||||
13,702 seam-split verts / 1.810 m / 14 bones / 10 materials**; `inspect` clean, feet grounded.
|
||||
The torso is inflated radially by 12% through z 0.72–1.40 (tapering out at the collar) so the
|
||||
shirt reads as fabric rather than body paint.
|
||||
- [x] Replace the spiky decimated MPFB hands with the project palm/finger primitives, straighten the
|
||||
splayed MPFB feet so the shoes point forward, and use rounded shoe shells + cuffs instead of
|
||||
flat blocks. Use flat slot colours in the prototype because the one-shot MPFB UV unwrap does
|
||||
not match the per-slot NPC atlases.
|
||||
- [x] Fix the neck junction without touching the accepted face: cut the decimated neck column
|
||||
radially (all-vertex test inside r=0.085) so the shirt shoulder silhouette stays intact,
|
||||
then bridge the opening with a smooth four-ring lathe neck (r 0.100 → 0.056, 20 segments).
|
||||
The lower rings are painted cloth so the opening reads as a crew neck rather than bare
|
||||
chest. No torus, disk shelf, neck brace or sawtooth collar.
|
||||
- [x] Reject destructive vertex smoothing at the neck: a `smooth_vert` pass moved the open body
|
||||
boundary away from the neck cone, opening a dark hole. The lathe gives smooth shading
|
||||
without moving the collar boundary.
|
||||
- [x] Shrink the shoes and seat the ankles: shoe shell 0.12 × 0.30 × 0.11 m, an inset 0.111 ×
|
||||
0.286 m black sole, a tongue and shoe centre x=0.17, with a 0.08 m pant cuff (r 0.055/0.061)
|
||||
bridging leg and shoe. The sleeve cuffs now use the `cloth` slot and the `accessory` slot is
|
||||
carried by the sole, so the cuffs read as sleeve ends instead of metal bands.
|
||||
- [x] Replace the bulbous toe/heel spheres with a single bevelled shoe shell (bevel 0.032, 2
|
||||
segments) plus tongue and inset sole, and add one light smoothing pass to the closed
|
||||
thigh/hip region. Protruding flat sole plates read as skates or bricks; laces were dropped
|
||||
for triangle budget after they proved too small to read.
|
||||
- [x] Reject the untucked shirt-hem band: both the 0.09 m and 0.12 m cloth cylinders read as a
|
||||
hanging white flap at the crotch. The V hem needs a proper clothing shell, not a band.
|
||||
- [x] Fix the neck material rule: the `radius < 0.08` skin test had no Z floor, so it painted a
|
||||
skin patch at the crotch. It is now guarded to `z > 1.35`; the crotch stays pants.
|
||||
- [x] Reject three cheap hem fixes: lowering the shirt to z 0.80 turns it into a unitard; tilting
|
||||
the hem up at the front reads as high-cut swimwear; a 13% hem flare reads as a peplum.
|
||||
The remaining V is the dark crotch hollow under a body-painted shirt, not a slot bug.
|
||||
- [ ] Owner reviews the updated MPFB body renders (`tmp/npc-parts/mpfb_proto/renders84/`).
|
||||
Remaining known defects: the crotch hollow still reads as a V under the shirt hem, the
|
||||
shirt is only partly loosened by the 12% torso inflation, and the 2k-tri body budget leaves
|
||||
some angular thigh facets. A separate loose shirt shell is the proper fix for the first
|
||||
two; these are polish, not blockers for the body direction.
|
||||
- [ ] Build a proper loose shirt shell instead of body-painted cloth. The quick open, flared
|
||||
solidified tube (0.150→0.196 m, z 1.05→0.80, 24 segments, ~288 tris) read as a hanging
|
||||
apron/tabard and was rejected. The shell needs a shaped torso, armholes and a curved hem,
|
||||
not a tube. It must fit in the 683 spare triangles or come with a head/body rebalance.
|
||||
- [ ] Rebalance the triangle budget before rollout: 683 spare is below the ~1,200 the firefighter
|
||||
and hoodie variants need. Protect the head budget first; do not lower the accepted face.
|
||||
- [ ] Fix the low-budget seams before any rollout: weld the torso/hip/arm components after the
|
||||
bisects, protect the hip/knee loops during decimation (or rebalance head/body triangles).
|
||||
- [ ] Record the production integration decision: either bake the approved MPFB body to a committed
|
||||
source GLB (CI and fresh checkouts stay MPFB-free) or keep the procedural body as the shipped
|
||||
path. Do not add the MPFB extension as a build/CI dependency.
|
||||
- [ ] If approved, roll the MPFB body into the 12 variants and regenerate AO maps, manifest entries,
|
||||
`inspect`/`fit`/`audit`/`texels`, the proportion report and the 12 × 6 baseline.
|
||||
|
||||
### 6. QA, docs and exit
|
||||
|
||||
- [ ] Capture front/three-quarter/profile head comparisons and open/grip hand comparisons; add
|
||||
|
||||
Binary file not shown.
@@ -257,6 +257,16 @@ at the MakeHuman landmarks. `build_character(name, head="mh")` assembles it; the
|
||||
`inspect` (14 bones, skinned, no issues). The judge reads the face ~3.5/5; remaining polish is
|
||||
welding the added eye/brow pieces into the head shell and a skull-conforming hair cap.
|
||||
|
||||
**Standalone head re-review (2026-11-04):** the separate
|
||||
`assets/models/blender_src/makehuman_head.glb` prototype (12,641 tris / 7,690 split verts,
|
||||
with a skin eyelid mask, recessed globes, flat iris/pupil discs and surface-anchored brows)
|
||||
was **accepted by the owner** as a basic human head. The automated vision gate still returns
|
||||
REJECT on the eye/lid region (globe depth, lid overlap, iris at three-quarter) as deferred
|
||||
polish. The assembled `npc_casual --head mh` now adds a skull-conforming hair cap, a hidden
|
||||
scalp trim and a narrowed neck/shoulder body: 14,753 tris / 12,823 seam-split verts /
|
||||
14 bones / 9 materials, `inspect` clean. All 12 variants count 14,693–15,987 tris. See
|
||||
[`makehuman-head-review.md`](makehuman-head-review.md).
|
||||
|
||||
### Head subdivision comparison (2026-11-04)
|
||||
|
||||
Three `head_proto` variants at UV-sphere segments 24/48/64 were rendered at 1.85 m and 1.5 m in
|
||||
@@ -485,6 +495,9 @@ and the full carded-cluster foliage pass is the one remaining owner-gated P1. No
|
||||
|
||||
## Next actions
|
||||
|
||||
- Review the assembled [`makehuman-head-review.md`](makehuman-head-review.md) prototype (hair cap and full-body silhouette) before rebuilding the 12 shipped character GLBs.
|
||||
|
||||
|
||||
Tracked in [`TODO.md`](../TODO.md) `3D model re-evaluation`: the remaining `A. Baseline…` item is
|
||||
this owner review; `B`/`C`/`D` carry the per-family work, and `E` adds the pose/weight,
|
||||
collider-coverage and scorecard-coverage gates this document found missing.
|
||||
@@ -13,6 +13,7 @@ This folder holds the project documentation, visual references and generated QA
|
||||
- [`../PLAN.md`](../PLAN.md) — product, design intent and development priorities
|
||||
- [`../TODO.md`](../TODO.md) — priority-ordered active tasks and roadmap
|
||||
- [`npc-model-sourcing.md`](npc-model-sourcing.md) — character-source evaluation, licence rules and the character-development plan
|
||||
- [`makehuman-head-review.md`](makehuman-head-review.md) — local MakeHuman head prototype, provenance and review verdict
|
||||
- [`architecture.md`](architecture.md) — arc42 system architecture
|
||||
- [`getting-started.md`](getting-started.md) — setup, run/test commands and controls
|
||||
- [`asset-tooling.md`](asset-tooling.md) — image/model pipelines and screenshot gates
|
||||
|
||||
@@ -140,11 +140,13 @@ MakeHuman sources against the character contract and records the 2026-11-03 dire
|
||||
|
||||
```bash
|
||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build
|
||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- build --head mh
|
||||
flatpak run org.blender.Blender --background --python "$PWD/tools/blender_assets.py" -- render
|
||||
python3 tools/blender_assets.py sheet # docs/blender_previews/sheet_*.jpg
|
||||
python3 tools/blender_assets.py audit # no Blender needed
|
||||
python3 tools/blender_assets.py fit # gear-vs-body fit report
|
||||
python3 tools/blender_assets.py texels # per-slot texel density (px/m) of the characters
|
||||
# `build --head mh` selects the MakeHuman head; the default stays `face` until rollout.
|
||||
godot --headless --path . --import
|
||||
```
|
||||
|
||||
|
||||
@@ -9,6 +9,7 @@ capture harnesses.
|
||||
| --- | --- |
|
||||
| [](sheet_npc.jpg) <br> `sheet_npc.jpg` | Eight skinned character variants (casual, jacket, coat, hi-vis vest, dress, hoodie, firefighter, guard) with their gear attach points. |
|
||||
| [](sheet_npc_baseline.jpg) <br> `sheet_npc_baseline.jpg` | Neutral studio baseline: 8 characters x front, three-quarter, profile, back, full body and crowd views. |
|
||||
| [](sheet_makehuman_head.jpg) <br> `sheet_makehuman_head.jpg` | MakeHuman head: front, three-quarter and profile review views. The owner accepted the basic head; the automated vision gate still rejects the eye/lid region as deferred polish. See [`../makehuman-head-review.md`](../makehuman-head-review.md). |
|
||||
| [](sheet_npc_baseline_compare-body.jpg) <br> `sheet_npc_baseline_compare-body.jpg` | Neck/shoulder transition: fuller neck base and a lower shoulder root. |
|
||||
| [](sheet_npc_baseline_compare-face-detail.jpg) <br> `sheet_npc_baseline_compare-face-detail.jpg` | Face detail: nose ala wings, a single rounded mouth and a darker ear concha. |
|
||||
| [](sheet_npc_baseline_compare-face.jpg) <br> `sheet_npc_baseline_compare-face.jpg` | Face-depth pass: recessed sclera, upper lid and sculpted chin/cheek planes. |
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -0,0 +1,139 @@
|
||||
# MakeHuman head prototype
|
||||
|
||||
Status: **owner-accepted head, assembled prototype, not yet shipped.**
|
||||
The owner accepted the basic human head on 2026-11-04. The automated vision gate
|
||||
still records REJECT on the eye/lid region as deferred polish. The asset is a
|
||||
standalone head/neck extracted from the CC0 MakeHuman base in
|
||||
`tmp/npc-research/makehuman/base.obj`; the assembled path now adds hair, a hidden
|
||||
scalp trim and the procedural body. The 12 shipped GLBs have not been rebuilt
|
||||
with it yet.
|
||||
|
||||
## Asset
|
||||
|
||||
- GLB: [`assets/models/blender_src/makehuman_head.glb`](../assets/models/blender_src/makehuman_head.glb)
|
||||
- Review sheet: [`blender_previews/sheet_makehuman_head.jpg`](blender_previews/sheet_makehuman_head.jpg)
|
||||
- Source workflow (development-only): `tmp/npc-research/build_makehuman_head.py`
|
||||
— extracts the MakeHuman head, cuts it at the neck, keeps the head/eye/mouth
|
||||
components, cuts an almond aperture through a skin eyelid mask, adds recessed
|
||||
eyeballs with flat iris/pupil discs, anchors brows to the face surface, renders
|
||||
front/three-quarter/profile and exports the standalone GLB.
|
||||
- Measurements (final standalone build, `inspect`): **12,641 triangles / 7,690
|
||||
split vertices**; bounds approximately `x ±0.094 m`, `y -0.180…0.042 m`,
|
||||
`z 1.530…1.803 m` in project Z-up space.
|
||||
- Assembled `npc_casual --head mh`: **14,753 triangles / 12,823 seam-split
|
||||
vertices / 14 bones / 9 materials**, `inspect` clean. All 12 variants count
|
||||
14,693–15,987 triangles with the same head.
|
||||
- Materials used: `skin`, `eye_white`, `iris`, `pupil`, `hair`. No textures are
|
||||
baked into this prototype.
|
||||
|
||||

|
||||
|
||||
## What was done
|
||||
|
||||
1. Imported the CC0 MakeHuman base OBJ and cleared the importer axis rotation.
|
||||
2. Applied the project scale/axis transform: 1.82 m body, Z-up, metres,
|
||||
front −Y.
|
||||
3. Cut the head at the neck (`z ≈ 1.53`) rather than keeping the cut shoulders;
|
||||
capped the neck opening.
|
||||
4. Built an eyelid mask by subtracting an almond aperture from a skin sphere
|
||||
centred on each eye (`solver = "EXACT"`), then recessed the globes so the
|
||||
cornea sits behind the lid plane.
|
||||
5. Added flat iris/pupil discs on the globe axis; disabled backface culling so
|
||||
the discs stay visible at three-quarter angles.
|
||||
6. Anchored brow strips to the skin with object-space ray casts and added a
|
||||
shallow supraorbital ridge so they do not float, then reduced the crown
|
||||
height to improve the profile silhouette.
|
||||
7. Fixed a material-remap bug introduced by joining the extra eye objects: the
|
||||
joined head faces were pointing at the `eye_white` slot, making the whole
|
||||
head render as white clay. The largest connected component is now forced
|
||||
back onto `skin` after the join.
|
||||
8. Rendered front/three-quarter/profile views in a neutral review studio and
|
||||
exported the standalone GLB.
|
||||
|
||||
## Provenance
|
||||
|
||||
- Base mesh: MakeHuman / MPFB base, CC0, from the local research set
|
||||
`tmp/npc-research/makehuman/`; see [`CREDITS.md`](../CREDITS.md).
|
||||
- The head is a **derived prototype**, not a shipped NPC. It does not enter
|
||||
`assets/models/blender/npc/`, `npc_base`, the runtime `NpcAvatar` path, CI, or
|
||||
a release package until the NPC overhaul plan accepts it.
|
||||
- Source photographs/proportions are not used as textures here; this is a
|
||||
geometry-only head built from the researched model data.
|
||||
|
||||
## Review criteria
|
||||
|
||||
Accept only if:
|
||||
|
||||
- the head reads as human at front/three-quarter/profile and the neck is a
|
||||
clean head/bust silhouette, not a shoulder cut;
|
||||
- eyes, brow, nose, mouth and ears are readable without spikes, holes or
|
||||
floating shells;
|
||||
- the asset is inside the character budget and has no script/contract violation;
|
||||
- the prototype remains clearly local and un-integrated until the part pipeline
|
||||
is approved.
|
||||
|
||||
## Judge result
|
||||
|
||||
**Owner verdict: ACCEPT (basic human head, 2026-11-04).**
|
||||
**Automated vision gate: REJECT** (vision judge `z-ai/glm-5.3-flash`, final
|
||||
standalone build render set above). The eye/lid findings below remain deferred
|
||||
polish; they are no longer a rollout blocker because the owner accepted the head.
|
||||
|
||||
Blocking findings:
|
||||
|
||||
- **P0 — globe depth is inconsistent across views.** The front/three-quarter
|
||||
reads still show a bead-like iris with a pale sclera halo, while the profile
|
||||
reduces the eye to a brown sliver. The judge reads this as a faked recess
|
||||
rather than a socketed globe under a lid.
|
||||
- **P0 — no convincing lid overlap.** The iris remains nearly circular in front
|
||||
and the upper lid does not visibly clip the globe; the skin lid mask reads as
|
||||
a crater around a bead.
|
||||
- **P0 — iris orientation reads decal-locked** at three-quarter, so the gaze
|
||||
appears to drift off the head axis.
|
||||
- **P0 — the aperture/lid mask is too circular** and lacks medial/lateral
|
||||
canthi, so the eye still reads as a doll eye at portrait distance.
|
||||
|
||||
Should-fix findings:
|
||||
|
||||
- the brow tubes are better anchored but the medial ends still detach in
|
||||
three-quarter/profile;
|
||||
- nose lacks a defined tip/wing and nostrils;
|
||||
- mouth is a faint geometric seam with no lip volume;
|
||||
- crown/back-of-skull is still faceted and egg-like in profile;
|
||||
- the catchlight is baked geometry and is not consistent between eyes.
|
||||
|
||||
What passed: the skin/eye material remap is correct, the eye openings are
|
||||
closed, the standalone GLB reports **12,641 triangles / 7,690 split vertices**,
|
||||
and an assembled `npc_casual` with this head passes `inspect` at **14,753 /
|
||||
16,000 triangles, 12,823 seam-split vertices, 14 bones, no issues**.
|
||||
|
||||
Known limits: bald mannequin-like presentation, stylized geometry-only
|
||||
iris/pupil, no rig, no UV texture pass, and the neck cap is a review cut rather
|
||||
than a body-welded seam.
|
||||
|
||||
## Assembled prototype (2026-11-04)
|
||||
|
||||
- `_makehuman_head` imports the GLB and adds a **skull-conforming hair cap**
|
||||
derived from the closed head shell (scaled shell + boolean front-sphere
|
||||
cut + decimate). Hair skips the old `hair_normal.png` because the skull UVs
|
||||
produced large dark tangent patches.
|
||||
- Head faces fully covered by the cap are deleted to recover ~950 triangles;
|
||||
boundary faces stay as a safety band, so the accepted face geometry is
|
||||
untouched. The hidden neck plug uses 12 segments.
|
||||
- The procedural body was narrowed at the neck and shoulders (chest 0.158,
|
||||
neck base 0.060, neck top 0.054, shoulder root z 1.345) and its skin decimate
|
||||
dropped to 0.21 so all 12 variants fit the 16,000 cap.
|
||||
- Local review renders: `tmp/npc-parts/prototype_mh/renders/` (portrait
|
||||
front/three-quarter/profile and full-body front/three-quarter).
|
||||
|
||||
## Deferred polish
|
||||
|
||||
- Replace the radial lid mask with real upper/lower lid masses that fold over
|
||||
the globe, with pointed canthi, and pass the profile view.
|
||||
- Move the iris onto a curved cornea cap so its orientation follows the globe at
|
||||
three-quarter angles, and use a lighting-driven catchlight instead of baked
|
||||
white geometry.
|
||||
- Refine the hair cap silhouette (current short cap reads as a helmet) and
|
||||
improve the full-body shoulder/arm shape before the 12-variant rollout.
|
||||
- Add a clean UV/material pass and a contract armature/weights when the head
|
||||
becomes part of the shipped library characters.
|
||||
@@ -46,9 +46,35 @@ project head/hands swap reduced this to **6,407 / 16,000 tris and**
|
||||
**6,651 / 16,000 verts**, but manual shoulder/neck retopology and part cleanup
|
||||
remain before it can be considered shippable.
|
||||
|
||||
The head/face/hand overhaul in [`TODO.md`](../TODO.md) is the prerequisite: the
|
||||
MakeHuman body should reuse the approved local head/hand parts rather than
|
||||
forking a second face implementation.
|
||||
A standalone MakeHuman head is recorded in
|
||||
[`makehuman-head-review.md`](makehuman-head-review.md). The owner accepted the basic
|
||||
human head on 2026-11-04; the assembled path (`build_character(head="mh")`) adds a
|
||||
skull-conforming hair cap and hides the scalp under it. The 12 shipped GLBs still use
|
||||
the procedural head until the hair/body review and rollout in [`TODO.md`](../TODO.md).
|
||||
The MakeHuman body should reuse the approved local head/hand parts rather than forking
|
||||
a second face implementation.
|
||||
|
||||
The current local MPFB full-body prototype (`tmp/npc-research/build_mpfb_npc.py`) assembles the
|
||||
accepted head on an MPFB 2.0.18 body at **15,317 / 16,000 tris**, 1.810 m, 14 bones and 10
|
||||
materials, with project hands, bevelled shoe shells with inset soles and tongues, pant cuffs,
|
||||
cloth sleeve cuffs and flat slot colours. The torso is radially inflated by 12% through z 0.72–1.40
|
||||
to loosen the shirt. It is a review candidate, not a shipped asset. The neck is a radial cut
|
||||
of the decimated column bridged by a four-ring, 20-segment lathe (lower rings painted cloth,
|
||||
reading as a crew neck); the ankles are hidden by 0.11 m shoe shells and 0.08 m pant cuffs, and a
|
||||
light smoothing pass softens the closed thigh/hip region. The neck skin test is guarded to z >
|
||||
1.35 so it cannot paint the crotch. Remaining polish: the crotch hollow still reads as a V under
|
||||
the shirt hem, a partly loosened shirt and angular thigh facets; a separate loose shirt shell is
|
||||
the fix. Lowering the hem to z 0.80, tilting it up at the front and a 13% hem flare were rejected
|
||||
as a unitard, high-cut swimwear and a peplum respectively. A quick open, flared solidified tube
|
||||
(0.150→0.196 m, z 1.05→0.80, 24 segments) read as a hanging apron/tabard; the shell needs a shaped
|
||||
torso, armholes and a curved hem.
|
||||
A torus collar, cloth cylinder collar, skin neck-blend sphere, destructive vertex-smoothing pass
|
||||
and untucked shirt-hem band were tried and removed because they read as a brace, shelf, donut or
|
||||
hanging flap, or tore a hole at the collar. Protruding flat sole plates read as skates or bricks,
|
||||
so the shoe detail is an inset sole and tongue under a rounded shell; laces were dropped for
|
||||
triangle budget. The production integration decision (bake a committed source GLB or keep the
|
||||
procedural body) and the owner review are tracked in `TODO.md`; MPFB itself must not become a
|
||||
CI/build dependency.
|
||||
|
||||
## Provenance rules
|
||||
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
# Head rework — review page (2026-11-04)
|
||||
|
||||
> **Historical page / changed source (2026-11-04).** The numbers below describe the
|
||||
> earlier decimated MakeHuman head (1,548-tri skin). The shared source
|
||||
> `assets/models/blender_src/makehuman_head.glb` now holds the higher-resolution
|
||||
> standalone prototype. The owner accepted the basic head; the automated vision gate
|
||||
> still records REJECT on the eye/lid region as deferred polish. See
|
||||
> [`../../makehuman-head-review.md`](../../makehuman-head-review.md). Re-run the
|
||||
> library head build before relying on the comparison counts below.
|
||||
|
||||
This page is the review checkpoint for the NPC head rework. The goal was to replace the
|
||||
sphere-and-floating-primitive face with a head that has real nose / chin / jaw landmarks at
|
||||
portrait distance.
|
||||
@@ -52,8 +60,9 @@ Left to right: front, three-quarter, profile.
|
||||
4. The head skin is 1.5k tris (smooth but low-poly); a retopology pass could add resolution if a
|
||||
portrait shows faceting.
|
||||
|
||||
## Decision needed
|
||||
## Decision (2026-11-04, owner)
|
||||
|
||||
Approve or reject the MakeHuman-head direction. If approved, the next step is rolling it into the
|
||||
other 11 variants (one asset family at a time) with a before/after sheet per batch, and re-running
|
||||
the 24-NPC budget + AO bake.
|
||||
The MakeHuman-head direction is approved for the basic human head. The remaining review is the
|
||||
assembled prototype's hair cap and full-body silhouette. After that, roll the head into the other
|
||||
11 variants (one asset family at a time) with a before/after sheet per batch, then re-run the
|
||||
24-NPC budget + AO bake.
|
||||
+161
-31
@@ -276,13 +276,17 @@ def material(name, textured_solid=False):
|
||||
check_existing=True)
|
||||
albedo.image.colorspace_settings.name = "sRGB"
|
||||
links.new(albedo.outputs["Color"], bsdf.inputs["Base Color"])
|
||||
normal_tex = nodes.new("ShaderNodeTexImage")
|
||||
normal_tex.image = bpy.data.images.load(
|
||||
str(NPC_TEX_DIR / f"{name}_normal.png"), check_existing=True)
|
||||
normal_tex.image.colorspace_settings.name = "Non-Color"
|
||||
nmap = nodes.new("ShaderNodeNormalMap")
|
||||
links.new(normal_tex.outputs["Color"], nmap.inputs["Color"])
|
||||
links.new(nmap.outputs["Normal"], bsdf.inputs["Normal"])
|
||||
# The hair normal map was authored against the old cap UVs. Reusing it
|
||||
# on the skull-conforming MakeHuman cap produces large dark tangent
|
||||
# patches, so hair keeps its albedo but takes flat shading normals.
|
||||
if name != "hair":
|
||||
normal_tex = nodes.new("ShaderNodeTexImage")
|
||||
normal_tex.image = bpy.data.images.load(
|
||||
str(NPC_TEX_DIR / f"{name}_normal.png"), check_existing=True)
|
||||
normal_tex.image.colorspace_settings.name = "Non-Color"
|
||||
nmap = nodes.new("ShaderNodeNormalMap")
|
||||
links.new(normal_tex.outputs["Color"], nmap.inputs["Color"])
|
||||
links.new(nmap.outputs["Normal"], bsdf.inputs["Normal"])
|
||||
rough = {"skin": 0.6, "cloth": 0.86, "pants": 0.92,
|
||||
"accessory": 0.5, "hair": 0.7}[name]
|
||||
bsdf.inputs["Roughness"].default_value = rough
|
||||
@@ -550,22 +554,21 @@ def _skin_body(spec):
|
||||
"guard": 0.05}.get(layer, 0.0)
|
||||
leg = 1.0 + {"responder": 0.1, "dress": -0.1}.get(layer, 0.0)
|
||||
nodes = [ # (pos, radius_x, radius_y)
|
||||
((0, 0, 0.80), 0.168 * w, 0.116 * d), # 0 pelvis
|
||||
((0, 0, 0.96), 0.146 * w * bulk, 0.103 * d * bulk), # 1 waist
|
||||
((0, 0.0, 1.18), 0.168 * w * bulk, 0.114 * d * bulk), # 2 chest
|
||||
((0, 0.0, 1.355), 0.170 * w * bulk, 0.108 * d * bulk), # 3 upper chest
|
||||
# A slightly fuller neck base and a lower shoulder root give the
|
||||
# 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
|
||||
((0, 0, 0.80), 0.158 * w, 0.112 * d), # 0 pelvis
|
||||
((0, 0, 0.96), 0.142 * w * bulk, 0.101 * d * bulk), # 1 waist
|
||||
((0, 0.0, 1.18), 0.155 * w * bulk, 0.110 * d * bulk), # 2 chest
|
||||
((0, 0.0, 1.335), 0.158 * w * bulk, 0.105 * d * bulk), # 3 upper chest
|
||||
# Narrow neck and a lower, sloped shoulder root. The old wide neck base
|
||||
# and horizontal shoulder shelf made the accepted head read as small.
|
||||
((0, 0, 1.42), 0.060, 0.060), # 4 neck base
|
||||
((0, -0.005, 1.53), 0.054, 0.054), # 5 neck top
|
||||
]
|
||||
edges = [(0, 1), (1, 2), (2, 3), (3, 4), (4, 5)]
|
||||
for s in (-1.0, 1.0):
|
||||
base = len(nodes)
|
||||
nodes += [
|
||||
((0.20 * s * w, 0, 1.37), 0.080 * t * sleeve, 0.074 * t * sleeve), # shoulder
|
||||
((0.27 * s * w, 0, 1.23), 0.058 * t * sleeve, 0.058 * t * sleeve), # upper arm
|
||||
((0.19 * s * w, 0, 1.345), 0.070 * t * sleeve, 0.068 * t * sleeve), # shoulder
|
||||
((0.27 * s * w, 0, 1.22), 0.054 * t * sleeve, 0.055 * 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.01, 0.78), 0.034 * t, 0.030 * t), # wrist
|
||||
]
|
||||
@@ -591,7 +594,7 @@ def _skin_body(spec):
|
||||
sub = obj.modifiers.new("Sub", "SUBSURF")
|
||||
sub.levels = 2
|
||||
dec = obj.modifiers.new("Decimate", "DECIMATE")
|
||||
dec.ratio = 0.26
|
||||
dec.ratio = 0.21
|
||||
dec.use_symmetry = True
|
||||
dec.symmetry_axis = "X"
|
||||
_apply_all(obj)
|
||||
@@ -716,6 +719,102 @@ def _hair_cap(name, mat, front_z, back_z, radius=0.163, center=(0, 0.008, 1.645)
|
||||
return _finish(_link(name, bm), mat, smooth=True, sharp_angle=70)
|
||||
|
||||
|
||||
def _hair_curve(style):
|
||||
"""Hairline endpoints and skull offset for a hair style."""
|
||||
if style == "buzz":
|
||||
return 1.734, 1.608, 1.035
|
||||
return 1.714, 1.588, 1.055
|
||||
|
||||
|
||||
def _trim_hidden_scalp(head, style):
|
||||
"""Delete the scalp that sits under the hair cap to free triangle budget.
|
||||
|
||||
The cap is a scaled copy of the skull, so any head face whose scaled
|
||||
vertices all fall outside the hairline sphere is fully covered. Keeping
|
||||
boundary-touching faces leaves a safety band under the hair rim; the open
|
||||
hole is never visible and the accepted face geometry is untouched.
|
||||
"""
|
||||
front_z, back_z, scale = _hair_curve(style)
|
||||
center = Vector((0.0, 0.0, 1.665))
|
||||
cutter_z = (front_z + back_z) * 0.5 - 0.396
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(head.data)
|
||||
|
||||
def covered(p):
|
||||
q = center + (p - center) * scale
|
||||
return (q.x * q.x + (q.y + 0.41) ** 2
|
||||
+ (q.z - cutter_z) ** 2) > 0.56 ** 2
|
||||
|
||||
doomed = [f for f in bm.faces if all(covered(v.co) for v in f.verts)]
|
||||
if doomed:
|
||||
bmesh.ops.delete(bm, geom=doomed, context="FACES")
|
||||
loose = [v for v in bm.verts if not v.link_faces]
|
||||
if loose:
|
||||
bmesh.ops.delete(bm, geom=loose, context="VERTS")
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||
bm.to_mesh(head.data)
|
||||
bm.free()
|
||||
|
||||
|
||||
def _hair_from_head(source, style, mat_hair):
|
||||
"""Build the hair cap as a solid shell derived from the skull.
|
||||
|
||||
A separate hair ellipsoid cannot follow the broad MakeHuman crown, and an
|
||||
open, face-deleted copy produces a sawtooth hairline. Scaling the closed
|
||||
head shell outwards and boolean-cutting the face with a large front sphere
|
||||
gives a watertight cap whose curved hairline is smooth from every angle.
|
||||
"""
|
||||
front_z, back_z, scale = _hair_curve(style)
|
||||
cap = source.copy()
|
||||
cap.data = source.data.copy()
|
||||
cap.name = "HairCap"
|
||||
bpy.context.scene.collection.objects.link(cap)
|
||||
center = Vector((0.0, 0.0, 1.665))
|
||||
for v in cap.data.vertices:
|
||||
v.co = center + (v.co - center) * scale
|
||||
# The cutter is a big sphere in front of the face: its upper surface rises
|
||||
# toward the brow and falls toward the nape, which is the hairline curve.
|
||||
mid_z = (front_z + back_z) * 0.5
|
||||
cutter_z = mid_z - 0.396
|
||||
bpy.ops.mesh.primitive_uv_sphere_add(segments=32, ring_count=20, radius=0.56,
|
||||
location=(0.0, -0.41, cutter_z))
|
||||
cutter = bpy.context.object
|
||||
bpy.context.view_layer.objects.active = cap
|
||||
mod = cap.modifiers.new("Hairline", "BOOLEAN")
|
||||
mod.operation = "DIFFERENCE"
|
||||
mod.solver = "EXACT"
|
||||
mod.object = cutter
|
||||
bpy.ops.object.modifier_apply(modifier=mod.name)
|
||||
bpy.data.objects.remove(cutter, do_unlink=True)
|
||||
# The boolean result is still head-density. Hair needs silhouette, not
|
||||
# pores, and the character budget has to stay under 16k triangles.
|
||||
dec = cap.modifiers.new("HairReduce", "DECIMATE")
|
||||
dec.ratio = 0.34
|
||||
dec.use_collapse_triangulate = True
|
||||
bpy.ops.object.modifier_apply(modifier=dec.name)
|
||||
bm = bmesh.new()
|
||||
bm.from_mesh(cap.data)
|
||||
bmesh.ops.recalc_face_normals(bm, faces=bm.faces)
|
||||
bm.to_mesh(cap.data)
|
||||
bm.free()
|
||||
cap.data.materials.clear()
|
||||
cap.data.materials.append(mat_hair)
|
||||
cap.data.shade_smooth()
|
||||
cap.data.set_sharp_from_angle(angle=math.radians(55))
|
||||
|
||||
parts = [cap]
|
||||
if style == "long":
|
||||
parts.append(sphere("HairBack", 0.095, (0, 0.060, 1.560), mat_hair,
|
||||
seg=14, rings=9, scale=(0.98, 0.55, 1.55)))
|
||||
for s in (-1.0, 1.0):
|
||||
parts.append(sphere("Lock", 0.048, (0.089 * s, -0.012, 1.585),
|
||||
mat_hair, seg=8, rings=6,
|
||||
scale=(0.55, 0.85, 2.05)))
|
||||
if style == "bun":
|
||||
parts.append(sphere("Bun", 0.060, (0, 0.100, 1.775), mat_hair,
|
||||
seg=12, rings=8, scale=(1.0, 0.9, 0.95)))
|
||||
return parts
|
||||
|
||||
def _hair(style, mat_hair, broad=False):
|
||||
parts = []
|
||||
if style == "buzz":
|
||||
@@ -968,11 +1067,12 @@ def _head_proto(mats, spec, subdiv=48):
|
||||
def _makehuman_head(mats, spec):
|
||||
"""MakeHuman head shape plus the project's eye/mouth/brow pieces.
|
||||
|
||||
The AI-judged head rework (2026-11-04) picked the decimated MakeHuman head
|
||||
over the displacement shell: it keeps the nose/chin/jaw landmarks at 1,573
|
||||
tris. The MakeHuman mesh has no eyeballs, so the project's sclera/iris/lid/
|
||||
brow pieces are added at the MakeHuman landmarks (eyes ≈ ±0.052, −0.160,
|
||||
1.664) for readability. Blender Z-up, face front −Y.
|
||||
The AI-judged head rework (2026-11-04) picked the MakeHuman head over the
|
||||
displacement shell: it keeps the nose/chin/jaw landmarks. Newer part builds
|
||||
carry their own embedded eye/iris/pupil/mouth geometry, so those materials
|
||||
are preserved; the older shell-only asset still gets the project's
|
||||
sclera/iris/lid/brow pieces added at the MakeHuman landmarks. Blender Z-up,
|
||||
face front −Y.
|
||||
"""
|
||||
skin, acc, hair = mats["skin"], mats["accessory"], mats["hair"]
|
||||
white = material("eye_white", textured_solid=True)
|
||||
@@ -991,9 +1091,32 @@ def _makehuman_head(mats, spec):
|
||||
if head is None:
|
||||
raise SystemExit(f"no head mesh in {src}")
|
||||
head.name = "HeadShell"
|
||||
# The decimated MakeHuman "hair" scalp slot reads as shaggy shards; collapse
|
||||
# the head to one skin slot so the join does not collide with the body's
|
||||
# cloth/pants slots (the project `_hair` cap provides hair later).
|
||||
# Newer MakeHuman part builds carry their own eye/mouth/brow materials and
|
||||
# geometry. Use those instead of adding the project's primitive face pieces
|
||||
# a second time; only the old shell-only asset falls through to that path.
|
||||
embedded = {m.name.split(".")[0] for m in head.data.materials if m}
|
||||
if "eye_white" in embedded:
|
||||
# Preserve the embedded iris/pupil/mouth materials; only map the
|
||||
# project contract slots onto the imported head.
|
||||
remap = {"skin": skin, "eye_white": white, "hair": hair}
|
||||
for index, mat in enumerate(list(head.data.materials)):
|
||||
if mat:
|
||||
target = remap.get(mat.name.split(".")[0])
|
||||
if target is not None:
|
||||
head.data.materials[index] = target
|
||||
# Bridge the 1.48-1.53 m neck gap between the MakeHuman cut and the
|
||||
# procedurally built torso. The plug sits inside both shells, so only
|
||||
# the visible band reads as neck and no seam ring reaches the surface.
|
||||
plug = cyl("NeckPlug", 0.062, 0.052, 0.095, (0, 0.002, 1.500), skin,
|
||||
seg=12, scale=(1.10, 1.0, 1.0))
|
||||
hair_style = "buzz" if spec["layer"] == "hood" else spec["hair"]
|
||||
hair_parts = _hair_from_head(head, hair_style, hair)
|
||||
_trim_hidden_scalp(head, hair_style)
|
||||
return [head, plug] + hair_parts
|
||||
|
||||
# The old decimated MakeHuman "hair" scalp slot reads as shaggy shards;
|
||||
# collapse the head to one skin slot so the join does not collide with the
|
||||
# body's cloth/pants slots (the project `_hair` cap provides hair later).
|
||||
head.data.materials.clear()
|
||||
head.data.materials.append(skin)
|
||||
# Clean the ragged neck cut left by the Head-group extraction: bisect below
|
||||
@@ -1229,7 +1352,8 @@ def build_character(name, head="face"):
|
||||
else:
|
||||
head_parts = _face(mats, spec)
|
||||
hair_style = spec["hair"] if spec["layer"] != "hood" else "buzz"
|
||||
head_parts += _hair(hair_style, mats["hair"])
|
||||
if head != "mh":
|
||||
head_parts += _hair(hair_style, mats["hair"])
|
||||
head = join(head_parts, "head")
|
||||
_auto_weights(head, layout, rigid=lambda p: "Head")
|
||||
|
||||
@@ -1795,10 +1919,10 @@ def build_prop(name):
|
||||
# Build + render entry points (inside Blender)
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def _build_one(category, name):
|
||||
def _build_one(category, name, head="face"):
|
||||
reset_scene()
|
||||
if category == "npc":
|
||||
objs = build_character(name)
|
||||
objs = build_character(name, head=head)
|
||||
elif category == "npc_gear":
|
||||
objs = build_gear(name)
|
||||
else:
|
||||
@@ -2748,6 +2872,9 @@ def main(argv=None):
|
||||
for cmd in ("build", "render"):
|
||||
s = sub.add_parser(cmd)
|
||||
s.add_argument("--only", default="")
|
||||
if cmd == "build":
|
||||
s.add_argument("--head", default="face", choices=("face", "proto", "mh"),
|
||||
help="character head source (default: face)")
|
||||
a = sub.add_parser("audit")
|
||||
a.add_argument("--json", action="store_true")
|
||||
fit = sub.add_parser("fit")
|
||||
@@ -2838,7 +2965,10 @@ def main(argv=None):
|
||||
file=sys.stderr)
|
||||
return 2
|
||||
for cat, name in _selected(args.only):
|
||||
(_build_one if args.cmd == "build" else _render_one)(cat, name)
|
||||
if args.cmd == "build":
|
||||
_build_one(cat, name, head=args.head)
|
||||
else:
|
||||
_render_one(cat, name)
|
||||
if args.cmd == "build":
|
||||
results = [audit_asset(c, n) for c, n in catalogue()]
|
||||
write_manifest([r for r in results if r["vertices"]])
|
||||
|
||||
Reference in new issue
Block a user