Files
animation/tools/make_canonical_rest.py
T

42 lines
1.7 KiB
Python
Raw Normal View History

"""Regenerate hand-poses/canonical_rest.json — the canonical Quaternius finger-bone REST
rotations, taken from the Kevin pack the hand poses were harvested against.
The runtime needs this to apply a pose REST-RELATIVE on rigs whose finger rest differs from
canonical (Mako's *_01 knuckles sit 11.5 deg off, so applying a canonical pose directly
rotates his knuckles away from his own rest and tears the palm/wrist boundary):
delta = canonical_rest^-1 * pose
target = body_rest * delta
usage: make_canonical_rest.py [kevin.glb] [out.json]
"""
import json, struct, sys
from pathlib import Path
REPO = Path(__file__).resolve().parents[1]
GAME = REPO.parent / "ariki-game"
src = Path(sys.argv[1]) if len(sys.argv) > 1 else \
GAME / "assets/quaternius/kevin/kevin_female_combat.glb"
out = Path(sys.argv[2]) if len(sys.argv) > 2 else REPO / "hand-poses/canonical_rest.json"
d = src.read_bytes()
jl = struct.unpack_from("<I", d, 12)[0]
g = json.loads(d[20:20 + jl].decode("utf-8"))
pose_bones = list(json.loads((REPO / "hand-poses/pose_flat.json").read_text())["bones"])
rest = {nd.get("name"): nd.get("rotation", [0, 0, 0, 1]) for nd in g["nodes"]}
missing = [b for b in pose_bones if b not in rest]
if missing:
raise SystemExit(f"canonical source lacks pose bones: {missing}")
payload = {
"_comment": ("Canonical Quaternius finger-bone REST rotations. The runtime applies a "
"pose rest-relative: delta = canonical_rest^-1 * pose, "
"target = body_rest * delta. Regenerate with tools/make_canonical_rest.py."),
"source": src.name,
"bones": {b: [round(v, 8) for v in rest[b]] for b in pose_bones},
}
out.write_text(json.dumps(payload, indent=1))
print(f"wrote {out} with {len(payload['bones'])} bones from {src.name}")