blender_armature/
coordinate_system.rs

1use crate::{BlenderArmature, Bone};
2
3/// A coordinate system is used to make sense of coordinates.
4///
5/// Without knowing the coordinate system you wouldn't know if a value is meant for the
6/// Y axis, or Z axis.
7#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
8pub struct CoordinateSystem {
9    up: Axis,
10    hand: Hand,
11}
12
13impl CoordinateSystem {
14    #[allow(missing_docs)]
15    pub fn new(up: Axis, hand: Hand) -> Self {
16        CoordinateSystem { up, hand }
17    }
18}
19
20#[allow(missing_docs)]
21#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
22pub enum Axis {
23    X,
24    Y,
25    Z,
26}
27
28/// Represents the orientation of the coordinate system using the [right hand rule].
29///
30/// [right hand rule]: https://en.wikipedia.org/wiki/Right-hand_rule
31#[derive(Debug, Eq, PartialEq, Copy, Clone, Serialize, Deserialize)]
32pub enum Hand {
33    /// A right handed coordinate system
34    Right,
35    /// A left handed coordinate system
36    Left,
37}
38
39/// Blender's coordinate system is Z up right handed
40impl Default for CoordinateSystem {
41    fn default() -> Self {
42        CoordinateSystem {
43            up: Axis::Z,
44            hand: Hand::Right,
45        }
46    }
47}
48
49impl BlenderArmature {
50    /// Shift around the data in the armature to a new coordinate system.
51    ///
52    /// For example, if the armature was previously Z up and we're switching to Y up
53    ///  - the new +Y axis would be the old +Z axis
54    ///  - the new +Z axis would be the old -Y axis
55    pub fn change_coordinate_system(&mut self, system: CoordinateSystem) {
56        if self.coordinate_system == system {
57            return;
58        }
59
60        match (
61            (self.coordinate_system.hand, self.coordinate_system.up),
62            (system.hand, system.up),
63        ) {
64            ((Hand::Right, Axis::Z), (Hand::Right, Axis::Y)) => {
65                for bone in self.inverse_bind_poses.iter_mut() {
66                    *bone = dual_quat_z_up_right_to_y_up_right(*bone);
67                }
68
69                for (_action_name, action) in self.bone_space_actions.iter_mut() {
70                    for (bone_idx, keyframes) in action.keyframes_mut() {
71                        for bone_keyframe in keyframes.iter_mut() {
72                            let bone = bone_keyframe.bone();
73                            bone_keyframe.set_bone(dual_quat_z_up_right_to_y_up_right(bone));
74                        }
75                    }
76                }
77            }
78            _ => unimplemented!(),
79        }
80
81        self.coordinate_system = system;
82    }
83}
84
85fn dual_quat_z_up_right_to_y_up_right(bone: Bone) -> Bone {
86    match bone {
87        Bone::Matrix(_) => unimplemented!(),
88        Bone::DualQuat(mut dq) => {
89            let rot_y = dq.real.j;
90            let rot_z = dq.real.k;
91
92            dq.real.j = rot_z;
93            dq.real.k = -rot_y;
94
95            let trans_y = dq.dual.j;
96            let trans_z = dq.dual.k;
97
98            dq.dual.j = trans_z;
99            dq.dual.k = -trans_y;
100
101            Bone::DualQuat(dq)
102        }
103    }
104}
105
106#[cfg(test)]
107mod tests {
108    use super::*;
109    use crate::interpolate::tests::dq_to_bone;
110    use crate::test_util::{action_name, action_with_keyframes, BONE_IDX};
111    use crate::{Action, BlenderArmature, BoneKeyframe, Keyframe};
112    use std::collections::HashMap;
113
114    /// Convert from the default Z-up right handed coordinate system to a Y-up right handed
115    /// coordinate system.
116    #[test]
117    fn convert_dual_quaternions_z_up_right_to_y_up_right() {
118        let mut arm = BlenderArmature::default();
119
120        let expected_bone = dq_to_bone([0., 1., 3., -2., 4., 5., 7., -6.]);
121
122        let bone = dq_to_bone([0., 1., 2., 3., 4., 5., 6., 7.]);
123        arm.inverse_bind_poses = vec![bone.clone()];
124
125        let keyframes = vec![BoneKeyframe::new(0, bone)];
126
127        arm.bone_space_actions = action_with_keyframes(keyframes);
128
129        arm.change_coordinate_system(CoordinateSystem::new(Axis::Y, Hand::Right));
130
131        assert_eq!(&arm.inverse_bind_poses[0], &expected_bone);
132        assert_eq!(
133            &arm.bone_space_actions[&action_name()].bone_keyframes()[&BONE_IDX][0].bone(),
134            &expected_bone
135        );
136    }
137
138    /// If the armature is already using the coordinate system that we want to change to
139    /// then nothing should change
140    #[test]
141    fn does_not_change_if_coordinate_system_same() {
142        let mut arm = BlenderArmature::default();
143        arm.change_coordinate_system(CoordinateSystem::new(Axis::Y, Hand::Right));
144
145        let expected_bone = dq_to_bone([0., 1., 2., 3., 4., 5., 6., 7.]);
146
147        let bone = dq_to_bone([0., 1., 2., 3., 4., 5., 6., 7.]);
148        arm.inverse_bind_poses = vec![bone];
149
150        let keyframes = vec![BoneKeyframe::new(0, bone)];
151
152        arm.bone_space_actions = action_with_keyframes(keyframes);
153
154        arm.change_coordinate_system(CoordinateSystem::new(Axis::Y, Hand::Right));
155
156        assert_eq!(&arm.inverse_bind_poses[0], &expected_bone);
157        assert_eq!(
158            &arm.bone_space_actions[&action_name()].bone_keyframes()[&BONE_IDX][0].bone(),
159            &expected_bone
160        );
161    }
162}