Everything about getting an anime-styled character into the game — import, stylization, the body/garment/hair separation, rigging, retargeting, cloth and hair physics, and how to measure any of it — collected into .claude/skills/character-pipeline/. Organised around the principle the Hoyoverse-class pipelines are built on and that every failure in this project traced back to: a character is not one object. It is a body, a set of garments and hair, authored and rigged separately and moved by different systems. The body is skinned and animated; the garments and hair are bone chains the animation never touches and physics moves. The skill's non-negotiables are the four ways that separation has been destroyed here before — joining meshes, keying cosmetic bones, skinning cloth to a leg, and running the auto-rig repair on authored weights. references/verification.md leads with the trap that invalidated every cloth measurement ever taken in this repo: Godot restores bone poses after the modifier pass, so a tool that reads them afterwards measures the animation and never sees what any modifier did. Also records what is known-unsolved, with numbers: peak cloth clipping in a run/slide/dash, no foot IK, no strafe clips. Co-Authored-By: Claude Opus 5 <[email protected]>
103 lines
4.2 KiB
Markdown
103 lines
4.2 KiB
Markdown
# Rigging and retargeting
|
|
|
|
## Roles, not names
|
|
|
|
`tools/rig_map.py` resolves a skeleton to ROLES — `hips`, `spine[]`, `neck`,
|
|
`head`, and `limb[(role, side)]` for `thigh/shin/foot/toe/shoulder/upper_arm/
|
|
forearm/hand`. Matching is by whole tokens plus anatomy (chain length, position,
|
|
which bone is a child of which), so a Rigify `DEF-thigh.L`, a Mixamo
|
|
`mixamorig:LeftUpLeg` and a bespoke `Bip01_L_Thigh` all land on the same role.
|
|
|
|
This is what removed the need to destroy foreign skeletons. `roles.missing_core()`
|
|
is the gate: if the core roles cannot be found the pipeline stops rather than
|
|
guessing.
|
|
|
|
The resolved roles are written to `<model>.rig.json` and read at runtime by
|
|
`ShooterPoseModifier._resolve`, which aliases its library-flavoured names
|
|
(`DEF-hips`, `DEF-spine.001`…) onto whatever this rig calls them. Taila's hips
|
|
are `DEF-spine`, her head is `DEF-spine.006`, and she has **no bone with "neck"
|
|
in its name at all** — unresolved, every lean, aim pitch and slide head-lift
|
|
silently did nothing.
|
|
|
|
## Rebuilding the hierarchy
|
|
|
|
A Rigify DEF-rig exports its chain roots parented straight to the armature root,
|
|
because Rigify drives them by constraint rather than by hierarchy. Left that way,
|
|
rotating the hips leaves the legs, skirt and hair floating in place.
|
|
|
|
`rebuild_hierarchy` re-attaches orphans: by anatomy where the role is known, and
|
|
by rest geometry (nearest plausible parent) otherwise. **Cloth may only attach to
|
|
the trunk.**
|
|
|
|
## Subdividing cloth panels
|
|
|
|
`subdivide_cloth_panels(arm, meshes, roles, segments=4)`.
|
|
|
|
A skirt panel that is a single bone from the waist is a rigid flap: it can only
|
|
rotate about its own head, and a contact near that head is unreachable at any
|
|
angle. Splitting each panel into a chain is what lets it bend, and it is why the
|
|
ZZZ-convention skirt is a grid rather than a fan.
|
|
|
|
On Taila this turns 21 panel bones into 21 chains of 4. The segment lengths come
|
|
out uneven (49/49/49/141 mm) because the last segment runs on to the hem.
|
|
|
|
Weights are redistributed along the panel as it is split, so the mesh follows the
|
|
new chain.
|
|
|
|
## Twist bones
|
|
|
|
A forearm or thigh twist bone takes half the roll of its parent so the skin does
|
|
not candy-wrap. They are detected (`is_segment_of`) and recorded in the sidecar's
|
|
`twist` list. They are also folded into the limb when measuring collider radii:
|
|
most of a thigh's surface belongs to `DEF-thigh.L.001`, and what is left
|
|
dominated by `DEF-thigh.L` is mostly hip flare, which fitted a 0.154 m radius —
|
|
a 30 cm thigh.
|
|
|
|
## Joint helpers
|
|
|
|
`SkinJointHelper.install` runs for EVERY model however it was rigged. Linear-blend
|
|
skinning collapses any joint by cos(angle/2) no matter how good the weights are;
|
|
measured at the knee, 0.77 without helpers against 0.99 with. They are updated
|
|
LAST, inside the modification pass, so each helper tracks whatever final rotation
|
|
its child bone ended up with.
|
|
|
|
## The retarget maths
|
|
|
|
Bake each clip as a **rest-relative delta**:
|
|
|
|
```
|
|
R_world = src_pose_rot * src_rest_rot⁻¹ what the clip does
|
|
tgt_rot = R_world * tgt_rest_rot done to THIS rig
|
|
```
|
|
|
|
Copying absolute world orientation instead — which is what a constraint bake does
|
|
— forces the library's bone ROLL onto a mesh bound with a different one, and
|
|
twists every limb by a constant offset.
|
|
|
|
Also handled: a facing correction (`facing_correction`) when the library and the
|
|
character face different ways, and a hips-height scale so a short character does
|
|
not float.
|
|
|
|
## Export flags that matter
|
|
|
|
```python
|
|
export_bake_animation=False,
|
|
export_optimize_animation_keep_anim_armature=False,
|
|
```
|
|
|
|
`keep_anim_armature` forces a track onto every bone whether or not the clip
|
|
touches it. Off, the skirt and hair export with **no tracks at all** and belong
|
|
entirely to the spring solver. This one flag is the animation/physics split.
|
|
|
|
## Height normalisation
|
|
|
|
`flatten_and_scale(arm, meshes, TARGET_HEIGHT)` — default 1.75 m. Applied before
|
|
the retarget so the library's stride matches the character's legs.
|
|
|
|
## When a model has no skeleton
|
|
|
|
`tools/autorig.py` will fit one, and the pipeline accepts the quality loss:
|
|
nearest-bone weights, cross-leg bleed, no cloth chains. `weights_authored` comes
|
|
out false, `SkinLegRepair` runs at load to snap the worst of it, and the
|
|
character will have no secondary motion. Prefer finding a rigged source.
|