Stage the Blender character/gear/prop library
Commit the finished generator output from the parallel Blender pass: 8 skinned NPC variants, 18 gear pieces, and 20 props under assets/models/blender/, editable sources (gdignored), manifest, review sheets, and the integration notes in docs/blender-assets.md. - audit: all 46 assets clean (characters 3,788-4,640 tris, under 6000/4000) - tests: Python manifest/audit contract and Godot import/bone/slot/ground checks - docs: README library section, architecture/plan references, TODO Phase 3.5 status, MEMORY integration rules - .gitignore: per-asset preview PNGs, extracted GLB textures, blend1 backups Nothing in the game loads the library yet; integration follows." && git log --oneline -1
This commit is contained in:
1 parent
7efb5024ec
commit
d513e0b82c
109 files changed
+1916
-114
No files matched your search
@@ -0,0 +1,182 @@
|
||||
# Blender asset library
|
||||
|
||||
Status: built and audited (2026-11-01). Nothing in the game loads these assets yet.
|
||||
They are staged for the Phase 3.5 integration in
|
||||
[`plan-graphics-v0.6.md`](plan-graphics-v0.6.md).
|
||||
|
||||
Generator: [`tools/blender_assets.py`](../tools/blender_assets.py). Manifest (the
|
||||
source of truth for integration):
|
||||
[`assets/models/blender/manifest.json`](../assets/models/blender/manifest.json).
|
||||
|
||||
## Art direction
|
||||
|
||||
The references were the AI mockups in `docs/screens/designs/` and the current
|
||||
captures in `docs/screenshots/`.
|
||||
|
||||
- **People** (`targeting_npc`, `joke_shop_queue`, `studio_arrival`,
|
||||
`crowd_panic`, `responder_arrival`): ordinary adults in sweaters, jackets,
|
||||
long coats, hoodies and beanies; a security guard in a dark uniform with a cap
|
||||
and belt; a firefighter in a dark turnout coat with yellow reflective bands,
|
||||
a helmet and an SCBA tank. The current skinned `npc_base` is a set of boxes
|
||||
and cylinders with a flat face, so silhouette was the main gap.
|
||||
- **Props** (`prop_gallery`, `venue_overview`, `late_night_avenue`,
|
||||
`joke_shop_queue`): the gallery mockup has an octagonal tapered bollard, a
|
||||
slatted bench with thin metal legs, an octagonal green litter bin with a slot
|
||||
and recessed panels, a stone planter with a lumpy shrub, a blue news box with
|
||||
a window and a slanted top, and a hydrant with caps and nozzles. The venue
|
||||
shots add ornate street lamps, palm trees, round stone pots with topiary, a
|
||||
chalkboard A-frame sign, a wall lantern, a bulb-lit marquee, glazed double
|
||||
doors, stair handrails and a dark sedan. Today's in-game props are primitive
|
||||
boxes, and the trees are cones.
|
||||
|
||||
## Running it
|
||||
|
||||
```bash
|
||||
# Build every asset (or --only a,b,c). Writes GLBs, .blend sources, manifest.
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/blender_assets.py" -- build
|
||||
# Cycles previews (CPU, ~3 s each) -> docs/blender_previews/<name>.png (gitignored)
|
||||
flatpak run org.blender.Blender --background \
|
||||
--python "$PWD/tools/blender_assets.py" -- render
|
||||
python3 tools/blender_assets.py sheet # -> docs/blender_previews/sheet_<category>.jpg
|
||||
python3 tools/blender_assets.py audit # stdlib only; exit 1 on any issue
|
||||
python3 tools/blender_assets.py list
|
||||
godot --headless --path . --import # after any rebuild
|
||||
```
|
||||
|
||||
The flatpak cannot read `/tmp` and its cwd is `/app/blender`, so the script
|
||||
derives every path from its own location. A full build takes about 5 s; a full
|
||||
render about 2.5 min.
|
||||
|
||||
## Layout
|
||||
|
||||
| Kind | Path | Count |
|
||||
| --- | --- | --- |
|
||||
| Characters (skinned) | `assets/models/blender/npc/<name>.glb` | 8 |
|
||||
| Gear (static) | `assets/models/blender/npc_gear/<name>.glb` | 18 |
|
||||
| Props (static) | `assets/models/blender/props/<name>.glb` | 20 |
|
||||
| Editable sources | `assets/models/blender_src/<name>.blend` (`.gdignore`d) | 46 |
|
||||
| Review sheets | `docs/blender_previews/sheet_{npc,npc_gear,props}.jpg` | 3 |
|
||||
|
||||
## Conventions
|
||||
|
||||
- Blender is Z-up. The glTF exporter converts to Godot Y-up.
|
||||
- **Front = Blender −Y = Godot +Z**, the face direction that
|
||||
`NpcAvatar.yaw_towards()` assumes.
|
||||
- Grounded assets have their origin on the floor (`min y ≈ 0`). The two
|
||||
exceptions are `wall_lantern` (origin on the wall plate, at mount height) and
|
||||
`marquee` (origin at the wall where the canopy meets it, at canopy height).
|
||||
- Units are metres, at the same scale as the current world (1.82 m characters).
|
||||
|
||||
## Characters
|
||||
|
||||
All eight use the `npc_base` contract, so each one can replace
|
||||
`npc_base.glb` without code changes:
|
||||
|
||||
- mesh node `npc_base` under an armature that imports as `Skeleton3D`
|
||||
- bones `Head`, `HipL/R`, `KneeL/R`, `ShoulderL/R`, `ElbowL/R`, `HandL/R`
|
||||
(plus `Hips`, `Spine`, `Neck`) at the **same rest positions as `npc_base`**.
|
||||
`test_character_joints_line_up_with_npc_base` checks this within 1 cm, so the
|
||||
animation proxies in `_build_skinned_body()` stay valid.
|
||||
- material slots `skin`, `cloth`, `pants`, `accessory`, `hair` carry the same
|
||||
neutral `assets/textures/npc/<slot>_{albedo,normal}.png` maps, so
|
||||
`Materials.npc_slot()` tints them the same way it tints `npc_base`.
|
||||
- budget ≤ 6000 triangles and ≤ 4000 vertices (glTF vertex count, after seam
|
||||
splits). The current range is 3,788–4,640 triangles and 3,200–3,960
|
||||
vertices.
|
||||
|
||||
Construction: the torso and limbs are one continuous Skin-modifier mesh,
|
||||
subdivided twice, decimated to 36 % with X symmetry, and cut at the waist, cuffs,
|
||||
sleeves and neck so the material borders are clean edge loops. The head,
|
||||
face (nose, ears, eyes, brows, mouth), hair, hands, shoes and clothing layers are
|
||||
separate parts. Weights come from distance to the bone segments, limited by body
|
||||
region. Parts that should stay rigid (head, hands, shoes) get a single bone.
|
||||
|
||||
| Character | `layer_for()` / look | Suggested roles | Preview gear |
|
||||
| --- | --- | --- | --- |
|
||||
| `npc_casual` | `none`: sweater/tee, short sleeves | tourist, confused visitor, intoxicated, guest | backpack, camera |
|
||||
| `npc_jacket` | `jacket`: collar + placket | authorized staff, protester, guest | lanyard, clipboard |
|
||||
| `npc_coat` | `coat`: knee-length coat, long hair | influencer, trespasser, violent threat | sunglasses, shoulder bag, phone |
|
||||
| `npc_vest` | `vest`: hi-vis vest, reflective bands | delivery | cap, clipboard |
|
||||
| `npc_dress` | `dress`: slim build, bun, tights | performer | shoulder bag |
|
||||
| `npc_hoodie` | `hood` accessory: hood up, beard, pocket | trespasser, mental crisis, violent threat | cup |
|
||||
| `npc_firefighter` | responder: broad build, banded turnout coat, boots | responder | helmet, SCBA tank, flashlight |
|
||||
| `npc_guard` | uniform: belt, buckle, badge, shoulder patches, radio | player guard / venue security | cap, wand |
|
||||
|
||||
**Fixed-colour materials.** Detail materials outside the five slots (`hivis`,
|
||||
`reflective`, `leather`, `steel`, `patch`, `plastic_black`, `rubber`) carry a 4×4
|
||||
colour texture. The current `_apply_outfit_materials()` sets
|
||||
`albedo_color = WHITE` on any slot it does not recognise, and these keep their
|
||||
colour through that. When integrating, tint only the five contract slots.
|
||||
|
||||
**Runtime tint notes.** `_build_skinned_body()` currently paints `cloth`
|
||||
hi-vis yellow for the `vest` layer. On `npc_vest` the vest is already the fixed
|
||||
`hivis` material and `cloth` is the undershirt, so skip that override for this
|
||||
model. For `npc_firefighter` and `npc_guard`, tint `cloth`/`pants` dark (turnout /
|
||||
uniform navy) and not with the role's `TYPE_COLORS`.
|
||||
|
||||
## Gear attach points
|
||||
|
||||
Gear is authored in attach-point space, so the origin of each GLB is its
|
||||
attach point:
|
||||
|
||||
| Attach | Godot parent / position | Gear |
|
||||
| --- | --- | --- |
|
||||
| `head` | `Rig/Head` marker (0, 1.62, 0) | cap, beanie, hood, sunglasses, firefighter_helmet |
|
||||
| `back` | `Rig` at (0, 1.10, 0), spine centre; items sit behind the body (Blender +Y = Godot −Z) | backpack, shoulder_bag, scba_tank |
|
||||
| `chest` | `Rig` at (0, 1.30, 0), sternum | lanyard |
|
||||
| `hand` | Hand proxy (±0.27, 0.78, 0.02); grip 5 cm below the origin | phone, cup, clipboard, umbrella, camera, protest_sign, flashlight, wand, radio |
|
||||
|
||||
The character previews in `sheet_npc.jpg` are built by importing the gear at
|
||||
exactly these points, which gives a quick visual check of the convention.
|
||||
|
||||
## Props
|
||||
|
||||
| Prop | Replaces / role | Notes |
|
||||
| --- | --- | --- |
|
||||
| `bollard`, `bench`, `trash_bin`, `planter_box`, `newspaper_box`, `fire_hydrant` | `assets/models/generated/<name>.glb` | same footprint and height as the generated set, so colliders and `PROP_PLACEMENTS` still fit |
|
||||
| `street_lamp` | the box lamps in `_build_street_furniture()` | 5 m; the arm points to −X with the bulb at (−0.85, 4.8, 0). Rotate 180° for the other side of the street. Emissive `bulb` |
|
||||
| `street_tree` | the cone trees | 3.6 m, with a tree grate |
|
||||
| `traffic_cone` | the `cone` prop accessory | 0.69 m |
|
||||
| `palm_tree` | new (boulevard) | 7 m, 14 fronds |
|
||||
| `round_planter` | new (mockup pots) | stone pot with topiary |
|
||||
| `a_frame_sign` | new (comedy club queue) | chalkboard |
|
||||
| `wall_lantern` | new (club door) | wall-mounted; emissive `bulb` |
|
||||
| `crowd_barrier`, `stanchion` | new (queue / crowd control) | 2.2 m bike-rack barricade; chrome post with velvet rope |
|
||||
| `handrail` | new (steps) | 2 m run that climbs 0.8 m along +X |
|
||||
| `security_podium` | new (door check-in) | tablet screen is mildly emissive |
|
||||
| `double_door` | the procedural entrance door | 2.0 × 2.7 m; emissive `lock_led` on the card reader |
|
||||
| `marquee` | the procedural marquee | 6 m canopy and sign box; emissive `bulb` + `sign_face`. Overlay the text with the existing `Label3D` |
|
||||
| `sedan` | new (studio arrival) | 4.6 m; emissive `headlight`, red tail lamps |
|
||||
|
||||
Props use plain PBR factors (base colour, roughness, metallic, emission) and
|
||||
have no textures. They are not yet AO-baked; that is still a Phase 4 task.
|
||||
Emissive materials are always on in the GLB. The integration should tie them to
|
||||
`WorldBuilder.set_night_mode()`.
|
||||
|
||||
## Checks
|
||||
|
||||
- `python3 tools/blender_assets.py audit`: container, `POSITION`/`NORMAL`/
|
||||
`TEXCOORD_0`, UVs in [0, 1], no zero-area triangles (Godot warns about them
|
||||
during LOD generation), budgets (characters 6000/4000, gear 1500, props 4000
|
||||
triangles), grounding, and an editable `.blend`. For characters it also
|
||||
checks skinning, bones, slots, the `npc_base` mesh node and the height.
|
||||
- `tools/test_blender_assets.py`: catalogue and contract consistency with
|
||||
`npc_pipeline.py`, plus a clean audit and full manifest coverage when the
|
||||
library is built.
|
||||
- `src/test/tests/test_blender_assets.gd`: every asset imports as a scene
|
||||
with geometry, characters keep their bones and slots, joints match
|
||||
`npc_base`, the bones accept `NpcAvatar`'s pose composition, and props are
|
||||
grounded.
|
||||
|
||||
## Known limitations
|
||||
|
||||
- The characters have no baked AO or ORM texture of their own. They reuse the
|
||||
tiling slot maps, so the runtime `SurfaceMaterials` recipes still supply
|
||||
roughness.
|
||||
- There is a single body topology per build (average, slim, broad) and no
|
||||
shape keys. Height variety still comes from `outfit_for()["height_scale"]`.
|
||||
- Skirts and coats follow the hip and knee bones, so a full stride can clip
|
||||
the legs through the hem.
|
||||
- Preview colours (`PREVIEW_TINTS`) only stand in for `outfit_for()`. The GLBs
|
||||
stay neutral.
|
||||
Reference in new issue
Block a user