Files
Papay-Shooter/characters/rig_roles.gd
T
2026-08-09 01:31:44 -04:00

73 lines
2.9 KiB
GDScript

extends Object
class_name RigRoles
## Canonical bone names -> this rig's actual bone indices.
##
## Pulled out of `ShooterPoseModifier._resolve` so the dance layer can use the
## same resolution instead of carrying a second copy of it. Two copies is how a
## rig ends up animating correctly under one modifier and not the other.
##
## Non-negotiable: never look a bone up by name. `tools/rig_map.py` resolves
## every rig to ROLES and writes them to the `<model>.rig.json` sidecar, and this
## reads that. The name fallback below exists only for a model with no sidecar,
## and it must never be the first thing tried — four characters could not hold a
## gun because of exactly one hardcoded spelling.
## The library skeleton's spine, hips first. A rig that kept its own names maps
## onto this through the sidecar's `spine` chain.
const SPINE := ["DEF-hips", "DEF-spine.001", "DEF-spine.002", "DEF-spine.003"]
## Everything either pose layer asks for.
const CANONICAL := [
"DEF-hips", "DEF-spine.001", "DEF-spine.002", "DEF-spine.003",
"DEF-neck", "DEF-head",
"DEF-upper_arm.R", "DEF-forearm.R", "DEF-hand.R",
"DEF-upper_arm.L", "DEF-forearm.L", "DEF-hand.L",
"DEF-thigh.R", "DEF-shin.R", "DEF-thigh.L", "DEF-shin.L",
]
## Godot sanitises ':' to '_' while turning glTF nodes into scene names. The
## sidecar intentionally keeps the source bone spelling so Blender can verify
## it against the exported GLB, therefore every runtime consumer must resolve
## through this bridge instead of assuming those two representations match.
static func find_imported_bone(skel: Skeleton3D, authored_name: String) -> int:
if authored_name == "":
return -1
var bone := skel.find_bone(authored_name)
if bone < 0 and authored_name.contains(":"):
bone = skel.find_bone(authored_name.replace(":", "_"))
return bone
## Resolve `names` against a skeleton, given the sidecar's role table.
##
## The spine needs its own handling because a rig that kept its own skeleton
## names things differently AND has a different number of spine bones — Taila's
## hips are `DEF-spine` and her head is `DEF-spine.006`, and she has no bone with
## "neck" in its name at all. Unresolved, every lean and aim pitch silently did
## nothing.
static func resolve(skel: Skeleton3D, roles: Dictionary,
names: Array = CANONICAL) -> Dictionary:
var alias := {}
if not roles.is_empty():
var neck: String = roles.get("neck", "")
var head: String = roles.get("head", "")
var torso: Array = []
for n in roles.get("spine", []):
if n != neck and n != head:
torso.append(n)
for i in mini(torso.size(), SPINE.size() - 1):
alias[SPINE[i + 1]] = torso[i]
var idx := {}
for n in names:
# Canonical names are the role keys with the DEF- prefix, so the limbs,
# hips, neck and head all map straight through.
var actual: String = alias.get(n, roles.get(String(n).trim_prefix("DEF-"), n))
var b := find_imported_bone(skel, actual)
if b < 0:
b = skel.find_bone(String(n))
idx[n] = b
return idx