Ship the Quaternius npc_casual pilot base: rig-agnostic pose sync, five tint slots, unwrapped UVs and wrist-strap camera
This commit is contained in:
1 parent
68f49253fe
commit
cd285b3a39
12 files changed
+242
-74
No files matched your search
@@ -9,6 +9,17 @@ const NPC_BASE := "res://assets/models/generated/npc_base.glb"
|
||||
const SLOTS := ["skin", "cloth", "pants", "accessory", "hair"]
|
||||
const BONES := ["Head", "HipL", "HipR", "KneeL", "KneeR", "ShoulderL", "ShoulderR",
|
||||
"ElbowL", "ElbowR", "HandL", "HandR"]
|
||||
## The Quaternius rig (ADR-0018) is a 62-joint armature. `NpcAvatar` maps these
|
||||
## joints onto the animation proxies, so they are the drop-in requirement for a
|
||||
## Quaternius-derived character instead of the 13-core names.
|
||||
const QUATERNIUS_BONES := ["Head", "UpperLeg.L", "UpperLeg.R", "LowerLeg.L",
|
||||
"LowerLeg.R", "UpperArm.L", "UpperArm.R", "LowerArm.L", "LowerArm.R",
|
||||
"Wrist.L", "Wrist.R"]
|
||||
|
||||
## Quaternius-derived characters are detected by a dot-suffixed UpperArm joint;
|
||||
## the procedural library uses ShoulderL/ShoulderR instead.
|
||||
func _is_quaternius(skeleton: Skeleton3D) -> bool:
|
||||
return skeleton != null and skeleton.find_bone("UpperArm.L") >= 0
|
||||
|
||||
func _manifest() -> Dictionary:
|
||||
if not FileAccess.file_exists(MANIFEST):
|
||||
@@ -76,7 +87,8 @@ func test_characters_match_the_npc_base_contract() -> bool:
|
||||
problems.append("%s lacks Skeleton3D/npc_base" % name)
|
||||
node.free()
|
||||
continue
|
||||
for bone in BONES:
|
||||
var required: Array = QUATERNIUS_BONES if _is_quaternius(skeleton) else BONES
|
||||
for bone in required:
|
||||
if skeleton.find_bone(bone) < 0:
|
||||
problems.append("%s missing bone %s" % [name, bone])
|
||||
var slots := {}
|
||||
@@ -105,6 +117,11 @@ func test_character_joints_line_up_with_npc_base() -> bool:
|
||||
var skeleton := node.find_child("Skeleton3D", true, false) as Skeleton3D if node else null
|
||||
if skeleton == null:
|
||||
problems.append("%s has no skeleton" % name)
|
||||
elif _is_quaternius(skeleton):
|
||||
# ADR-0018: a Quaternius character keeps its own 62-joint skeleton and
|
||||
# NpcAvatar re-derives the proxy pivots from it, so the joints are not
|
||||
# expected to sit on npc_base's 13-core positions.
|
||||
pass
|
||||
else:
|
||||
for bone in BONES:
|
||||
var a := _global_rest(base_skel, base_skel.find_bone(bone)).origin
|
||||
@@ -127,10 +144,18 @@ func test_character_bones_accept_the_avatar_pose_math() -> bool:
|
||||
if skeleton == null:
|
||||
problems.append(name)
|
||||
else:
|
||||
for bone in BONES:
|
||||
var bones: Array = QUATERNIUS_BONES if _is_quaternius(skeleton) else BONES
|
||||
for bone in bones:
|
||||
var index := skeleton.find_bone(bone)
|
||||
var rest := skeleton.get_bone_rest(index).basis.get_rotation_quaternion()
|
||||
skeleton.set_bone_pose_rotation(index, rest * Quaternion(Vector3.RIGHT, 0.4))
|
||||
if index < 0:
|
||||
problems.append("%s missing %s" % [name, bone])
|
||||
continue
|
||||
# NpcAvatar._swing_bone(): pose = rest^-1 * swing * rest, applied
|
||||
# through the global pose. The composition must stay a valid
|
||||
# rotation on every limb bone of either rig.
|
||||
var rest := skeleton.get_bone_global_rest(index).basis.get_rotation_quaternion()
|
||||
var swing := Quaternion(Vector3.RIGHT, 0.4)
|
||||
skeleton.set_bone_pose_rotation(index, rest.inverse() * swing * rest)
|
||||
if not skeleton.get_bone_pose_rotation(index).is_normalized():
|
||||
problems.append("%s.%s" % [name, bone])
|
||||
if node:
|
||||
|
||||
Reference in new issue
Block a user