Add tools for mirroring animations
This commit is contained in:
@@ -8,7 +8,7 @@ applied to the mesh before upload.
|
||||
I personally use manglers to strip bounding box materials you need
|
||||
to add to give a mesh an arbitrary center of rotation / scaling.
|
||||
"""
|
||||
|
||||
from hippolyzer.lib.base.helpers import reorient_coord
|
||||
from hippolyzer.lib.base.mesh import MeshAsset
|
||||
from hippolyzer.lib.proxy.addons import AddonManager
|
||||
|
||||
@@ -16,23 +16,8 @@ import local_mesh
|
||||
AddonManager.hot_reload(local_mesh, require_addons_loaded=True)
|
||||
|
||||
|
||||
def _reorient_coord(coord, orientation, normals=False):
|
||||
coords = []
|
||||
for axis in orientation:
|
||||
axis_idx = abs(axis) - 1
|
||||
if normals:
|
||||
# Normals have a static domain from -1.0 to 1.0, just negate.
|
||||
new_coord = coord[axis_idx] if axis >= 0 else -coord[axis_idx]
|
||||
else:
|
||||
new_coord = coord[axis_idx] if axis >= 0 else 1.0 - coord[axis_idx]
|
||||
coords.append(new_coord)
|
||||
if coord.__class__ in (list, tuple):
|
||||
return coord.__class__(coords)
|
||||
return coord.__class__(*coords)
|
||||
|
||||
|
||||
def _reorient_coord_list(coord_list, orientation, normals=False):
|
||||
return [_reorient_coord(x, orientation, normals) for x in coord_list]
|
||||
def _reorient_coord_list(coord_list, orientation, min_val: int | float = 0):
|
||||
return [reorient_coord(x, orientation, min_val) for x in coord_list]
|
||||
|
||||
|
||||
def reorient_mesh(orientation):
|
||||
@@ -47,7 +32,7 @@ def reorient_mesh(orientation):
|
||||
# flipping the axes around.
|
||||
material["Position"] = _reorient_coord_list(material["Position"], orientation)
|
||||
# Are you even supposed to do this to the normals?
|
||||
material["Normal"] = _reorient_coord_list(material["Normal"], orientation, normals=True)
|
||||
material["Normal"] = _reorient_coord_list(material["Normal"], orientation, min_val=-1)
|
||||
return mesh
|
||||
return _reorienter
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@ Assorted utilities to make creating animations from scratch easier
|
||||
"""
|
||||
|
||||
import copy
|
||||
from typing import List, Union
|
||||
from typing import List, Union, Mapping
|
||||
|
||||
from hippolyzer.lib.base.datatypes import Vector3, Quaternion
|
||||
from hippolyzer.lib.base.llanim import PosKeyframe, RotKeyframe
|
||||
from hippolyzer.lib.base.llanim import PosKeyframe, RotKeyframe, JOINTS_DICT, Joint
|
||||
from hippolyzer.lib.base.mesh_skeleton import AVATAR_SKELETON
|
||||
from hippolyzer.lib.base.multidict import OrderedMultiDict
|
||||
|
||||
|
||||
def smooth_step(t: float):
|
||||
@@ -89,3 +91,35 @@ def smooth_rot(start: Quaternion, end: Quaternion, inter_frames: int, time: floa
|
||||
smooth_t = smooth_step(t)
|
||||
frames.append(RotKeyframe(time=time + (t * duration), rot=rot_interp(start, end, smooth_t)))
|
||||
return frames + [RotKeyframe(time=time + duration, rot=end)]
|
||||
|
||||
|
||||
def mirror_joints(joints_dict: Mapping[str, Joint]) -> JOINTS_DICT:
|
||||
"""Mirror a joints dict so left / right are swapped, including transformations"""
|
||||
new_joints: JOINTS_DICT = OrderedMultiDict()
|
||||
|
||||
for joint_name, joint in joints_dict.items():
|
||||
inverse_joint_node = AVATAR_SKELETON[joint_name].inverse
|
||||
if not inverse_joint_node:
|
||||
new_joints[joint_name] = joint
|
||||
continue
|
||||
|
||||
# Okay, this is one we have to actually mirror
|
||||
new_joint = Joint(joint.priority, [], [])
|
||||
|
||||
for rot_keyframe in joint.rot_keyframes:
|
||||
new_joint.rot_keyframes.append(RotKeyframe(
|
||||
time=rot_keyframe.time,
|
||||
# Just need to mirror on yaw and roll
|
||||
rot=Quaternion.from_euler(*(rot_keyframe.rot.to_euler() * Vector3(-1, 1, -1)))
|
||||
))
|
||||
|
||||
for pos_keyframe in joint.pos_keyframes:
|
||||
new_joint.pos_keyframes.append(PosKeyframe(
|
||||
time=pos_keyframe.time,
|
||||
# Y is left / right so just negate it.
|
||||
pos=pos_keyframe.pos * Vector3(1, -1, 1)
|
||||
))
|
||||
|
||||
new_joints[inverse_joint_node.name] = new_joint
|
||||
|
||||
return new_joints
|
||||
|
||||
@@ -195,3 +195,21 @@ def create_logged_task(
|
||||
task = asyncio.create_task(coro, name=name)
|
||||
add_future_logger(task, name, logger)
|
||||
return task
|
||||
|
||||
|
||||
def reorient_coord(coord, new_orientation, min_val: int | float = 0):
|
||||
"""
|
||||
Reorient a coordinate instance such that its components are negated and transposed appropriately.
|
||||
|
||||
For ex:
|
||||
reorient_coord((1,2,3), (3,-2,-1)) == (3,-2,-1)
|
||||
"""
|
||||
min_val = abs(min_val)
|
||||
coords = []
|
||||
for axis in new_orientation:
|
||||
axis_idx = abs(axis) - 1
|
||||
new_coord = coord[axis_idx] if axis >= 0 else min_val - coord[axis_idx]
|
||||
coords.append(new_coord)
|
||||
if coord.__class__ in (list, tuple):
|
||||
return coord.__class__(coords)
|
||||
return coord.__class__(*coords)
|
||||
|
||||
@@ -15,6 +15,8 @@ CONSTRAINT_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(Constraint))
|
||||
POSKEYFRAME_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(PosKeyframe))
|
||||
ROTKEYFRAME_DATACLASS = se.ForwardSerializable(lambda: se.Dataclass(RotKeyframe))
|
||||
|
||||
JOINTS_DICT = OrderedMultiDict[str, "Joint"]
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class Animation:
|
||||
@@ -29,7 +31,7 @@ class Animation:
|
||||
ease_in_duration: float = se.dataclass_field(se.F32)
|
||||
ease_out_duration: float = se.dataclass_field(se.F32)
|
||||
hand_pose: HandPose = se.dataclass_field(lambda: se.IntEnum(HandPose, se.U32), default=0)
|
||||
joints: OrderedMultiDict[str, Joint] = se.dataclass_field(se.MultiDictAdapter(
|
||||
joints: JOINTS_DICT = se.dataclass_field(se.MultiDictAdapter(
|
||||
se.Collection(se.U32, se.Tuple(se.CStr(), JOINT_DATACLASS)),
|
||||
))
|
||||
constraints: List[Constraint] = se.dataclass_field(
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import copy
|
||||
import dataclasses
|
||||
import re
|
||||
import weakref
|
||||
from typing import *
|
||||
|
||||
@@ -74,6 +75,29 @@ class JointNode:
|
||||
children.append(node)
|
||||
return children
|
||||
|
||||
@property
|
||||
def inverse(self) -> Optional[JointNode]:
|
||||
l_re = re.compile(r"(.*?(?:_|\b))L((?:_|\b).*)")
|
||||
r_re = re.compile(r"(.*?(?:_|\b))R((?:_|\b).*)")
|
||||
|
||||
inverse_name = None
|
||||
if "Left" in self.name:
|
||||
inverse_name = self.name.replace("Left", "Right")
|
||||
elif "LEFT" in self.name:
|
||||
inverse_name = self.name.replace("LEFT", "RIGHT")
|
||||
elif l_re.match(self.name):
|
||||
inverse_name = re.sub(l_re, r"\1R\2", self.name)
|
||||
elif "Right" in self.name:
|
||||
inverse_name = self.name.replace("Right", "Left")
|
||||
elif "RIGHT" in self.name:
|
||||
inverse_name = self.name.replace("RIGHT", "LEFT")
|
||||
elif r_re.match(self.name):
|
||||
inverse_name = re.sub(r_re, r"\1L\2", self.name)
|
||||
|
||||
if inverse_name:
|
||||
return self.skeleton().joint_dict.get(inverse_name)
|
||||
return None
|
||||
|
||||
@property
|
||||
def descendents(self) -> Set[JointNode]:
|
||||
descendents: Set[JointNode] = set()
|
||||
|
||||
@@ -30,3 +30,9 @@ class TestSkeleton(unittest.TestCase):
|
||||
[0., 0., 0., 1.]
|
||||
])
|
||||
np.testing.assert_equal(expected_mat, self.skeleton["mNeck"].matrix)
|
||||
|
||||
def test_get_inverse_joint(self):
|
||||
self.assertEqual("R_CLAVICLE", self.skeleton["L_CLAVICLE"].inverse.name)
|
||||
self.assertEqual(None, self.skeleton["mChest"].inverse)
|
||||
self.assertEqual("mHandMiddle1Right", self.skeleton["mHandMiddle1Left"].inverse.name)
|
||||
self.assertEqual("RIGHT_HANDLE", self.skeleton["LEFT_HANDLE"].inverse.name)
|
||||
|
||||
Reference in New Issue
Block a user