1use std::collections::HashMap;
11use glam::{Mat4, Quat, Vec3};
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
17pub struct BoneId(pub u32);
18
19impl BoneId {
20 pub const ROOT: BoneId = BoneId(0);
22
23 pub fn index(self) -> usize {
24 self.0 as usize
25 }
26}
27
28#[derive(Debug, Clone, Copy, PartialEq)]
32pub struct Transform3D {
33 pub translation: Vec3,
34 pub rotation: Quat,
35 pub scale: Vec3,
36}
37
38impl Transform3D {
39 pub fn identity() -> Self {
40 Self {
41 translation: Vec3::ZERO,
42 rotation: Quat::IDENTITY,
43 scale: Vec3::ONE,
44 }
45 }
46
47 pub fn new(translation: Vec3, rotation: Quat, scale: Vec3) -> Self {
48 Self { translation, rotation, scale }
49 }
50
51 pub fn to_mat4(self) -> Mat4 {
53 Mat4::from_scale_rotation_translation(self.scale, self.rotation, self.translation)
54 }
55
56 pub fn lerp(self, other: Self, t: f32) -> Self {
58 Self {
59 translation: self.translation.lerp(other.translation, t),
60 rotation: self.rotation.slerp(other.rotation, t),
61 scale: self.scale.lerp(other.scale, t),
62 }
63 }
64
65 pub fn add_weighted(self, additive: Self, weight: f32) -> Self {
67 let ref_identity = Transform3D::identity();
68 let delta_trans = additive.translation - ref_identity.translation;
70 let delta_scale = additive.scale - ref_identity.scale;
71 let delta_rot = Quat::IDENTITY.slerp(additive.rotation, weight);
73 Self {
74 translation: self.translation + delta_trans * weight,
75 rotation: (self.rotation * delta_rot).normalize(),
76 scale: self.scale + delta_scale * weight,
77 }
78 }
79}
80
81impl Default for Transform3D {
82 fn default() -> Self { Self::identity() }
83}
84
85#[derive(Debug, Clone)]
89pub struct Bone {
90 pub id: BoneId,
91 pub name: String,
92 pub parent: Option<BoneId>,
93 pub local_bind_pose: Transform3D,
95 pub inv_bind_matrix: Mat4,
97 pub children: Vec<BoneId>,
98}
99
100impl Bone {
101 pub fn new(id: BoneId, name: impl Into<String>, parent: Option<BoneId>, local_bind_pose: Transform3D) -> Self {
102 Self {
103 id,
104 name: name.into(),
105 parent,
106 local_bind_pose,
107 inv_bind_matrix: Mat4::IDENTITY,
108 children: Vec::new(),
109 }
110 }
111}
112
113#[derive(Debug, Clone)]
121pub struct Skeleton {
122 pub bones: Vec<Bone>,
123 pub name_index: HashMap<String, BoneId>,
124}
125
126impl Skeleton {
127 pub fn new() -> Self {
129 Self {
130 bones: Vec::new(),
131 name_index: HashMap::new(),
132 }
133 }
134
135 pub fn len(&self) -> usize { self.bones.len() }
137 pub fn is_empty(&self) -> bool { self.bones.is_empty() }
138
139 pub fn bone_by_name(&self, name: &str) -> Option<&Bone> {
141 let id = self.name_index.get(name)?;
142 self.bones.get(id.index())
143 }
144
145 pub fn bone(&self, id: BoneId) -> Option<&Bone> {
147 self.bones.get(id.index())
148 }
149
150 pub fn bone_mut(&mut self, id: BoneId) -> Option<&mut Bone> {
152 self.bones.get_mut(id.index())
153 }
154
155 pub fn root_id(&self) -> Option<BoneId> {
157 self.bones.first().map(|b| b.id)
158 }
159
160 pub fn compute_bind_world_matrices(&self) -> Vec<Mat4> {
163 let n = self.bones.len();
164 let mut world = vec![Mat4::IDENTITY; n];
165 for bone in &self.bones {
166 let local = bone.local_bind_pose.to_mat4();
167 world[bone.id.index()] = match bone.parent {
168 None => local,
169 Some(parent) => world[parent.index()] * local,
170 };
171 }
172 world
173 }
174
175 pub fn recompute_inv_bind_matrices(&mut self) {
177 let world = self.compute_bind_world_matrices();
178 for bone in &mut self.bones {
179 bone.inv_bind_matrix = world[bone.id.index()];
180 }
181 }
182
183 pub fn rest_pose(&self) -> Pose {
185 let mut pose = Pose::new(self.bones.len());
186 for bone in &self.bones {
187 pose.local_transforms[bone.id.index()] = bone.local_bind_pose;
188 }
189 pose
190 }
191
192 pub fn topological_order(&self) -> Vec<BoneId> {
194 self.bones.iter().map(|b| b.id).collect()
195 }
196
197 pub fn children_of(&self, id: BoneId) -> &[BoneId] {
199 self.bones.get(id.index()).map(|b| b.children.as_slice()).unwrap_or(&[])
200 }
201}
202
203impl Default for Skeleton {
204 fn default() -> Self { Self::new() }
205}
206
207#[derive(Debug, Clone)]
213pub struct Pose {
214 pub local_transforms: Vec<Transform3D>,
215}
216
217impl Pose {
218 pub fn new(bone_count: usize) -> Self {
220 Self {
221 local_transforms: vec![Transform3D::identity(); bone_count],
222 }
223 }
224
225 pub fn len(&self) -> usize { self.local_transforms.len() }
227 pub fn is_empty(&self) -> bool { self.local_transforms.is_empty() }
228
229 pub fn get(&self, id: BoneId) -> Option<Transform3D> {
231 self.local_transforms.get(id.index()).copied()
232 }
233
234 pub fn set(&mut self, id: BoneId, xform: Transform3D) {
236 if let Some(slot) = self.local_transforms.get_mut(id.index()) {
237 *slot = xform;
238 }
239 }
240
241 pub fn blend(&self, other: &Pose, t: f32) -> Pose {
245 let len = self.local_transforms.len().min(other.local_transforms.len());
246 let mut result = Pose::new(len);
247 for i in 0..len {
248 result.local_transforms[i] = self.local_transforms[i].lerp(other.local_transforms[i], t);
249 }
250 result
251 }
252
253 pub fn add_pose(&self, additive: &Pose, weight: f32) -> Pose {
257 let len = self.local_transforms.len().min(additive.local_transforms.len());
258 let mut result = self.clone();
259 for i in 0..len {
260 result.local_transforms[i] = self.local_transforms[i]
261 .add_weighted(additive.local_transforms[i], weight);
262 }
263 result
264 }
265
266 pub fn apply_mask(&self, other: &Pose, mask: &BoneMask) -> Pose {
271 let len = self.local_transforms.len().min(other.local_transforms.len());
272 let mut result = self.clone();
273 for i in 0..len {
274 let w = mask.weights.get(i).copied().unwrap_or(0.0);
275 result.local_transforms[i] = self.local_transforms[i].lerp(other.local_transforms[i], w);
276 }
277 result
278 }
279
280 pub fn override_with_mask(&self, other: &Pose, mask: &BoneMask, threshold: f32) -> Pose {
282 let len = self.local_transforms.len().min(other.local_transforms.len());
283 let mut result = self.clone();
284 for i in 0..len {
285 let w = mask.weights.get(i).copied().unwrap_or(0.0);
286 if w > threshold {
287 result.local_transforms[i] = other.local_transforms[i];
288 }
289 }
290 result
291 }
292}
293
294#[derive(Debug, Clone)]
301pub struct BoneMask {
302 pub weights: Vec<f32>,
304}
305
306impl BoneMask {
307 pub fn uniform(bone_count: usize, default_weight: f32) -> Self {
309 Self { weights: vec![default_weight.clamp(0.0, 1.0); bone_count] }
310 }
311
312 pub fn zero(bone_count: usize) -> Self {
314 Self::uniform(bone_count, 0.0)
315 }
316
317 pub fn full_body(bone_count: usize) -> Self {
319 Self::uniform(bone_count, 1.0)
320 }
321
322 pub fn upper_body(skeleton: &Skeleton) -> Self {
327 let mut mask = Self::zero(skeleton.len());
328 let upper_keywords = [
329 "spine", "chest", "neck", "head", "shoulder",
330 "arm", "hand", "finger", "thumb", "index", "middle",
331 "ring", "pinky", "clavicle", "elbow", "wrist",
332 ];
333 for bone in &skeleton.bones {
334 let name_lower = bone.name.to_lowercase();
335 let is_upper = upper_keywords.iter().any(|kw| name_lower.contains(kw));
336 if is_upper {
337 if let Some(w) = mask.weights.get_mut(bone.id.index()) {
338 *w = 1.0;
339 }
340 }
341 }
342 mask
343 }
344
345 pub fn lower_body(skeleton: &Skeleton) -> Self {
350 let mut mask = Self::zero(skeleton.len());
351 let lower_keywords = [
352 "hip", "pelvis", "leg", "thigh", "knee",
353 "shin", "calf", "ankle", "foot", "toe",
354 ];
355 for bone in &skeleton.bones {
356 let name_lower = bone.name.to_lowercase();
357 let is_lower = lower_keywords.iter().any(|kw| name_lower.contains(kw));
358 if is_lower {
359 if let Some(w) = mask.weights.get_mut(bone.id.index()) {
360 *w = 1.0;
361 }
362 }
363 }
364 mask
365 }
366
367 pub fn set_weight(&mut self, id: BoneId, weight: f32) {
369 if let Some(w) = self.weights.get_mut(id.index()) {
370 *w = weight.clamp(0.0, 1.0);
371 }
372 }
373
374 pub fn get_weight(&self, id: BoneId) -> f32 {
376 self.weights.get(id.index()).copied().unwrap_or(0.0)
377 }
378
379 pub fn scale(&self, factor: f32) -> Self {
381 Self {
382 weights: self.weights.iter().map(|&w| (w * factor).clamp(0.0, 1.0)).collect(),
383 }
384 }
385
386 pub fn union(&self, other: &BoneMask) -> Self {
388 let len = self.weights.len().max(other.weights.len());
389 let mut weights = vec![0.0f32; len];
390 for i in 0..len {
391 let a = self.weights.get(i).copied().unwrap_or(0.0);
392 let b = other.weights.get(i).copied().unwrap_or(0.0);
393 weights[i] = a.max(b);
394 }
395 Self { weights }
396 }
397
398 pub fn intersection(&self, other: &BoneMask) -> Self {
400 let len = self.weights.len().min(other.weights.len());
401 let weights = (0..len)
402 .map(|i| {
403 let a = self.weights.get(i).copied().unwrap_or(0.0);
404 let b = other.weights.get(i).copied().unwrap_or(0.0);
405 a.min(b)
406 })
407 .collect();
408 Self { weights }
409 }
410
411 pub fn invert(&self) -> Self {
413 Self {
414 weights: self.weights.iter().map(|&w| 1.0 - w).collect(),
415 }
416 }
417
418 pub fn from_pairs(bone_count: usize, pairs: &[(BoneId, f32)]) -> Self {
420 let mut mask = Self::zero(bone_count);
421 for &(id, weight) in pairs {
422 mask.set_weight(id, weight);
423 }
424 mask
425 }
426
427 pub fn from_bone_subtree(skeleton: &Skeleton, root_bones: &[BoneId], weight: f32) -> Self {
429 let mut mask = Self::zero(skeleton.len());
430 let mut stack: Vec<BoneId> = root_bones.to_vec();
431 while let Some(id) = stack.pop() {
432 mask.set_weight(id, weight);
433 for &child in skeleton.children_of(id) {
434 stack.push(child);
435 }
436 }
437 mask
438 }
439}
440
441#[derive(Debug, Clone)]
448pub struct SkinningMatrices {
449 pub matrices: Vec<Mat4>,
450}
451
452impl SkinningMatrices {
453 pub fn compute(skeleton: &Skeleton, pose: &Pose) -> Self {
458 let n = skeleton.len();
459 let mut world = vec![Mat4::IDENTITY; n];
460
461 for bone in &skeleton.bones {
463 let idx = bone.id.index();
464 let local_xform = pose.local_transforms.get(idx)
465 .copied()
466 .unwrap_or_else(Transform3D::identity);
467 let local_mat = local_xform.to_mat4();
468 world[idx] = match bone.parent {
469 None => local_mat,
470 Some(parent) => world[parent.index()] * local_mat,
471 };
472 }
473
474 let matrices = skeleton.bones.iter().map(|bone| {
476 bone.inv_bind_matrix * world[bone.id.index()]
477 }).collect();
478
479 Self { matrices }
480 }
481
482 pub fn len(&self) -> usize { self.matrices.len() }
484 pub fn is_empty(&self) -> bool { self.matrices.is_empty() }
485
486 pub fn get(&self, id: BoneId) -> Option<Mat4> {
488 self.matrices.get(id.index()).copied()
489 }
490
491 pub fn as_flat_slice(&self) -> Vec<f32> {
494 self.matrices.iter().flat_map(|m| m.to_cols_array()).collect()
495 }
496
497 pub fn as_arrays(&self) -> Vec<[f32; 16]> {
499 self.matrices.iter().map(|m| m.to_cols_array()).collect()
500 }
501}
502
503#[derive(Debug, Default)]
515pub struct SkeletonBuilder {
516 pending: Vec<(String, Option<String>, Transform3D)>,
518}
519
520impl SkeletonBuilder {
521 pub fn new() -> Self { Self::default() }
522
523 pub fn add_bone(
527 mut self,
528 name: impl Into<String>,
529 parent: Option<&str>,
530 local_bind_pose: Transform3D,
531 ) -> Self {
532 self.pending.push((name.into(), parent.map(str::to_owned), local_bind_pose));
533 self
534 }
535
536 pub fn add_bone_components(
538 self,
539 name: impl Into<String>,
540 parent: Option<&str>,
541 translation: Vec3,
542 rotation: Quat,
543 scale: Vec3,
544 ) -> Self {
545 self.add_bone(name, parent, Transform3D::new(translation, rotation, scale))
546 }
547
548 pub fn build(self) -> Skeleton {
550 let mut skeleton = Skeleton::new();
551
552 for (idx, (name, parent_name, local_bind_pose)) in self.pending.into_iter().enumerate() {
553 let id = BoneId(idx as u32);
554 let parent_id = parent_name.as_deref().and_then(|pn| skeleton.name_index.get(pn).copied());
555
556 let bone = Bone::new(id, name.clone(), parent_id, local_bind_pose);
557 skeleton.name_index.insert(name, id);
558
559 if let Some(pid) = parent_id {
561 if let Some(parent_bone) = skeleton.bones.get_mut(pid.index()) {
562 parent_bone.children.push(id);
563 }
564 }
565
566 skeleton.bones.push(bone);
567 }
568
569 skeleton.recompute_inv_bind_matrices();
570 skeleton
571 }
572}
573
574impl Skeleton {
577 pub fn standard_humanoid() -> Self {
579 SkeletonBuilder::new()
580 .add_bone("root", None, Transform3D::identity())
582 .add_bone("pelvis", Some("root"), Transform3D::new(Vec3::new(0.0, 1.0, 0.0), Quat::IDENTITY, Vec3::ONE))
583 .add_bone("spine_01", Some("pelvis"), Transform3D::new(Vec3::new(0.0, 0.15, 0.0), Quat::IDENTITY, Vec3::ONE))
585 .add_bone("spine_02", Some("spine_01"), Transform3D::new(Vec3::new(0.0, 0.15, 0.0), Quat::IDENTITY, Vec3::ONE))
586 .add_bone("spine_03", Some("spine_02"), Transform3D::new(Vec3::new(0.0, 0.15, 0.0), Quat::IDENTITY, Vec3::ONE))
587 .add_bone("neck", Some("spine_03"), Transform3D::new(Vec3::new(0.0, 0.10, 0.0), Quat::IDENTITY, Vec3::ONE))
589 .add_bone("head", Some("neck"), Transform3D::new(Vec3::new(0.0, 0.10, 0.0), Quat::IDENTITY, Vec3::ONE))
590 .add_bone("clavicle_l", Some("spine_03"), Transform3D::new(Vec3::new(-0.10, 0.05, 0.0), Quat::IDENTITY, Vec3::ONE))
592 .add_bone("upperarm_l", Some("clavicle_l"),Transform3D::new(Vec3::new(-0.15, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
593 .add_bone("lowerarm_l", Some("upperarm_l"),Transform3D::new(Vec3::new(-0.28, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
594 .add_bone("hand_l", Some("lowerarm_l"),Transform3D::new(Vec3::new(-0.25, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
595 .add_bone("clavicle_r", Some("spine_03"), Transform3D::new(Vec3::new( 0.10, 0.05, 0.0), Quat::IDENTITY, Vec3::ONE))
597 .add_bone("upperarm_r", Some("clavicle_r"),Transform3D::new(Vec3::new( 0.15, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
598 .add_bone("lowerarm_r", Some("upperarm_r"),Transform3D::new(Vec3::new( 0.28, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
599 .add_bone("hand_r", Some("lowerarm_r"),Transform3D::new(Vec3::new( 0.25, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
600 .add_bone("thigh_l", Some("pelvis"), Transform3D::new(Vec3::new(-0.10, -0.05, 0.0),Quat::IDENTITY, Vec3::ONE))
602 .add_bone("calf_l", Some("thigh_l"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
603 .add_bone("foot_l", Some("calf_l"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
604 .add_bone("toe_l", Some("foot_l"), Transform3D::new(Vec3::new(0.0, 0.0, 0.14), Quat::IDENTITY, Vec3::ONE))
605 .add_bone("thigh_r", Some("pelvis"), Transform3D::new(Vec3::new( 0.10, -0.05, 0.0),Quat::IDENTITY, Vec3::ONE))
607 .add_bone("calf_r", Some("thigh_r"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
608 .add_bone("foot_r", Some("calf_r"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
609 .add_bone("toe_r", Some("foot_r"), Transform3D::new(Vec3::new(0.0, 0.0, 0.14), Quat::IDENTITY, Vec3::ONE))
610 .build()
611 }
612}
613
614#[cfg(test)]
617mod tests {
618 use super::*;
619
620 fn simple_skeleton() -> Skeleton {
621 SkeletonBuilder::new()
622 .add_bone("root", None, Transform3D::identity())
623 .add_bone("spine", Some("root"), Transform3D::new(Vec3::new(0.0, 1.0, 0.0), Quat::IDENTITY, Vec3::ONE))
624 .add_bone("head", Some("spine"), Transform3D::new(Vec3::new(0.0, 0.5, 0.0), Quat::IDENTITY, Vec3::ONE))
625 .add_bone("arm_l", Some("spine"), Transform3D::new(Vec3::new(-0.3, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
626 .add_bone("arm_r", Some("spine"), Transform3D::new(Vec3::new( 0.3, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
627 .build()
628 }
629
630 #[test]
631 fn test_bone_id_index() {
632 assert_eq!(BoneId(3).index(), 3);
633 assert_eq!(BoneId::ROOT.index(), 0);
634 }
635
636 #[test]
637 fn test_skeleton_builder_creates_bones() {
638 let skeleton = simple_skeleton();
639 assert_eq!(skeleton.len(), 5);
640 assert!(skeleton.bone_by_name("root").is_some());
641 assert!(skeleton.bone_by_name("spine").is_some());
642 assert!(skeleton.bone_by_name("head").is_some());
643 }
644
645 #[test]
646 fn test_skeleton_name_index() {
647 let skeleton = simple_skeleton();
648 let id = skeleton.name_index["spine"];
649 assert_eq!(id.index(), 1);
650 }
651
652 #[test]
653 fn test_skeleton_parent_child_links() {
654 let skeleton = simple_skeleton();
655 let spine = skeleton.bone_by_name("spine").unwrap();
656 assert_eq!(spine.parent, Some(BoneId(0)));
657 assert!(spine.children.contains(&BoneId(2))); }
659
660 #[test]
661 fn test_skeleton_inv_bind_matrices_not_zero() {
662 let skeleton = simple_skeleton();
663 let root = &skeleton.bones[0];
665 assert!((root.inv_bind_matrix - Mat4::IDENTITY).abs_diff_eq(Mat4::ZERO, 1e-5));
667 let spine = &skeleton.bones[1];
669 let diff = spine.inv_bind_matrix - Mat4::IDENTITY;
670 let max_elem = [diff.x_axis, diff.y_axis, diff.z_axis, diff.w_axis]
671 .iter()
672 .flat_map(|col| [col.x, col.y, col.z, col.w])
673 .fold(f32::NEG_INFINITY, f32::max);
674 assert!(max_elem > 0.01);
675 }
676
677 #[test]
678 fn test_rest_pose_matches_bind() {
679 let skeleton = simple_skeleton();
680 let pose = skeleton.rest_pose();
681 assert_eq!(pose.len(), skeleton.len());
682 for (i, bone) in skeleton.bones.iter().enumerate() {
683 assert_eq!(pose.local_transforms[i].translation, bone.local_bind_pose.translation);
684 }
685 }
686
687 #[test]
688 fn test_pose_blend_halfway() {
689 let n = 3;
690 let mut a = Pose::new(n);
691 let mut b = Pose::new(n);
692 a.local_transforms[0].translation = Vec3::ZERO;
693 b.local_transforms[0].translation = Vec3::new(2.0, 0.0, 0.0);
694 let blended = a.blend(&b, 0.5);
695 assert!((blended.local_transforms[0].translation.x - 1.0).abs() < 1e-5);
696 }
697
698 #[test]
699 fn test_pose_blend_extremes() {
700 let n = 2;
701 let mut a = Pose::new(n);
702 let mut b = Pose::new(n);
703 a.local_transforms[0].translation = Vec3::new(1.0, 0.0, 0.0);
704 b.local_transforms[0].translation = Vec3::new(3.0, 0.0, 0.0);
705 let at_zero = a.blend(&b, 0.0);
706 let at_one = a.blend(&b, 1.0);
707 assert!((at_zero.local_transforms[0].translation.x - 1.0).abs() < 1e-5);
708 assert!((at_one.local_transforms[0].translation.x - 3.0).abs() < 1e-5);
709 }
710
711 #[test]
712 fn test_pose_add_pose() {
713 let n = 2;
714 let mut base = Pose::new(n);
715 let mut additive = Pose::new(n);
716 base.local_transforms[0].translation = Vec3::new(1.0, 0.0, 0.0);
717 additive.local_transforms[0].translation = Vec3::new(0.5, 0.0, 0.0);
718 let result = base.add_pose(&additive, 1.0);
719 assert!(result.local_transforms[0].translation.x > 1.0);
721 }
722
723 #[test]
724 fn test_bone_mask_full_body() {
725 let skeleton = simple_skeleton();
726 let mask = BoneMask::full_body(skeleton.len());
727 for &w in &mask.weights {
728 assert!((w - 1.0).abs() < 1e-6);
729 }
730 }
731
732 #[test]
733 fn test_bone_mask_zero() {
734 let skeleton = simple_skeleton();
735 let mask = BoneMask::zero(skeleton.len());
736 for &w in &mask.weights {
737 assert!(w.abs() < 1e-6);
738 }
739 }
740
741 #[test]
742 fn test_skinning_matrices_identity_pose() {
743 let skeleton = simple_skeleton();
744 let pose = skeleton.rest_pose();
745 let skinning = SkinningMatrices::compute(&skeleton, &pose);
746 assert_eq!(skinning.len(), skeleton.len());
747 for m in &skinning.matrices {
750 assert!(m.abs_diff_eq(Mat4::IDENTITY, 1e-4),
752 "Expected near-identity skinning matrix for rest pose, got {:?}", m);
753 }
754 }
755
756 #[test]
757 fn test_skinning_flat_slice_length() {
758 let skeleton = simple_skeleton();
759 let pose = skeleton.rest_pose();
760 let skinning = SkinningMatrices::compute(&skeleton, &pose);
761 let flat = skinning.as_flat_slice();
762 assert_eq!(flat.len(), skeleton.len() * 16);
763 }
764
765 #[test]
766 fn test_standard_humanoid_bone_count() {
767 let skeleton = Skeleton::standard_humanoid();
768 assert_eq!(skeleton.len(), 23);
769 assert!(skeleton.bone_by_name("head").is_some());
770 assert!(skeleton.bone_by_name("hand_l").is_some());
771 assert!(skeleton.bone_by_name("foot_r").is_some());
772 }
773
774 #[test]
775 fn test_upper_body_mask_has_arm_bones() {
776 let skeleton = Skeleton::standard_humanoid();
777 let mask = BoneMask::upper_body(&skeleton);
778 let upperarm_l_id = skeleton.name_index["upperarm_l"];
779 assert!((mask.get_weight(upperarm_l_id) - 1.0).abs() < 1e-6);
780 }
781
782 #[test]
783 fn test_lower_body_mask_has_leg_bones() {
784 let skeleton = Skeleton::standard_humanoid();
785 let mask = BoneMask::lower_body(&skeleton);
786 let thigh_l_id = skeleton.name_index["thigh_l"];
787 assert!((mask.get_weight(thigh_l_id) - 1.0).abs() < 1e-6);
788 }
789
790 #[test]
791 fn test_mask_subtree() {
792 let skeleton = simple_skeleton();
793 let spine_id = skeleton.name_index["spine"];
794 let mask = BoneMask::from_bone_subtree(&skeleton, &[spine_id], 1.0);
795 assert!((mask.get_weight(spine_id) - 1.0).abs() < 1e-6);
797 let head_id = skeleton.name_index["head"];
798 assert!((mask.get_weight(head_id) - 1.0).abs() < 1e-6);
799 }
800}