glTF fixups, parse skeleton definition from avatar_skeleton.xml
This commit is contained in:
@@ -39,6 +39,9 @@ class _IterableStub:
|
||||
__iter__: Callable
|
||||
|
||||
|
||||
RAD_TO_DEG = 180 / math.pi
|
||||
|
||||
|
||||
class TupleCoord(recordclass.datatuple, _IterableStub): # type: ignore
|
||||
__options__ = {
|
||||
"fast_new": False,
|
||||
@@ -372,5 +375,5 @@ class TaggedUnion(recordclass.datatuple): # type: ignore
|
||||
__all__ = [
|
||||
"Vector3", "Vector4", "Vector2", "Quaternion", "TupleCoord",
|
||||
"UUID", "RawBytes", "StringEnum", "JankStringyBytes", "TaggedUnion",
|
||||
"IntEnum", "IntFlag", "flags_to_pod", "Pretty"
|
||||
"IntEnum", "IntFlag", "flags_to_pod", "Pretty", "RAD_TO_DEG"
|
||||
]
|
||||
|
||||
@@ -7,10 +7,6 @@ WIP LLMesh -> glTF converter, for testing eventual glTF -> LLMesh conversion log
|
||||
# * * The weird scaling on the collision volumes / fitted mesh bones will clearly be a problem.
|
||||
# Blender's Collada importer / export stuff appears to sidestep these problems (by only applying
|
||||
# the bind shape matrix to the model in the viewport?)
|
||||
# * Do we actually need to convert to GLTF coordinates space, or is it enough to parent everything
|
||||
# to a correctly rotated scene node? That would definitely be less nasty. I wasn't totally sure
|
||||
# how that would affect coordinates on re-exports if you did export selected without selecting the
|
||||
# scene root, though. This seems to be what assimp does so maybe it's fine?
|
||||
|
||||
import math
|
||||
import pprint
|
||||
@@ -25,7 +21,7 @@ import transformations
|
||||
|
||||
from hippolyzer.lib.base.colladatools import llsd_to_mat4
|
||||
from hippolyzer.lib.base.mesh import LLMeshSerializer, MeshAsset, positions_from_domain, SkinSegmentDict, VertexWeight
|
||||
from hippolyzer.lib.base.mesh_skeleton import required_joint_hierarchy, SKELETON_JOINTS
|
||||
from hippolyzer.lib.base.mesh_skeleton import AVATAR_SKELETON
|
||||
from hippolyzer.lib.base.serialization import BufferReader
|
||||
|
||||
|
||||
@@ -70,7 +66,11 @@ def sl_mat4_to_gltf(mat: np.ndarray) -> List[float]:
|
||||
|
||||
This should only be done immediately before storing the matrix in a glTF structure!
|
||||
"""
|
||||
# TODO: This is probably not correct. We definitely need to flip Z but there's
|
||||
# probably a better way to do it.
|
||||
decomp = [sl_to_gltf_coords(x) for x in transformations.decompose_matrix(mat)]
|
||||
trans = decomp[3]
|
||||
decomp[3] = (trans[0], trans[1], -trans[2])
|
||||
return list(transformations.compose_matrix(*decomp).flatten(order='F'))
|
||||
|
||||
|
||||
@@ -91,9 +91,15 @@ def sl_weights_to_gltf(sl_weights: List[List[VertexWeight]]) -> Tuple[np.ndarray
|
||||
weights = np.zeros((len(sl_weights), 4), dtype=np.float32)
|
||||
|
||||
for i, vert_weights in enumerate(sl_weights):
|
||||
# We need to re-normalize these since the quantization can mess them up
|
||||
collected_weights = []
|
||||
for j, vert_weight in enumerate(vert_weights):
|
||||
joints[i, j] = vert_weight.joint_idx
|
||||
weights[i, j] = vert_weight.weight
|
||||
collected_weights.append(vert_weight.weight)
|
||||
weight_sum = sum(collected_weights)
|
||||
if weight_sum:
|
||||
for j, weight in enumerate(collected_weights):
|
||||
weights[i, j] = weight / weight_sum
|
||||
|
||||
return joints, weights
|
||||
|
||||
@@ -124,12 +130,10 @@ class GLTFBuilder:
|
||||
mesh: Optional[gltflib.Mesh] = None,
|
||||
transform: Optional[np.ndarray] = None,
|
||||
) -> gltflib.Node:
|
||||
if transform is None:
|
||||
transform = np.identity(4)
|
||||
node = gltflib.Node(
|
||||
name=name,
|
||||
mesh=self.model.meshes.index(mesh) if mesh else None,
|
||||
matrix=sl_mat4_to_gltf(transform),
|
||||
matrix=sl_mat4_to_gltf(transform) if transform is not None else None,
|
||||
children=[],
|
||||
)
|
||||
self.model.nodes.append(node)
|
||||
@@ -264,7 +268,7 @@ class GLTFBuilder:
|
||||
# that expect to be able to reorient the entire mesh through the
|
||||
# inverse bind matrices.
|
||||
joints: Dict[str, gltflib.Node] = {}
|
||||
required_joints = required_joint_hierarchy(skin['joint_names'])
|
||||
required_joints = AVATAR_SKELETON.get_required_joints(skin['joint_names'])
|
||||
# If this is present, it overrides the joint position from the skeleton definition
|
||||
if 'alt_inverse_bind_matrix' in skin:
|
||||
joint_overrides = dict(zip(skin['joint_names'], skin['alt_inverse_bind_matrix']))
|
||||
@@ -272,19 +276,22 @@ class GLTFBuilder:
|
||||
joint_overrides = {}
|
||||
|
||||
for joint_name in required_joints:
|
||||
joint_pos = SKELETON_JOINTS[joint_name].translation
|
||||
joint = AVATAR_SKELETON[joint_name]
|
||||
joint_matrix = joint.matrix
|
||||
override = joint_overrides.get(joint_name)
|
||||
decomp = list(transformations.decompose_matrix(joint_matrix))
|
||||
if override:
|
||||
# We specifically only want the translation from the override!
|
||||
joint_pos = transformations.translation_from_matrix(llsd_to_mat4(override))
|
||||
node = self.add_node(joint_name, transform=transformations.compose_matrix(translate=tuple(joint_pos)))
|
||||
decomp_override = transformations.decompose_matrix(llsd_to_mat4(override))
|
||||
decomp[3] = decomp_override[3]
|
||||
joint_matrix = transformations.compose_matrix(*decomp)
|
||||
node = self.add_node(joint_name, transform=joint_matrix)
|
||||
joints[joint_name] = node
|
||||
|
||||
# Add each joint to the child list of their respective parents
|
||||
for joint_name, joint_node in joints.items():
|
||||
parent_name = SKELETON_JOINTS[joint_name].parent
|
||||
if parent_name:
|
||||
joints[parent_name].children.append(self.model.nodes.index(joint_node))
|
||||
if parent := AVATAR_SKELETON[joint_name].parent:
|
||||
joints[parent().name].children.append(self.model.nodes.index(joint_node))
|
||||
return joints
|
||||
|
||||
def add_skin(self, name: str, joint_nodes: Dict[str, gltflib.Node], skin_seg: SkinSegmentDict) -> gltflib.Skin:
|
||||
@@ -292,11 +299,14 @@ class GLTFBuilder:
|
||||
for joint_name in skin_seg['joint_names']:
|
||||
joints_arr.append(self.model.nodes.index(joint_nodes[joint_name]))
|
||||
|
||||
# TODO: glTF also doesn't have a concept of a "bind shape matrix" per its skinning docs,
|
||||
# so we may have to mix that into the mesh data or inverse bind matrices somehow.
|
||||
# glTF also doesn't have a concept of a "bind shape matrix" like Collada does
|
||||
# per its skinning docs, so we have to mix it into the inverse bind matrices.
|
||||
# See https://github.com/KhronosGroup/glTF-Tutorials/blob/master/gltfTutorial/gltfTutorial_020_Skins.md
|
||||
# TODO: apply the bind shape matrix to the mesh data instead?
|
||||
inv_binds = []
|
||||
for inv_bind in skin_seg['inverse_bind_matrix']:
|
||||
inv_bind = llsd_to_mat4(inv_bind)
|
||||
bind_shape_matrix = llsd_to_mat4(skin_seg['bind_shape_matrix'])
|
||||
for joint_name, inv_bind in zip(skin_seg['joint_names'], skin_seg['inverse_bind_matrix']):
|
||||
inv_bind = np.matmul(llsd_to_mat4(inv_bind), bind_shape_matrix)
|
||||
inv_binds.append(sl_mat4_to_gltf(inv_bind))
|
||||
inv_binds_data = np.array(inv_binds, dtype=np.float32).tobytes()
|
||||
buffer_view = self.add_buffer_view(inv_binds_data, target=None)
|
||||
@@ -340,7 +350,6 @@ def build_gltf_mesh(builder: GLTFBuilder, mesh: MeshAsset, name: str):
|
||||
|
||||
skin_seg: Optional[SkinSegmentDict] = mesh.segments.get('skin')
|
||||
skin = None
|
||||
armature_node = None
|
||||
mesh_transform = np.identity(4)
|
||||
if skin_seg:
|
||||
mesh_transform = llsd_to_mat4(skin_seg['bind_shape_matrix'])
|
||||
@@ -351,6 +360,7 @@ def build_gltf_mesh(builder: GLTFBuilder, mesh: MeshAsset, name: str):
|
||||
builder.scene.nodes.append(gltf_model.nodes.index(armature_node))
|
||||
armature_node.children.append(gltf_model.nodes.index(joint_nodes['mPelvis']))
|
||||
skin = builder.add_skin("Armature", joint_nodes, skin_seg)
|
||||
# skin = None
|
||||
|
||||
mesh_node = builder.add_node(
|
||||
name,
|
||||
@@ -359,15 +369,32 @@ def build_gltf_mesh(builder: GLTFBuilder, mesh: MeshAsset, name: str):
|
||||
)
|
||||
if skin and False:
|
||||
# TODO: This badly mangles up the mesh right now, especially given the
|
||||
# collision volume scales. Investigate using a more accurate skeleton def.
|
||||
# collision volume scales. This is an issue in blender where it doesn't
|
||||
# apply the inverse bind matrices relative to the scale and rotation of
|
||||
# the bones themselves, as it should. Blender's glTF loader tries to recover
|
||||
# from this by applying certain transforms as a pose, but the damage has
|
||||
# been done by that point. Nobody else runs really runs into this because
|
||||
# they have the good sense to not use some nightmare abomination rig with
|
||||
# scaling and rotation on the skeleton like SL does.
|
||||
#
|
||||
# I can't see any way to properly support this without changing how Blender
|
||||
# handles armatures to make inverse bind matrices relative to bone scale and rot
|
||||
# (which they probably won't and shouldn't do, since there's no internal concept
|
||||
# of bone scale or rot in Blender right now.)
|
||||
#
|
||||
# Should investigate an Avastar-style approach of optionally retargeting
|
||||
# to a Blender-compatible rig with translation-only bones, and modify
|
||||
# the bind matrices to accommodate. The glTF importer supports metadata through
|
||||
# the "extras" fields, so we can potentially abuse the "bind_mat" metadata field
|
||||
# that Blender already uses for the "Keep Bind Info" Collada import / export hack.
|
||||
mesh_node.matrix = None
|
||||
mesh_node.skin = gltf_model.skins.index(skin)
|
||||
armature_node.children.append(gltf_model.nodes.index(mesh_node))
|
||||
builder.scene.nodes.append(gltf_model.nodes.index(armature_node))
|
||||
else:
|
||||
# Add a root node that will re-orient our scene into GLTF coords.
|
||||
# scene_node = builder.add_node("scene")
|
||||
# scene_node.children.append(gltf_model.nodes.index(mesh_node))
|
||||
builder.scene.nodes.append(gltf_model.nodes.index(mesh_node))
|
||||
|
||||
builder.scene.nodes.append(gltf_model.nodes.index(mesh_node))
|
||||
|
||||
for node in builder.model.nodes:
|
||||
if not node.children:
|
||||
node.children = None
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
@@ -1,212 +1,90 @@
|
||||
import dataclasses
|
||||
import weakref
|
||||
from typing import *
|
||||
|
||||
from hippolyzer.lib.base.datatypes import Vector3
|
||||
import transformations
|
||||
from lxml import etree
|
||||
|
||||
from hippolyzer.lib.base.datatypes import Vector3, RAD_TO_DEG
|
||||
from hippolyzer.lib.base.helpers import get_resource_filename
|
||||
|
||||
|
||||
class JointNode(NamedTuple):
|
||||
parent: Optional[str]
|
||||
MAYBE_JOINT_REF = Optional[Callable[[], "JointNode"]]
|
||||
|
||||
|
||||
@dataclasses.dataclass(unsafe_hash=True)
|
||||
class JointNode:
|
||||
name: str
|
||||
parent: MAYBE_JOINT_REF
|
||||
translation: Vector3
|
||||
pivot: Vector3 # pivot point for the joint, generally the same as translation
|
||||
rotation: Vector3 # Euler rotation in degrees
|
||||
scale: Vector3
|
||||
type: str # bone or collision_volume
|
||||
|
||||
@property
|
||||
def matrix(self):
|
||||
return transformations.compose_matrix(
|
||||
scale=tuple(self.scale),
|
||||
angles=tuple(self.rotation / RAD_TO_DEG),
|
||||
translate=tuple(self.translation),
|
||||
)
|
||||
|
||||
|
||||
# Joint translation data resolved from the male collada file
|
||||
# TODO: Get this from avatar_skeleton.xml instead? We may need other transforms.
|
||||
SKELETON_JOINTS = {
|
||||
'mPelvis': JointNode(parent=None, translation=Vector3(0.0, 0.0, 1.067)),
|
||||
'PELVIS': JointNode(parent='mPelvis', translation=Vector3(-0.01, 0.0, -0.02)),
|
||||
'BUTT': JointNode(parent='mPelvis', translation=Vector3(-0.06, 0.0, -0.1)),
|
||||
'mSpine1': JointNode(parent='mPelvis', translation=Vector3(0.0, 0.0, 0.084)),
|
||||
'mSpine2': JointNode(parent='mSpine1', translation=Vector3(0.0, 0.0, -0.084)),
|
||||
'mTorso': JointNode(parent='mSpine2', translation=Vector3(0.0, 0.0, 0.084)),
|
||||
'BELLY': JointNode(parent='mTorso', translation=Vector3(0.028, 0.0, 0.04)),
|
||||
'LEFT_HANDLE': JointNode(parent='mTorso', translation=Vector3(0.0, 0.1, 0.058)),
|
||||
'RIGHT_HANDLE': JointNode(parent='mTorso', translation=Vector3(0.0, -0.1, 0.058)),
|
||||
'LOWER_BACK': JointNode(parent='mTorso', translation=Vector3(0.0, 0.0, 0.023)),
|
||||
'mSpine3': JointNode(parent='mTorso', translation=Vector3(-0.015, 0.0, 0.205)),
|
||||
'mSpine4': JointNode(parent='mSpine3', translation=Vector3(0.015, 0.0, -0.205)),
|
||||
'mChest': JointNode(parent='mSpine4', translation=Vector3(-0.015, 0.0, 0.205)),
|
||||
'CHEST': JointNode(parent='mChest', translation=Vector3(0.028, 0.0, 0.07)),
|
||||
'LEFT_PEC': JointNode(parent='mChest', translation=Vector3(0.119, 0.082, 0.042)),
|
||||
'RIGHT_PEC': JointNode(parent='mChest', translation=Vector3(0.119, -0.082, 0.042)),
|
||||
'UPPER_BACK': JointNode(parent='mChest', translation=Vector3(0.0, 0.0, 0.017)),
|
||||
'mNeck': JointNode(parent='mChest', translation=Vector3(-0.01, 0.0, 0.251)),
|
||||
'NECK': JointNode(parent='mNeck', translation=Vector3(0.0, 0.0, 0.02)),
|
||||
'mHead': JointNode(parent='mNeck', translation=Vector3(0.0, 0.0, 0.076)),
|
||||
'HEAD': JointNode(parent='mHead', translation=Vector3(0.02, 0.0, 0.07)),
|
||||
'mSkull': JointNode(parent='mHead', translation=Vector3(0.0, 0.0, 0.079)),
|
||||
'mEyeRight': JointNode(parent='mHead', translation=Vector3(0.098, -0.036, 0.079)),
|
||||
'mEyeLeft': JointNode(parent='mHead', translation=Vector3(0.098, 0.036, 0.079)),
|
||||
'mFaceRoot': JointNode(parent='mHead', translation=Vector3(0.025, 0.0, 0.045)),
|
||||
'mFaceEyeAltRight': JointNode(parent='mFaceRoot', translation=Vector3(0.073, -0.036, 0.034)),
|
||||
'mFaceEyeAltLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.073, 0.036, 0.034)),
|
||||
'mFaceForeheadLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.061, 0.035, 0.083)),
|
||||
'mFaceForeheadRight': JointNode(parent='mFaceRoot', translation=Vector3(0.061, -0.035, 0.083)),
|
||||
'mFaceEyebrowOuterLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.064, 0.051, 0.048)),
|
||||
'mFaceEyebrowCenterLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.07, 0.043, 0.056)),
|
||||
'mFaceEyebrowInnerLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.075, 0.022, 0.051)),
|
||||
'mFaceEyebrowOuterRight': JointNode(parent='mFaceRoot', translation=Vector3(0.064, -0.051, 0.048)),
|
||||
'mFaceEyebrowCenterRight': JointNode(parent='mFaceRoot', translation=Vector3(0.07, -0.043, 0.056)),
|
||||
'mFaceEyebrowInnerRight': JointNode(parent='mFaceRoot', translation=Vector3(0.075, -0.022, 0.051)),
|
||||
'mFaceEyeLidUpperLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.073, 0.036, 0.034)),
|
||||
'mFaceEyeLidLowerLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.073, 0.036, 0.034)),
|
||||
'mFaceEyeLidUpperRight': JointNode(parent='mFaceRoot', translation=Vector3(0.073, -0.036, 0.034)),
|
||||
'mFaceEyeLidLowerRight': JointNode(parent='mFaceRoot', translation=Vector3(0.073, -0.036, 0.034)),
|
||||
'mFaceEar1Left': JointNode(parent='mFaceRoot', translation=Vector3(0.0, 0.08, 0.002)),
|
||||
'mFaceEar2Left': JointNode(parent='mFaceEar1Left', translation=Vector3(-0.019, 0.018, 0.025)),
|
||||
'mFaceEar1Right': JointNode(parent='mFaceRoot', translation=Vector3(0.0, -0.08, 0.002)),
|
||||
'mFaceEar2Right': JointNode(parent='mFaceEar1Right', translation=Vector3(-0.019, -0.018, 0.025)),
|
||||
'mFaceNoseLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.086, 0.015, -0.004)),
|
||||
'mFaceNoseCenter': JointNode(parent='mFaceRoot', translation=Vector3(0.102, 0.0, 0.0)),
|
||||
'mFaceNoseRight': JointNode(parent='mFaceRoot', translation=Vector3(0.086, -0.015, -0.004)),
|
||||
'mFaceCheekLowerLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.05, 0.034, -0.031)),
|
||||
'mFaceCheekUpperLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.07, 0.034, -0.005)),
|
||||
'mFaceCheekLowerRight': JointNode(parent='mFaceRoot', translation=Vector3(0.05, -0.034, -0.031)),
|
||||
'mFaceCheekUpperRight': JointNode(parent='mFaceRoot', translation=Vector3(0.07, -0.034, -0.005)),
|
||||
'mFaceJaw': JointNode(parent='mFaceRoot', translation=Vector3(-0.001, 0.0, -0.015)),
|
||||
'mFaceChin': JointNode(parent='mFaceJaw', translation=Vector3(0.074, 0.0, -0.054)),
|
||||
'mFaceTeethLower': JointNode(parent='mFaceJaw', translation=Vector3(0.021, 0.0, -0.039)),
|
||||
'mFaceLipLowerLeft': JointNode(parent='mFaceTeethLower', translation=Vector3(0.045, 0.0, 0.0)),
|
||||
'mFaceLipLowerRight': JointNode(parent='mFaceTeethLower', translation=Vector3(0.045, 0.0, 0.0)),
|
||||
'mFaceLipLowerCenter': JointNode(parent='mFaceTeethLower', translation=Vector3(0.045, 0.0, 0.0)),
|
||||
'mFaceTongueBase': JointNode(parent='mFaceTeethLower', translation=Vector3(0.039, 0.0, 0.005)),
|
||||
'mFaceTongueTip': JointNode(parent='mFaceTongueBase', translation=Vector3(0.022, 0.0, 0.007)),
|
||||
'mFaceJawShaper': JointNode(parent='mFaceRoot', translation=Vector3(0.0, 0.0, 0.0)),
|
||||
'mFaceForeheadCenter': JointNode(parent='mFaceRoot', translation=Vector3(0.069, 0.0, 0.065)),
|
||||
'mFaceNoseBase': JointNode(parent='mFaceRoot', translation=Vector3(0.094, 0.0, -0.016)),
|
||||
'mFaceTeethUpper': JointNode(parent='mFaceRoot', translation=Vector3(0.02, 0.0, -0.03)),
|
||||
'mFaceLipUpperLeft': JointNode(parent='mFaceTeethUpper', translation=Vector3(0.045, 0.0, -0.003)),
|
||||
'mFaceLipUpperRight': JointNode(parent='mFaceTeethUpper', translation=Vector3(0.045, 0.0, -0.003)),
|
||||
'mFaceLipCornerLeft': JointNode(parent='mFaceTeethUpper', translation=Vector3(0.028, -0.019, -0.01)),
|
||||
'mFaceLipCornerRight': JointNode(parent='mFaceTeethUpper', translation=Vector3(0.028, 0.019, -0.01)),
|
||||
'mFaceLipUpperCenter': JointNode(parent='mFaceTeethUpper', translation=Vector3(0.045, 0.0, -0.003)),
|
||||
'mFaceEyecornerInnerLeft': JointNode(parent='mFaceRoot', translation=Vector3(0.075, 0.017, 0.032)),
|
||||
'mFaceEyecornerInnerRight': JointNode(parent='mFaceRoot', translation=Vector3(0.075, -0.017, 0.032)),
|
||||
'mFaceNoseBridge': JointNode(parent='mFaceRoot', translation=Vector3(0.091, 0.0, 0.02)),
|
||||
'mCollarLeft': JointNode(parent='mChest', translation=Vector3(-0.021, 0.085, 0.165)),
|
||||
'L_CLAVICLE': JointNode(parent='mCollarLeft', translation=Vector3(0.02, 0.0, 0.02)),
|
||||
'mShoulderLeft': JointNode(parent='mCollarLeft', translation=Vector3(0.0, 0.079, 0.0)),
|
||||
'L_UPPER_ARM': JointNode(parent='mShoulderLeft', translation=Vector3(0.0, 0.12, 0.01)),
|
||||
'mElbowLeft': JointNode(parent='mShoulderLeft', translation=Vector3(0.0, 0.248, 0.0)),
|
||||
'L_LOWER_ARM': JointNode(parent='mElbowLeft', translation=Vector3(0.0, 0.1, 0.0)),
|
||||
'mWristLeft': JointNode(parent='mElbowLeft', translation=Vector3(0.0, 0.205, 0.0)),
|
||||
'L_HAND': JointNode(parent='mWristLeft', translation=Vector3(0.01, 0.05, 0.0)),
|
||||
'mHandMiddle1Left': JointNode(parent='mWristLeft', translation=Vector3(0.013, 0.101, 0.015)),
|
||||
'mHandMiddle2Left': JointNode(parent='mHandMiddle1Left', translation=Vector3(-0.001, 0.04, -0.006)),
|
||||
'mHandMiddle3Left': JointNode(parent='mHandMiddle2Left', translation=Vector3(-0.001, 0.049, -0.008)),
|
||||
'mHandIndex1Left': JointNode(parent='mWristLeft', translation=Vector3(0.038, 0.097, 0.015)),
|
||||
'mHandIndex2Left': JointNode(parent='mHandIndex1Left', translation=Vector3(0.017, 0.036, -0.006)),
|
||||
'mHandIndex3Left': JointNode(parent='mHandIndex2Left', translation=Vector3(0.014, 0.032, -0.006)),
|
||||
'mHandRing1Left': JointNode(parent='mWristLeft', translation=Vector3(-0.01, 0.099, 0.009)),
|
||||
'mHandRing2Left': JointNode(parent='mHandRing1Left', translation=Vector3(-0.013, 0.038, -0.008)),
|
||||
'mHandRing3Left': JointNode(parent='mHandRing2Left', translation=Vector3(-0.013, 0.04, -0.009)),
|
||||
'mHandPinky1Left': JointNode(parent='mWristLeft', translation=Vector3(-0.031, 0.095, 0.003)),
|
||||
'mHandPinky2Left': JointNode(parent='mHandPinky1Left', translation=Vector3(-0.024, 0.025, -0.006)),
|
||||
'mHandPinky3Left': JointNode(parent='mHandPinky2Left', translation=Vector3(-0.015, 0.018, -0.004)),
|
||||
'mHandThumb1Left': JointNode(parent='mWristLeft', translation=Vector3(0.031, 0.026, 0.004)),
|
||||
'mHandThumb2Left': JointNode(parent='mHandThumb1Left', translation=Vector3(0.028, 0.032, -0.001)),
|
||||
'mHandThumb3Left': JointNode(parent='mHandThumb2Left', translation=Vector3(0.023, 0.031, -0.001)),
|
||||
'mCollarRight': JointNode(parent='mChest', translation=Vector3(-0.021, -0.085, 0.165)),
|
||||
'R_CLAVICLE': JointNode(parent='mCollarRight', translation=Vector3(0.02, 0.0, 0.02)),
|
||||
'mShoulderRight': JointNode(parent='mCollarRight', translation=Vector3(0.0, -0.079, 0.0)),
|
||||
'R_UPPER_ARM': JointNode(parent='mShoulderRight', translation=Vector3(0.0, -0.12, 0.01)),
|
||||
'mElbowRight': JointNode(parent='mShoulderRight', translation=Vector3(0.0, -0.248, 0.0)),
|
||||
'R_LOWER_ARM': JointNode(parent='mElbowRight', translation=Vector3(0.0, -0.1, 0.0)),
|
||||
'mWristRight': JointNode(parent='mElbowRight', translation=Vector3(0.0, -0.205, 0.0)),
|
||||
'R_HAND': JointNode(parent='mWristRight', translation=Vector3(0.01, -0.05, 0.0)),
|
||||
'mHandMiddle1Right': JointNode(parent='mWristRight', translation=Vector3(0.013, -0.101, 0.015)),
|
||||
'mHandMiddle2Right': JointNode(parent='mHandMiddle1Right', translation=Vector3(-0.001, -0.04, -0.006)),
|
||||
'mHandMiddle3Right': JointNode(parent='mHandMiddle2Right', translation=Vector3(-0.001, -0.049, -0.008)),
|
||||
'mHandIndex1Right': JointNode(parent='mWristRight', translation=Vector3(0.038, -0.097, 0.015)),
|
||||
'mHandIndex2Right': JointNode(parent='mHandIndex1Right', translation=Vector3(0.017, -0.036, -0.006)),
|
||||
'mHandIndex3Right': JointNode(parent='mHandIndex2Right', translation=Vector3(0.014, -0.032, -0.006)),
|
||||
'mHandRing1Right': JointNode(parent='mWristRight', translation=Vector3(-0.01, -0.099, 0.009)),
|
||||
'mHandRing2Right': JointNode(parent='mHandRing1Right', translation=Vector3(-0.013, -0.038, -0.008)),
|
||||
'mHandRing3Right': JointNode(parent='mHandRing2Right', translation=Vector3(-0.013, -0.04, -0.009)),
|
||||
'mHandPinky1Right': JointNode(parent='mWristRight', translation=Vector3(-0.031, -0.095, 0.003)),
|
||||
'mHandPinky2Right': JointNode(parent='mHandPinky1Right', translation=Vector3(-0.024, -0.025, -0.006)),
|
||||
'mHandPinky3Right': JointNode(parent='mHandPinky2Right', translation=Vector3(-0.015, -0.018, -0.004)),
|
||||
'mHandThumb1Right': JointNode(parent='mWristRight', translation=Vector3(0.031, -0.026, 0.004)),
|
||||
'mHandThumb2Right': JointNode(parent='mHandThumb1Right', translation=Vector3(0.028, -0.032, -0.001)),
|
||||
'mHandThumb3Right': JointNode(parent='mHandThumb2Right', translation=Vector3(0.023, -0.031, -0.001)),
|
||||
'mWingsRoot': JointNode(parent='mChest', translation=Vector3(-0.014, 0.0, 0.0)),
|
||||
'mWing1Left': JointNode(parent='mWingsRoot', translation=Vector3(-0.099, 0.105, 0.181)),
|
||||
'mWing2Left': JointNode(parent='mWing1Left', translation=Vector3(-0.168, 0.169, 0.067)),
|
||||
'mWing3Left': JointNode(parent='mWing2Left', translation=Vector3(-0.181, 0.183, 0.0)),
|
||||
'mWing4Left': JointNode(parent='mWing3Left', translation=Vector3(-0.171, 0.173, 0.0)),
|
||||
'mWing4FanLeft': JointNode(parent='mWing3Left', translation=Vector3(-0.171, 0.173, 0.0)),
|
||||
'mWing1Right': JointNode(parent='mWingsRoot', translation=Vector3(-0.099, -0.105, 0.181)),
|
||||
'mWing2Right': JointNode(parent='mWing1Right', translation=Vector3(-0.168, -0.169, 0.067)),
|
||||
'mWing3Right': JointNode(parent='mWing2Right', translation=Vector3(-0.181, -0.183, 0.0)),
|
||||
'mWing4Right': JointNode(parent='mWing3Right', translation=Vector3(-0.171, -0.173, 0.0)),
|
||||
'mWing4FanRight': JointNode(parent='mWing3Right', translation=Vector3(-0.171, -0.173, 0.0)),
|
||||
'mHipRight': JointNode(parent='mPelvis', translation=Vector3(0.034, -0.129, -0.041)),
|
||||
'R_UPPER_LEG': JointNode(parent='mHipRight', translation=Vector3(-0.02, 0.05, -0.22)),
|
||||
'mKneeRight': JointNode(parent='mHipRight', translation=Vector3(-0.001, 0.049, -0.491)),
|
||||
'R_LOWER_LEG': JointNode(parent='mKneeRight', translation=Vector3(-0.02, 0.0, -0.2)),
|
||||
'mAnkleRight': JointNode(parent='mKneeRight', translation=Vector3(-0.029, 0.0, -0.468)),
|
||||
'R_FOOT': JointNode(parent='mAnkleRight', translation=Vector3(0.077, 0.0, -0.041)),
|
||||
'mFootRight': JointNode(parent='mAnkleRight', translation=Vector3(0.112, 0.0, -0.061)),
|
||||
'mToeRight': JointNode(parent='mFootRight', translation=Vector3(0.109, 0.0, 0.0)),
|
||||
'mHipLeft': JointNode(parent='mPelvis', translation=Vector3(0.034, 0.127, -0.041)),
|
||||
'L_UPPER_LEG': JointNode(parent='mHipLeft', translation=Vector3(-0.02, -0.05, -0.22)),
|
||||
'mKneeLeft': JointNode(parent='mHipLeft', translation=Vector3(-0.001, -0.046, -0.491)),
|
||||
'L_LOWER_LEG': JointNode(parent='mKneeLeft', translation=Vector3(-0.02, 0.0, -0.2)),
|
||||
'mAnkleLeft': JointNode(parent='mKneeLeft', translation=Vector3(-0.029, 0.001, -0.468)),
|
||||
'L_FOOT': JointNode(parent='mAnkleLeft', translation=Vector3(0.077, 0.0, -0.041)),
|
||||
'mFootLeft': JointNode(parent='mAnkleLeft', translation=Vector3(0.112, 0.0, -0.061)),
|
||||
'mToeLeft': JointNode(parent='mFootLeft', translation=Vector3(0.109, 0.0, 0.0)),
|
||||
'mTail1': JointNode(parent='mPelvis', translation=Vector3(-0.116, 0.0, 0.047)),
|
||||
'mTail2': JointNode(parent='mTail1', translation=Vector3(-0.197, 0.0, 0.0)),
|
||||
'mTail3': JointNode(parent='mTail2', translation=Vector3(-0.168, 0.0, 0.0)),
|
||||
'mTail4': JointNode(parent='mTail3', translation=Vector3(-0.142, 0.0, 0.0)),
|
||||
'mTail5': JointNode(parent='mTail4', translation=Vector3(-0.112, 0.0, 0.0)),
|
||||
'mTail6': JointNode(parent='mTail5', translation=Vector3(-0.094, 0.0, 0.0)),
|
||||
'mGroin': JointNode(parent='mPelvis', translation=Vector3(0.064, 0.0, -0.097)),
|
||||
'mHindLimbsRoot': JointNode(parent='mPelvis', translation=Vector3(-0.2, 0.0, 0.084)),
|
||||
'mHindLimb1Left': JointNode(parent='mHindLimbsRoot', translation=Vector3(-0.204, 0.129, -0.125)),
|
||||
'mHindLimb2Left': JointNode(parent='mHindLimb1Left', translation=Vector3(0.002, -0.046, -0.491)),
|
||||
'mHindLimb3Left': JointNode(parent='mHindLimb2Left', translation=Vector3(-0.03, -0.003, -0.468)),
|
||||
'mHindLimb4Left': JointNode(parent='mHindLimb3Left', translation=Vector3(0.112, 0.0, -0.061)),
|
||||
'mHindLimb1Right': JointNode(parent='mHindLimbsRoot', translation=Vector3(-0.204, -0.129, -0.125)),
|
||||
'mHindLimb2Right': JointNode(parent='mHindLimb1Right', translation=Vector3(0.002, 0.046, -0.491)),
|
||||
'mHindLimb3Right': JointNode(parent='mHindLimb2Right', translation=Vector3(-0.03, 0.003, -0.468)),
|
||||
'mHindLimb4Right': JointNode(parent='mHindLimb3Right', translation=Vector3(0.112, 0.0, -0.061)),
|
||||
}
|
||||
@dataclasses.dataclass
|
||||
class Skeleton:
|
||||
joint_dict: Dict[str, JointNode]
|
||||
|
||||
def __getitem__(self, item: str) -> JointNode:
|
||||
return self.joint_dict[item]
|
||||
|
||||
@classmethod
|
||||
def _parse_node_children(cls, joint_dict: Dict[str, JointNode], node: etree.ElementBase, parent: MAYBE_JOINT_REF):
|
||||
name = node.get('name')
|
||||
joint = JointNode(
|
||||
name=name,
|
||||
parent=parent,
|
||||
translation=_get_vec_attr(node, "pos", Vector3()),
|
||||
pivot=_get_vec_attr(node, "pivot", Vector3()),
|
||||
rotation=_get_vec_attr(node, "rot", Vector3()),
|
||||
scale=_get_vec_attr(node, "scale", Vector3(1, 1, 1)),
|
||||
type=node.tag,
|
||||
)
|
||||
joint_dict[name] = joint
|
||||
for child in node.iterchildren():
|
||||
cls._parse_node_children(joint_dict, child, weakref.ref(joint))
|
||||
|
||||
@classmethod
|
||||
def from_xml(cls, node: etree.ElementBase):
|
||||
joint_dict = {}
|
||||
cls._parse_node_children(joint_dict, node, None)
|
||||
return cls(joint_dict)
|
||||
|
||||
def get_required_joints(self, joint_names: Collection[str]) -> Set[str]:
|
||||
"""Get all joints required to have a chain from all joints up to the root joint"""
|
||||
required = set(joint_names)
|
||||
for joint_name in joint_names:
|
||||
joint_node = self.joint_dict.get(joint_name)
|
||||
while joint_node:
|
||||
required.add(joint_node.name)
|
||||
if not joint_node.parent:
|
||||
break
|
||||
joint_node = joint_node.parent()
|
||||
return required
|
||||
|
||||
|
||||
def required_joint_hierarchy(joint_names: Collection[str]) -> Set[str]:
|
||||
"""Get all joints required to have a chain from all joints up to the root joint"""
|
||||
required = set(joint_names)
|
||||
for joint_name in joint_names:
|
||||
while parent_node := SKELETON_JOINTS.get(joint_name):
|
||||
required.add(joint_name)
|
||||
joint_name = parent_node.parent
|
||||
return required
|
||||
def load_avatar_skeleton() -> Skeleton:
|
||||
skel_path = get_resource_filename("lib/base/data/avatar_skeleton.xml")
|
||||
with open(skel_path, 'r') as f:
|
||||
skel_root = etree.fromstring(f.read())
|
||||
return Skeleton.from_xml(skel_root.getchildren()[0])
|
||||
|
||||
|
||||
# def load_skeleton_nodes() -> etree.ElementBase:
|
||||
# # TODO: this sucks. Can't we construct nodes with the appropriate transformation
|
||||
# # matrices from the data in `avatar_skeleton.xml`?
|
||||
# skel_path = get_resource_filename("lib/base/data/male_collada_joints.xml")
|
||||
# with open(skel_path, 'r') as f:
|
||||
# return etree.fromstring(f.read())
|
||||
#
|
||||
#
|
||||
# def print_skeleton():
|
||||
# import pprint
|
||||
# skel_root = load_skeleton_nodes()
|
||||
#
|
||||
# dae = collada.Collada()
|
||||
# joints = {}
|
||||
# for skel_node in skel_root.iter():
|
||||
# # xpath is loathsome so this is easier.
|
||||
# if "node" not in skel_node.tag or skel_node.get('type') != 'JOINT':
|
||||
# continue
|
||||
# col_node = collada.scene.Node.load(dae, skel_node, {})
|
||||
#
|
||||
# par_node = skel_node.getparent()
|
||||
# parent_name = None
|
||||
# if par_node and par_node.get('name'):
|
||||
# parent_name = par_node.get('name')
|
||||
# translation = Vector3(*transformations.translation_from_matrix(col_node.matrix))
|
||||
# joints[col_node.id] = JointNode(parent=parent_name, translation=translation)
|
||||
# pprint.pprint(joints, sort_dicts=False)
|
||||
def _get_vec_attr(node, attr_name, default) -> Vector3:
|
||||
attr_val = node.get(attr_name, None)
|
||||
if not attr_val:
|
||||
return default
|
||||
return Vector3(*(float(x) for x in attr_val.split(" ") if x))
|
||||
|
||||
|
||||
AVATAR_SKELETON = load_avatar_skeleton()
|
||||
|
||||
Reference in New Issue
Block a user