Beispiel #1
0
    def _init_t_model_to_joint(self, t_parent_to_model: Transformation = None):
        if t_parent_to_model is None:
            t_parent_to_model = Transformation()

        t_joint_to_model = t_parent_to_model.compose(
            self.t_initial_joint_to_parent)
        self.t_model_to_joint = t_joint_to_model.inverse()
        for child in self.children:
            child._init_t_model_to_joint(t_joint_to_model)
Beispiel #2
0
    def calc_t_joint(self, t_parent_to_world: Transformation = None):
        if t_parent_to_world is None:
            t_parent_to_world = Transformation()
        self.t_joint_to_world = t_parent_to_world.compose(
            self.t_current_joint_to_parent)
        self.t_joint = self.t_joint_to_world.compose(self.t_model_to_joint)

        for child in self.children:
            child.calc_t_joint(self.t_joint_to_world)
Beispiel #3
0
    def __init__(
        self,
        name: str,
        t_joint_to_parent: ndarray,
        index: int = None,
        # This is for supporting livecap models
        axis: ndarray = None,
        movement_type: MovementType = None,
    ):
        if not isinstance(t_joint_to_parent, ndarray):
            raise ValueError
        if t_joint_to_parent.shape != (4, 4):
            raise ValueError

        self.is_root = False
        self.index = index
        self.name = name
        self.children: List[Joint] = []

        self.t_initial_joint_to_parent = Transformation(
            t_joint_to_parent.copy())  # initial transformation from the parent
        self.t_current_joint_to_parent = Transformation(
            t_joint_to_parent.copy())  # current transformation from the parent
        self.t_model_to_joint: Transformation = None  # inverse bind transform

        self.t_joint = Transformation(
        )  # this matrix will be applied to vertices
        self.t_joint_to_world = Transformation(
        )  # this is in order to get the current position

        self.animation = JointAnimation()

        self.axis = axis
        self.movement_type = movement_type
Beispiel #4
0
    def get_animation(self) -> Animation:
        # make sure that all of the timestamps are close in time
        timestamps = None
        for joint in self.root_joint.preorder_iterator():
            if joint.has_animation():
                if timestamps is None:
                    timestamps = joint.animation.timestamps
                elif np.any((timestamps != joint.animation.timestamps)):
                    raise RuntimeError(
                        "2 joints with different timestamps discovered.")

        # for each time stamp, collect all of the transformations
        keyframes = []
        for i in range(self.n_keyframes):
            timestamp = timestamps[i]
            t_list = []
            for j in range(self.n_total_joints):
                joint = self.root_joint.find_joint_by_index(j)
                if joint.has_animation():
                    t_list.append(Transformation(joint.animation.poses[i]))
                else:
                    t_list.append(joint.t_initial_joint_to_parent.copy())

            keyframes.append(KeyFrame(t_list, timestamp))

        return Animation(keyframes)
Beispiel #5
0
    def _transform_model(self, transformation_matrix: ndarray, scale: float):
        t_old_to_new = Transformation(transformation_matrix)
        t_new_to_old = t_old_to_new.inverse()
        self.vertices = t_old_to_new.apply(self.vertices)
        self.vertices *= scale
        self.vertex_normals = t_old_to_new.apply(self.vertex_normals,
                                                 is_point=False)

        for joint in self.joint_list:
            joint.convert_basis(t_old_to_new, t_new_to_old, scale)
            if joint.has_animation():
                poses = joint.animation.poses
                for i in range(len(poses)):
                    poses[i] = t_old_to_new.matrix @ poses[
                        i] @ t_new_to_old.matrix
                joint.animation.poses = poses
Beispiel #6
0
 def convert_basis(self,
                   t_old_to_new: Transformation,
                   t_new_to_old: Transformation = None,
                   scale: float = 1):
     self.t_initial_joint_to_parent.convert_basis(t_old_to_new,
                                                  t_new_to_old, scale)
     self.t_current_joint_to_parent.convert_basis(t_old_to_new,
                                                  t_new_to_old, scale)
     self.t_model_to_joint.convert_basis(t_old_to_new, t_new_to_old, scale)
     self.t_joint.convert_basis(t_old_to_new, t_new_to_old, scale)
     self.t_joint_to_world.convert_basis(t_old_to_new, t_new_to_old, scale)
     if self.axis is not None:
         self.axis = t_old_to_new.apply(self.axis, is_point=False)
