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}