| from fairmotion.ops import conversions |
| import numpy as np |
| from IPython import embed |
|
|
| from fairmotion.core import motion as motion_class |
| from fairmotion.ops import motion as fm_motion_util |
| from fairmotion.utils import constants |
| import copy |
|
|
| from fairmotion.ops.motion import transform_poses |
|
|
| """ ====================== STITCHING METHODS ======================= """ |
|
|
|
|
| def simple_stitch(cur_pose, next_poses, align_id=0): |
| next_pose = next_poses[align_id] |
| nextT = next_pose.get_root_facing_transform_byRoot(use_height=False) |
| curT = cur_pose.get_root_facing_transform_byRoot(use_height=False) |
| alignT = curT @ np.linalg.inv(nextT) |
| fm_motion_util.transform_poses(next_poses, alignT, local=False) |
| return next_poses |
|
|
|
|
| def overlap_blend(from_poses, to_poses, skel_preserve=False): |
| |
| assert len(from_poses) == len(to_poses) |
|
|
| F = len(from_poses) |
| ratios = [0.5 - 0.5 * np.cos(i / (F - 1) * np.pi) for i in range(F)] |
| for i in range(F): |
| to_poses[i] = fm_motion_util.blend( |
| from_poses[i], to_poses[i], ratios[i], skel_preserve |
| ) |
|
|
| return to_poses, ratios |
|
|
|
|
| """ ====================== NORMALIZE/DENORMALIZE MOTION =======================""" |
|
|
|
|
| |
| |
| def motion_normalize(motion, tpose=None): |
| if tpose is None: |
| tpose = motion.poses[0] |
| recon_motion = motion_class.Motion(skel=copy.deepcopy(motion.skel)) |
| recon_motion.set_fps(motion.fps) |
|
|
| for i, joint in enumerate(recon_motion.skel.joints): |
| if joint.parent_joint is not None: |
| joint.xform_from_parent_joint = constants.eye_T() |
| joint.xform_global = constants.eye_T() |
|
|
| for i, joint in enumerate(recon_motion.skel.joints): |
| jidx = recon_motion.skel.get_index_joint(joint) |
| jpos = tpose.get_transform(jidx, local=False)[0:3, 3] |
| if joint.parent_joint is not None: |
| pidx = recon_motion.skel.get_index_joint(joint.parent_joint) |
| ppos = tpose.get_transform(pidx, local=False)[0:3, 3] |
| else: |
| ppos = jpos |
|
|
| T_new = constants.eye_T() |
| T_new[0:3, 3] = jpos - ppos |
|
|
| joint.xform_from_parent_joint = T_new |
| if joint.parent_joint is not None: |
| joint.xform_global = np.dot( |
| joint.parent_joint.xform_global, |
| joint.xform_from_parent_joint, |
| ) |
| else: |
| joint.xform_global = joint.xform_from_parent_joint |
|
|
| for el in joint.extra_links: |
| elpos = np.dot( |
| tpose.get_transform(jidx, local=False), joint.extra_links[el] |
| )[0:3, 3] |
| joint.extra_links[el] = constants.eye_T() |
| joint.extra_links[el][0:3, 3] = elpos - jpos |
|
|
| num_frames = len(motion.poses) |
| T_relative = np.zeros((num_frames, motion.skel.num_joints(), 4, 4)) |
| T_relative[...] = constants.eye_T() |
| for i in range(num_frames): |
| recon_motion.add_one_frame(T_relative[i]) |
|
|
| motion_local = motion.to_matrix(local=True) |
| motion_global = motion.to_matrix(local=False) |
|
|
| for j, (joint, recon_joint) in enumerate( |
| zip(motion.skel.joints, recon_motion.skel.joints) |
| ): |
| if joint.parent_joint is None: |
| try: |
| |
| T_relative[:, j, :3, :3] = motion_local[:, j, :3, :3] @ np.linalg.inv( |
| tpose.data[j][:3, :3] |
| ) |
| T_relative[:, j, :3, 3] = motion_local[:, j, :3, 3] |
| except: |
| embed() |
| else: |
| pidx = motion.skel.get_index_joint(joint.parent_joint) |
| jidx = motion.skel.get_index_joint(joint) |
| assert j == jidx |
|
|
| gp = motion_global[:, pidx, :3, :3] |
| gj = motion_global[:, jidx, :3, :3] |
| p2j = joint.xform_from_parent_joint[:3, :3] |
|
|
| delta_global = constants.eye_R() |
| delta_global = gj @ np.linalg.inv(gp @ p2j @ tpose.data[j][:3, :3]) |
|
|
| new_gp = recon_motion.get_joint_transforms(joint, local=False)[:, :3, :3] |
| new_p2j = recon_joint.xform_from_parent_joint[:3, :3] |
| new_pg_p2j = new_gp @ new_p2j |
|
|
| delta_local = constants.eye_R() |
| delta_local = np.linalg.inv(new_pg_p2j) @ delta_global @ new_pg_p2j |
|
|
| T_relative[:, j, :3, :3] = delta_local |
|
|
| return recon_motion |
|
|
|
|
| |
| def copy_normalized_joint(norm_motion, out_motion, joint_name): |
| |
| |
| norm_joint = norm_motion.skel.get_joint(joint_name) |
| norm_parent_joint = norm_joint.parent_joint |
| out_parent_joint = out_motion.skel.get_joint(norm_parent_joint.name) |
|
|
| |
| |
| I = constants.eye_T() |
| xform_bw_skel = ( |
| np.linalg.inv(out_parent_joint.xform_global) @ norm_parent_joint.xform_global |
| ) |
| orig_xform = norm_joint.xform_from_parent_joint |
| out_xform = xform_bw_skel @ orig_xform |
|
|
| out_new_joint = motion_class.Joint( |
| joint_name, xform_from_parent_joint=out_xform, parent_joint=out_parent_joint |
| ) |
| out_motion.skel.add_joint(out_new_joint, out_parent_joint) |
| for pose in out_motion.poses: |
| assert pose.skel is out_motion.skel |
| |
| data = pose.data |
| pose.data = np.concatenate( |
| (pose.data, np.expand_dims(out_xform, axis=0)), axis=0 |
| ) |
|
|
|
|
| |
| def motion_denormalize(norm_motion, tpose_motion, add_missing_joints=False): |
| |
| norm_bvh_joints = list(map(lambda x: x.name, norm_motion.skel.joints)) |
| tpose_bvh_joints = list(map(lambda x: x.name, tpose_motion.skel.joints)) |
|
|
| parent_names = set( |
| map( |
| lambda x: x.parent_joint.name if x.parent_joint is not None else ":None:", |
| norm_motion.skel.joints, |
| ) |
| ) |
|
|
| error = False |
|
|
| for joint_name in norm_bvh_joints: |
| in_joint = norm_motion.skel.get_joint(joint_name) |
| in_parent_name = ( |
| in_joint.parent_joint.name |
| if in_joint.parent_joint is not None |
| else ":None:" |
| ) |
|
|
| if joint_name not in tpose_bvh_joints: |
| if in_parent_name in tpose_bvh_joints and add_missing_joints: |
| copy_normalized_joint(norm_motion, tpose_motion, joint_name) |
| tpose_bvh_joints.append(joint_name) |
| continue |
| else: |
| print("Required joint {} is missing in tpose".format(joint_name)) |
| error = True |
|
|
| t_joint = tpose_motion.skel.get_joint(joint_name) |
| t_parent_name = ( |
| t_joint.parent_joint.name if t_joint.parent_joint is not None else ":None:" |
| ) |
|
|
| if in_parent_name != t_parent_name: |
| print( |
| "{}'s parent joint name is inconsistent: {} and {}".format( |
| joint_name, in_parent_name, t_parent_name |
| ) |
| ) |
| error = True |
|
|
| if error: |
| return "Failed to denormalize motion {}".format(norm_motion.name) |
|
|
| |
| zero_pose = motion_class.Pose(tpose_motion.skel) |
| tpose_motion.add_one_frame(zero_pose.data) |
|
|
| norm_tpose_motion = motion_normalize(tpose_motion) |
| norm_zero_pose = norm_tpose_motion.poses[-1] |
|
|
| |
| in_zero_pose = motion_class.Pose(norm_motion.skel) |
| for i, joint in enumerate(norm_motion.skel.joints): |
| if joint.name in tpose_bvh_joints: |
| data_idx = tpose_motion.skel.get_index_joint(joint.name) |
| in_zero_pose.data[i] = norm_zero_pose.data[data_idx] |
|
|
| norm_motion_denorm = motion_normalize(norm_motion, tpose=in_zero_pose) |
|
|
| return norm_motion_denorm |
|
|
|
|
| def tpose_height_to_skel(motion, tpose, apply_=False): |
| if apply_: |
| res_motion = motion |
| else: |
| res_motion = copy.deepcopy(motion) |
|
|
| tpose_root = tpose.data[0][:3, 3] |
| tpose_root[0] = 0 |
| tpose_root[2] = 0 |
|
|
| for n_pose in res_motion.poses: |
| n_pose.data[0, :3, 3] -= tpose_root |
|
|
| root_new_xform_global = res_motion.skel.joints[0].xform_global |
| root_new_xform_global[:3, 3] += tpose_root |
| res_motion.skel.joints[0].set_xform_global_recursive(root_new_xform_global) |
|
|
| return res_motion |
|
|
|
|
| def motion_normalize_h2s(motion, handle_penetration=True): |
|
|
| tpose = motion.poses[0] |
| if handle_penetration: |
| skel = motion.skel |
| joint_names = [j.name for j in skel.joints] |
| lt = "LeftToeBase_End" if "LeftToeBase_End" in joint_names else "LeftToe_End" |
| rt = "RightToeBase_End" if "RightToeBase_End" in joint_names else "RightToe_End" |
| if not (lt in joint_names) or not (rt in joint_names): |
| print("handle penetration Err") |
| embed() |
| exit() |
| lty = tpose.get_transform(lt, local=False)[1, 3] |
| rty = tpose.get_transform(rt, local=False)[1, 3] |
| if (lty + rty) / 2 < 0: |
| print("handle penetrating t-pose") |
| print(motion.name) |
| |
| transform_poses([tpose], conversions.p2T([0, -(lty + rty) / 2.0, 0])) |
|
|
| n_motion = motion_normalize(motion, tpose) |
| n_tpose = n_motion.poses[0] |
| n_motion.poses = n_motion.poses[1:] |
| tpose_height_to_skel(n_motion, n_tpose, apply_=True) |
|
|
| |
| |
|
|
| return n_motion, n_tpose |
|
|
|
|
| """ ====================== other ... =======================""" |
|
|
|
|
| def get_links(skel): |
| """ |
| ex) |
| [[0, 1, 2, 3, 4, 5], |
| [0, 6, 7, 8, 9, 10], |
| [0, 11, 12, 13], |
| [13, 14, 15, 16], |
| [13, 17, 18, 19, 20], |
| [13, 21, 22, 23, 24]] |
| """ |
|
|
| tree = [] |
| mask = np.array([0] * len(skel.joints)) |
|
|
| def dfs_tweak(joint): |
| ji = skel.get_index_joint(joint) |
| mask[ji] = 1 |
| if ji != 0 and len(joint.child_joints) > 1: |
| tree[-1].append(ji) |
| for child in joint.child_joints: |
| ci = skel.get_index_joint(child) |
| if len(joint.child_joints) > 1: |
| tree.append([]) |
| tree[-1].append(ji) |
| if not mask[ci]: |
| dfs_tweak(child) |
| if len(joint.child_joints) == 0: |
| tree[-1].append(ji) |
|
|
| dfs_tweak(skel.joints[0]) |
| return tree |
|
|
|
|
| def get_edge(skel, bidirection=True): |
| """ |
| input: skeletona |
| - bidiretion |
| - TRUE: (parent, child) and (child, parent) |
| - FALSE: (parent, child) only |
| - include_ee: treat end-effector as joint or not |
| output: edge [2, E] |
| """ |
| edge = [] |
| for joint in skel.joints: |
| ji = skel.get_index_joint(joint) |
| for child in joint.child_joints: |
| ci = skel.get_index_joint(child) |
| edge.append([ji, ci]) |
| if bidirection: |
| edge.append([ci, ji]) |
|
|
| return np.array(edge).transpose(1, 0) |
|
|
|
|
| def skel_interpolate(skel_a, skel_b, ratio): |
| |
| joint_names_a = [joint.name for joint in skel_a.joints] |
| joint_names_b = [joint.name for joint in skel_b.joints] |
| assert set(joint_names_a) == set(joint_names_b) |
|
|
| skel_new = copy.deepcopy(skel_a) |
| for joint_name in joint_names_a: |
| joint_a = skel_a.get_joint(joint_name) |
| joint_b = skel_b.get_joint(joint_name) |
| joint_new = skel_new.get_joint(joint_name) |
| joint_new.xform_from_parent_joint[:3, 3] = ( |
| 1 - ratio |
| ) * joint_a.xform_from_parent_joint[ |
| :3, 3 |
| ] + ratio * joint_b.xform_from_parent_joint[ |
| :3, 3 |
| ] |
|
|
| for joint_new in skel_new.joints: |
| if joint_new.parent_joint is None: |
| continue |
| joint_new.xform_global = np.dot( |
| joint_new.parent_joint.xform_global, |
| joint_new.xform_from_parent_joint, |
| ) |
| return skel_new |
|
|
|
|
| from fairmotion.utils import contact_utils |
|
|
|
|
| def make_motion( |
| skel, |
| qR, |
| ra_T, |
| c, |
| first_frame_zero=False, |
| contact_cleanup=False, |
| cid=None, |
| motion=None, |
| ): |
| """ |
| skel motion_class.Skel |
| qR arr [T, J, 3, 3] |
| ra_T arr [T, 4, 4] # root height applied here |
| c arr [T, J, 1] |
| cid list [int] |
| """ |
| frame_num, joint_num = qR.shape[0], qR.shape[1] |
|
|
| poses_T = np.zeros((frame_num, joint_num, 4, 4)) |
| poses_T[...] = constants.eye_T() |
| poses_T[..., :3, :3] = qR |
|
|
| if first_frame_zero: |
| ra_T[0] = constants.eye_T() |
|
|
| poses_T[:, 0] = ra_T @ poses_T[:, 0] |
| poses_T[:, 0, 1, 3] = ra_T[:, 1, 3] - skel.joints[0].xform_from_parent_joint[1, 3] |
|
|
| if not motion: |
| motion = motion_class.Motion(skel=skel) |
| for f in range(frame_num): |
| motion.add_one_frame(poses_T[f]) |
|
|
| if contact_cleanup: |
| assert cid is not None |
| toe_idx = [int(cid[1]), int(cid[3])] |
| if contact_cleanup: |
| |
| contact_utils.init_contact(motion, 0, toe_idx) |
| contact_utils.motion_foot_cleanup(motion, c[:, toe_idx, 0]) |
| np.set_printoptions(precision=5, suppress=True) |
| return motion, (toe_idx, c) |
| else: |
| return motion, (list(range(joint_num)), c) |
|
|
|
|
| if __name__ == "__main__": |
|
|
| import os |
| from .mypath import * |
| from fairmotion.data import bvh |
|
|
| motion_megan = bvh.load( |
| os.path.join(DATA_DIR, "characters", "polished_bvh", "megan.bvh") |
| ) |
| n_motion_megan, n_tpose_megan = motion_normalize_h2s(motion_megan) |
| skel_megan = n_motion_megan.skel |
|
|
| motion_mousey = bvh.load( |
| os.path.join(DATA_DIR, "characters", "polished_bvh", "mousey.bvh") |
| ) |
| n_motion_mousey, n_tpose_mousey = motion_normalize_h2s(motion_mousey) |
| skel_mousey = n_motion_mousey.skel |
|
|
| new_skel = skel_interpolate(skel_megan, skel_mousey, 0.5) |
|
|