Beispiel #7
0
def interpolate_transformations(tr1: Transformation, tr1_time: float,
                                tr2: Transformation, tr2_time: float,
                                current_time: float) -> Transformation:
    '''
    '''
    assert tr1_time <= current_time <= tr2_time

    # interpolate rotations
    key_times = [tr1_time, tr2_time]
    key_rotations: Rotation = R.from_matrix(
        (tr1.rotation.as_matrix(), tr2.rotation.as_matrix()))
    slerp = Slerp(key_times, key_rotations)
    interpolated_rotation = slerp([current_time]).as_matrix()[0]

    # interpolate translation
    p = (current_time - tr1_time) / (tr2_time - tr1_time)
    interpolated_translation = (1 - p) * tr1.translation + p * tr2.translation

    # return the interpolated transformation
    interpolated_transform = Transformation(
        R.from_matrix(interpolated_rotation), interpolated_translation)
    return interpolated_transform
Beispiel #8
0
def read_collada_file(model_path: Union[Path, str]) -> RawModelData:
    """Reads the model and returns it's data in the desirable format."""
    if isinstance(model_path, Path):
        model_path = str(model_path)
    collada_obj = Collada(model_path)

    # assumes that there is a single controller
    if len(collada_obj.controllers) != 1:
        raise RuntimeError(
            f'there should be exactly 1 controller, got {len(collada_obj.controllers)}'
        )
    controller: Skin = collada_obj.controllers[0]
    triangles: TriangleSet = controller.geometry.primitives[0]

    # get vertices
    vertices = triangles.vertex
    n_vertices = vertices.shape[0]
    # apply the bind_shape_matrix to all of the vertices
    t_bind_shape = Transformation(controller.bind_shape_matrix)
    vertices = t_bind_shape.apply(vertices)
    # get faces
    faces = triangles.vertex_index
    n_faces = triangles.ntriangles
    _check_all_vertices_in_faces(n_vertices, faces)
    # get texture
    vertex_texture_coords = _get_vertex_texture_coords(
        n_vertices, faces, triangles.texcoord_indexset, triangles.texcoordset)
    # get normals
    vertex_normals = _get_vertex_normals(n_vertices, faces,
                                         triangles.normal_index,
                                         triangles.normal)

    # get joint data
    root_joint, joint_list, n_bound_joints, n_total_joints = _get_joints_data(
        collada_obj.scene.nodes, controller)

    # get weight matrix
    weight_matrix = _get_weight_matrix(n_vertices, n_bound_joints,
                                       controller.weights.data.flatten(),
                                       controller.weight_index,
                                       controller.joint_index)
    # read animation data
    animations = collada_obj.animations
    n_keyframes = 0
    if len(animations) > 0:
        n_keyframes = _read_animations(animations, root_joint)

    return RawModelData(
        n_vertices=n_vertices,
        n_faces=n_faces,
        n_bound_joints=n_bound_joints,
        n_total_joints=n_total_joints,
        vertices=vertices,
        faces=faces,
        vertex_normals=vertex_normals,
        vertex_texture_coords=vertex_texture_coords,
        weight_matrix=weight_matrix,
        joint_list=joint_list,
        root_joint=root_joint,
        n_keyframes=n_keyframes,
    )
