blender_armature/
lib.rs

1//! Data structures and methods for dealing with armatures.
2//!
3//! @see https://docs.blender.org/manual/en/dev/modeling/armature/introduction.html - Armature Introduction
4
5#[macro_use]
6extern crate serde_derive;
7
8use std::collections::HashMap;
9
10use crate::serde::serialize_hashmap_deterministic;
11
12pub use self::action::*;
13pub use self::bone::*;
14pub use self::coordinate_system::*;
15pub use self::export::*;
16pub use self::interpolate::*;
17use std::borrow::Borrow;
18use std::hash::Hash;
19
20mod action;
21mod bone;
22mod convert;
23mod coordinate_system;
24mod export;
25mod interpolate;
26mod serde;
27
28#[cfg(test)]
29mod test_util;
30
31/// Something went wrong in the Blender child process that was trying to parse your armature data.
32#[derive(Debug, thiserror::Error)]
33pub enum BlenderError {
34    /// Errors in Blender are written to stderr. We capture the stderr from the `blender` child
35    /// process that we spawned when attempting to export armature from a `.blend` file.
36    #[error(
37        "There was an issue while exporting armature: Blender stderr output: {}",
38        _0
39    )]
40    Stderr(String),
41}
42
43/// All of the data about a Blender armature that we've exported from Blender.
44/// A BlenderArmature should have all of the data that you need to implement skeletal
45/// animation.
46///
47/// If you have other needs, such as a way to know the model space position of any bone at any
48/// time so that you can, say, render a baseball in on top of your hand bone.. Open an issue.
49/// (I plan to support this specific example in the future)
50#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
51#[cfg_attr(test, derive(Clone))]
52// TODO: BlenderArmature<T: Bone> for DQ and matrix
53pub struct BlenderArmature {
54    name: String,
55    #[serde(serialize_with = "serialize_hashmap_deterministic")]
56    joint_indices: HashMap<String, u8>,
57    bone_child_to_parent: HashMap<u8, u8>,
58    inverse_bind_poses: Vec<Bone>,
59    #[serde(serialize_with = "serialize_hashmap_deterministic")]
60    bone_space_actions: HashMap<String, Action>,
61    #[serde(serialize_with = "serialize_hashmap_deterministic")]
62    bone_groups: HashMap<String, Vec<u8>>,
63    #[serde(default)]
64    coordinate_system: CoordinateSystem,
65}
66
67impl BlenderArmature {
68    /// The name of the armature
69    pub fn name(&self) -> &String {
70        &self.name
71    }
72
73    /// Set the name of the armature.
74    ///
75    /// # Example
76    ///
77    /// ```
78    /// # use blender_armature::BlenderArmature;
79    /// let mut armature = BlenderArmature::default();
80    /// armature.set_name("Some Name".to_string());
81    ///
82    /// assert_eq!(armature.name(), "Some Name");
83    /// ```
84    pub fn set_name(&mut self, name: String) {
85        self.name = name;
86    }
87
88    /// Blender [bone groups]
89    ///
90    /// Maps bone group name to a vector of the bones indices that are in that bone group.
91    ///
92    /// ```rust
93    /// # use blender_armature::{Action, BlenderArmature, FrameOffset, SampleDesc, JointIndicesRef};
94    /// # use std::time::Duration;
95    ///
96    /// let armature = create_blender_armature();
97    ///
98    /// let joint_indices = armature.bone_groups().get("My bone group").unwrap();
99    ///
100    /// let sample_desc = SampleDesc {
101    ///     frame_offset: FrameOffset::new_with_elapsed_time_and_frames_per_second(
102    ///         Duration::from_secs(2),
103    ///         24,
104    ///     ),
105    ///     should_loop: false
106    /// };
107    ///
108    /// let _bones = armature.interpolate_bones(
109    ///     "SomeAction",
110    ///     JointIndicesRef::Some(joint_indices),
111    ///     sample_desc
112    /// );
113    ///
114    /// # fn create_blender_armature() -> BlenderArmature {
115    /// #   let mut  b = BlenderArmature::default();
116    /// #   b.insert_bone_space_action("SomeAction".to_string(), Action::new());
117    /// #   b.create_bone_group("My bone group".to_string(), vec![]);
118    /// #   b
119    /// # }
120    /// ```
121    ///
122    /// [bone groups]: https://docs.blender.org/manual/en/latest/animation/armatures/properties/bone_groups.html
123    pub fn bone_groups(&self) -> &HashMap<String, Vec<u8>> {
124        &self.bone_groups
125    }
126
127    /// Create a new bone group
128    pub fn create_bone_group(&mut self, name: String, joint_indices: Vec<u8>) {
129        self.bone_groups.insert(name, joint_indices);
130    }
131
132    /// Get a bone's index into the various Vec<Bone> data structures that hold bone data.
133    ///
134    /// # Example
135    ///
136    /// ```
137    /// use blender_armature::BlenderArmature;
138    /// let mut armature = BlenderArmature::default();
139    ///
140    /// armature.insert_joint_index("Spine".to_string(), 0);
141    ///
142    /// assert_eq!(armature.joint_indices().len(), 1);
143    /// ```
144    pub fn joint_indices(&self) -> &HashMap<String, u8> {
145        &self.joint_indices
146    }
147
148    /// Set a bone's index into the various Vec<Bone> data structures that hold bone data.
149    ///
150    /// # Example
151    ///
152    /// ```
153    /// use blender_armature::BlenderArmature;
154    /// let mut armature = BlenderArmature::default();
155    ///
156    /// armature.insert_joint_index("Spine".to_string(), 0);
157    /// armature.insert_joint_index("UpperArm".to_string(), 2);
158    ///
159    /// assert_eq!(armature.joint_indices().len(), 2);
160    /// ```
161    pub fn insert_joint_index(&mut self, joint_name: String, joint_idx: u8) {
162        self.joint_indices.insert(joint_name, joint_idx);
163    }
164
165    /// Every bone's inverse bind pose.
166    ///
167    /// # From Blender
168    /// When exporting from Blender these include the armature's world space matrix.
169    ///
170    /// So, effectively these are `(armature_world_space_matrix * bone_bind_pose).inverse()`
171    pub fn inverse_bind_poses(&self) -> &Vec<Bone> {
172        &self.inverse_bind_poses
173    }
174
175    /// Set the inverse bind poses.
176    pub fn set_inverse_bind_poses(&mut self, poses: Vec<Bone>) {
177        self.inverse_bind_poses = poses;
178    }
179
180    /// All of the actions defined on the armature, keyed by action name.
181    ///
182    /// FIXME: Rename to `bone_local_space_actions`
183    pub fn bone_space_actions(&self) -> &HashMap<String, Action> {
184        &self.bone_space_actions
185    }
186
187    /// Insert an action into the map of actions.
188    pub fn insert_bone_space_action(&mut self, name: String, action: Action) {
189        self.bone_space_actions.insert(name, action);
190    }
191
192    /// Remove an action from the map.
193    pub fn remove_bone_space_action<Q>(&mut self, name: &Q) -> Option<Action>
194    where
195        String: Borrow<Q>,
196        Q: Hash + Eq,
197    {
198        self.bone_space_actions.remove(name)
199    }
200
201    /// A map of a bone chil to its parent
202    ///
203    /// If a bone is not stored in this map then it does not have a parent.
204    pub fn bone_child_to_parent(&self) -> &HashMap<u8, u8> {
205        &self.bone_child_to_parent
206    }
207
208    /// # Example
209    ///
210    /// ```
211    /// # use blender_armature::BlenderArmature;
212    /// let mut armature = BlenderArmature::default();
213    ///
214    /// let child_idx = 4;
215    /// let parent_idx = 2;
216    ///
217    /// armature.insert_joint_index("UpperArm".to_string(), parent_idx);
218    /// armature.insert_joint_index("Lower Arm".to_string(), child_idx);
219    ///
220    /// armature.insert_child_to_parent(child_idx, parent_idx);
221    /// ```
222    pub fn insert_child_to_parent(&mut self, child: u8, parent: u8) {
223        self.bone_child_to_parent.insert(child, parent);
224    }
225}
226
227/// The pose bones at an individual keyframe time
228#[derive(Debug, Serialize, Deserialize, PartialEq)]
229#[cfg_attr(test, derive(Default, Clone))]
230pub struct Keyframe {
231    frame: u16,
232    bones: Vec<Bone>,
233}
234
235impl Keyframe {
236    #[allow(missing_docs)]
237    pub fn new(frame: u16, bones: Vec<Bone>) -> Self {
238        Keyframe { frame, bones }
239    }
240
241    /// All of the bones for this keyframe.
242    pub fn bones(&self) -> &Vec<Bone> {
243        &self.bones
244    }
245
246    /// All of the bones for this keyframe.
247    pub fn bones_mut(&mut self) -> &mut Vec<Bone> {
248        &mut self.bones
249    }
250
251    /// The frame number
252    pub fn frame(&self) -> u16 {
253        self.frame
254    }
255}
256
257// TODO: These methods can be abstracted into calling a method that takes a callback
258impl BlenderArmature {
259    /// Tranpose all of the bone matrices in our armature's action keyframes.
260    /// Blender uses row major matrices, but OpenGL uses column major matrices so you'll
261    /// usually want to transpose your matrices before using them.
262    pub fn transpose_actions(&mut self) {
263        for (_name, action) in self.bone_space_actions.iter_mut() {
264            for (_bone_idx, keyframes) in action.keyframes_mut().iter_mut() {
265                for bone in keyframes.iter_mut() {
266                    bone.bone_mut().transpose();
267                }
268            }
269        }
270
271        for bone in self.inverse_bind_poses.iter_mut() {
272            bone.transpose();
273        }
274    }
275}
276
277impl BlenderArmature {
278    /// Convert your action matrices into dual quaternions so that you can implement
279    /// dual quaternion linear blending.
280    pub fn matrices_to_dual_quats(&mut self) {
281        for (_, keyframes) in self.bone_space_actions.iter_mut() {
282            for (bone_idx, keyframes) in keyframes.keyframes_mut().iter_mut() {
283                for bone_keyframe in keyframes.iter_mut() {
284                    bone_keyframe
285                        .set_bone(BlenderArmature::matrix_to_dual_quat(&bone_keyframe.bone()));
286                }
287            }
288        }
289
290        for bone in self.inverse_bind_poses.iter_mut() {
291            *bone = BlenderArmature::matrix_to_dual_quat(bone);
292        }
293    }
294}
295
296impl Bone {
297    fn transpose(&mut self) {
298        match self {
299            Bone::Matrix(ref mut matrix) => {
300                matrix.transpose_mut();
301            }
302            Bone::DualQuat(_) => unimplemented!(),
303        };
304    }
305
306    // DELETE ME
307    fn multiply(&mut self, rhs: Bone) {
308        match self {
309            Bone::Matrix(lhs_matrix) => match rhs {
310                Bone::Matrix(rhs_matrix) => {
311                    //
312                    *self = Bone::Matrix(rhs_matrix * *lhs_matrix)
313                }
314                Bone::DualQuat(_) => {}
315            },
316            Bone::DualQuat(_) => {}
317        };
318    }
319}
320
321// DELETE ME
322impl BlenderArmature {
323    /// Iterate over all of the action bones and apply and multiply in the inverse bind pose.
324    ///
325    /// TODO: another function to apply bind shape matrix? Most armatures seem to export an identity
326    ///  bind shape matrix but that might not be the same for every armature.
327    ///
328    /// TODO: Do not mutate the matrices and instead just return the new values and let the caller
329    ///  handle caching them? Would mean less moving parts in our data structures and you always
330    ///  know exactly what you are getting. Right now you have no way actions of knowing whether or
331    ///  not actions have their bind poses pre-multiplied in.
332    pub fn apply_inverse_bind_poses(&mut self) {
333        for (_name, action) in self.bone_space_actions.iter_mut() {
334            for (bone_idx, keyframe) in action.keyframes_mut().iter_mut() {
335                for (index, bone) in keyframe.iter_mut().enumerate() {
336                    bone.bone_mut()
337                        .multiply(self.inverse_bind_poses[*bone_idx as usize]);
338                }
339            }
340        }
341    }
342}
343
344#[cfg(test)]
345mod tests {
346    use super::*;
347    use crate::interpolate::tests::dq_to_bone;
348    use crate::test_util::action_with_keyframes;
349    use nalgebra::Matrix4;
350
351    #[test]
352    fn convert_actions_to_dual_quats() {
353        let mut keyframes = vec![];
354        keyframes.push(BoneKeyframe::new(
355            1,
356            Bone::Matrix(Matrix4::from_column_slice(&[
357                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
358            ])),
359        ));
360
361        let mut start_armature = BlenderArmature {
362            bone_space_actions: action_with_keyframes(keyframes),
363            ..BlenderArmature::default()
364        };
365
366        start_armature.matrices_to_dual_quats();
367
368        let mut new_keyframes = vec![];
369        new_keyframes.push(BoneKeyframe::new(
370            1,
371            dq_to_bone([1.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]),
372        ));
373
374        let expected_armature = BlenderArmature {
375            bone_space_actions: action_with_keyframes(new_keyframes),
376            ..start_armature.clone()
377        };
378
379        assert_eq!(start_armature, expected_armature);
380    }
381
382    // TODO: Function to return these start_actions that we keep using
383    #[test]
384    fn transpose_actions() {
385        let keyframes = vec![BoneKeyframe::new(
386            1,
387            Bone::Matrix(Matrix4::from_column_slice(&[
388                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 5.0, 1.0,
389            ])),
390        )];
391
392        let mut start_armature = BlenderArmature {
393            bone_space_actions: action_with_keyframes(keyframes),
394            ..BlenderArmature::default()
395        };
396
397        start_armature.transpose_actions();
398
399        let new_keyframes = vec![BoneKeyframe::new(
400            1,
401            Bone::Matrix(Matrix4::from_column_slice(&[
402                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 5.0, 0.0, 0.0, 0.0, 1.0,
403            ])),
404        )];
405
406        let expected_armature = BlenderArmature {
407            bone_space_actions: action_with_keyframes(new_keyframes),
408            ..start_armature.clone()
409        };
410
411        assert_eq!(start_armature, expected_armature);
412    }
413}