Beispiel #9
0
class Joint:
    """Represents a Joint.

    Attributes:
        name(str): name of the joint
        children(List[Joint]): a list of all of the joint's children
        is_root(bool): if this joint is the root of the joint tree.
        index(int): an index property that is in use in the model's rendering
        t_joint_to_parent(ndarray): transformation from the joint to it's parent.
            corresponds to the 'matrix' attribute in the collada.Node
        t_model_to_joint(ndarray): transformation from the model space to the joint space, also
            called the 'inverse_bind_matrix'
        t_model_to_world(ndarray): the cumulative transformation that a joint operates on a joint. i.e., in run time,
            to get the vertex location with respect to that joint we only multiply by this matrix.
            also called 'joint_matrix'
        animation(JointAnimation): this is a JointAnimation object that is associated with this joint

    Methods:
        __init__: creates a new Joint
        set_root: sets the Joint as the root of the joint tree
        init_t_model_to_joint: initializes the 't_model_to_joint' of all the joints in the tree,
            this method should only be called on the root of the tree for the results to be correct
        calc_joint_matrices: calculates the 't_model_to_world' matrix for all the joints in the tree.
            this method should only be called on the root
        print_tree: prints the joint tree

    Notes:
        reference frames:
            model - where the vertex coordinates are
            joint - joint local coordinate system where the joint is at the origin
            parent - the joint parent's local coordinate system
            world - the coordinate system in the world
    """
    def __init__(
        self,
        name: str,
        t_joint_to_parent: ndarray,
        index: int = None,
        # This is for supporting livecap models
        axis: ndarray = None,
        movement_type: MovementType = None,
    ):
        if not isinstance(t_joint_to_parent, ndarray):
            raise ValueError
        if t_joint_to_parent.shape != (4, 4):
            raise ValueError

        self.is_root = False
        self.index = index
        self.name = name
        self.children: List[Joint] = []

        self.t_initial_joint_to_parent = Transformation(
            t_joint_to_parent.copy())  # initial transformation from the parent
        self.t_current_joint_to_parent = Transformation(
            t_joint_to_parent.copy())  # current transformation from the parent
        self.t_model_to_joint: Transformation = None  # inverse bind transform

        self.t_joint = Transformation(
        )  # this matrix will be applied to vertices
        self.t_joint_to_world = Transformation(
        )  # this is in order to get the current position

        self.animation = JointAnimation()

        self.axis = axis
        self.movement_type = movement_type

    def set_root(self):
        self.is_root = True

    def init_skeleton(self):
        if not self.is_root:
            raise RuntimeError(
                "This method should only be called on the joint root")
        self._init_t_model_to_joint()
        self.calc_t_joint()

    def calc_t_joint(self, t_parent_to_world: Transformation = None):
        if t_parent_to_world is None:
            t_parent_to_world = Transformation()
        self.t_joint_to_world = t_parent_to_world.compose(
            self.t_current_joint_to_parent)
        self.t_joint = self.t_joint_to_world.compose(self.t_model_to_joint)

        for child in self.children:
            child.calc_t_joint(self.t_joint_to_world)

    def _init_t_model_to_joint(self, t_parent_to_model: Transformation = None):
        if t_parent_to_model is None:
            t_parent_to_model = Transformation()

        t_joint_to_model = t_parent_to_model.compose(
            self.t_initial_joint_to_parent)
        self.t_model_to_joint = t_joint_to_model.inverse()
        for child in self.children:
            child._init_t_model_to_joint(t_joint_to_model)

    @property
    def initial_translation(self):
        return self.t_initial_joint_to_parent.translation

    @property
    def initial_angles(self):
        return self.t_initial_joint_to_parent.angles

    @property
    def world_coordinates(self):
        return self.t_joint_to_world.translation

    def apply_along_axis(self, value: float):
        if self.axis is None or self.movement_type is None:
            raise RuntimeError
        vector = self.axis * value
        if self.movement_type == MovementType.translation:
            self.t_current_joint_to_parent.translation = vector
        elif self.movement_type == MovementType.rotation:
            self.t_current_joint_to_parent.rotvec = vector

    def set_joint_to_parent_angles(self, angles: ndarray):
        self.t_current_joint_to_parent.angles = angles

    def set_joint_to_parent_translation(self, translation: ndarray):
        self.t_current_joint_to_parent.translation = translation

    def convert_basis(self,
                      t_old_to_new: Transformation,
                      t_new_to_old: Transformation = None,
                      scale: float = 1):
        self.t_initial_joint_to_parent.convert_basis(t_old_to_new,
                                                     t_new_to_old, scale)
        self.t_current_joint_to_parent.convert_basis(t_old_to_new,
                                                     t_new_to_old, scale)
        self.t_model_to_joint.convert_basis(t_old_to_new, t_new_to_old, scale)
        self.t_joint.convert_basis(t_old_to_new, t_new_to_old, scale)
        self.t_joint_to_world.convert_basis(t_old_to_new, t_new_to_old, scale)
        if self.axis is not None:
            self.axis = t_old_to_new.apply(self.axis, is_point=False)

    def find_joint_by_name(self, name: str):
        """Finds a joint in this joint's tree by its name."""
        if self.name == name:
            return self
        for child in self.children:
            joint = child.find_joint_by_name(name)
            if joint is not None:
                return joint
        return None

    def find_joint_by_index(self, index: int):
        if self.index == index:
            return self
        for child in self.children:
            joint = child.find_joint_by_index(index)
            if joint is not None:
                return joint

    def has_animation(self) -> bool:
        return self.animation.poses is not None and self.animation.timestamps is not None

    def __str__(self):
        return f'<Joint: index={self.index}, name=\'{self.name}\', n_children={len(self.children)}, len={len(self)}>'

    def __len__(self):
        n_joints = 1
        for child in self.children:
            n_joints += len(child)
        return n_joints

    def print_tree(self, level: int = 0):
        print('\t' * level, self)
        for child in self.children:
            child.print_tree(level + 1)

    def preorder_iterator(self):
        yield self
        for child in self.children:
            yield from child.preorder_iterator()
Beispiel #10
0
def read_skinning_data(skin_path: Path, skeleton_path: Path) -> dict:
    # Notes:
    # make sure that the joints that are bounded to vertices are first, and the indices are correct
    joints, dofs, face_parts = read_skeleton(skeleton_path)
    bone_names, weight_matrix = read_skin(skin_path)

    # build the joint tree
    # create the joints, and a mapping from name to joint
    name_to_joint = {}
    for joint_data in joints:
        t_joint_to_parent = Transformation()
        t_joint_to_parent.translation = joint_data.translation
        joint = Joint(name=joint_data.name,
                      t_joint_to_parent=t_joint_to_parent.matrix,
                      axis=joint_data.rotation_axis,
                      movement_type=joint_data.movement_type)
        name_to_joint[joint.name] = joint
    # add face parts as joints
    for face_part in face_parts:
        t_joint_to_parent = Transformation()
        t_joint_to_parent.translation = face_part.translation
        joint = Joint(name=face_part.name,
                      t_joint_to_parent=t_joint_to_parent.matrix)
        name_to_joint[joint.name] = joint

    # make the dof and face_part point at the correct joint
    for dof in dofs:
        dof.joint = name_to_joint[dof.joint]
    # create the the joint tree, and find the root of the tree
    root = None
    for joint_data in chain(joints, face_parts):
        joint = name_to_joint[joint_data.name]
        if joint_data.parent is None:
            joint.set_root()
            root = joint
        else:
            parent = name_to_joint[joint_data.parent]
            parent.children.append(joint)

    assert root is not None
    root.init_skeleton()

    # match the joints in the bones list to the last joint in the hierarchy that matches that joint
    bones_to_joints = {}
    name_extractor = re.compile(r'(.*)_[a-z]*')

    def find_deepest_match(joint: Joint):
        name = name_extractor.match(joint.name)
        if name:
            name = name.group(1)
            bones_to_joints[name] = joint
        for child in joint.children:
            find_deepest_match(child)

    find_deepest_match(root)

    assert set(bone_names).issubset(bones_to_joints.keys())

    # order the list with the first joints
    joint_list = []
    for i, bone_name in enumerate(bone_names):
        joint = bones_to_joints[bone_name]
        joint.index = i
        joint_list.append(joint)

    i = len(joint_list)
    n_bound_joints = i
    # then index all of the other joints
    for joint in name_to_joint.values():
        if joint.index is not None:
            continue
        joint.index = i
        i += 1
        joint_list.append(joint)

    n_joints = len(joint_list)

    return {
        'n_bound_joints': n_bound_joints,
        'n_total_joints': n_joints,
        'weight_matrix': weight_matrix,
        'joint_list': joint_list,
        'dofs': dofs,
        'root_joint': root,
    }