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 {
181 bone.inv_bind_matrix = world[bone.id.index()].inverse();
182 }
183 }
184
185 pub fn rest_pose(&self) -> Pose {
187 let mut pose = Pose::new(self.bones.len());
188 for bone in &self.bones {
189 pose.local_transforms[bone.id.index()] = bone.local_bind_pose;
190 }
191 pose
192 }
193
194 pub fn topological_order(&self) -> Vec<BoneId> {
196 self.bones.iter().map(|b| b.id).collect()
197 }
198
199 pub fn children_of(&self, id: BoneId) -> &[BoneId] {
201 self.bones.get(id.index()).map(|b| b.children.as_slice()).unwrap_or(&[])
202 }
203}
204
205impl Default for Skeleton {
206 fn default() -> Self { Self::new() }
207}
208
209#[derive(Debug, Clone)]
215pub struct Pose {
216 pub local_transforms: Vec<Transform3D>,
217}
218
219impl Pose {
220 pub fn new(bone_count: usize) -> Self {
222 Self {
223 local_transforms: vec![Transform3D::identity(); bone_count],
224 }
225 }
226
227 pub fn len(&self) -> usize { self.local_transforms.len() }
229 pub fn is_empty(&self) -> bool { self.local_transforms.is_empty() }
230
231 pub fn get(&self, id: BoneId) -> Option<Transform3D> {
233 self.local_transforms.get(id.index()).copied()
234 }
235
236 pub fn set(&mut self, id: BoneId, xform: Transform3D) {
238 if let Some(slot) = self.local_transforms.get_mut(id.index()) {
239 *slot = xform;
240 }
241 }
242
243 pub fn blend(&self, other: &Pose, t: f32) -> Pose {
247 let len = self.local_transforms.len().min(other.local_transforms.len());
248 let mut result = Pose::new(len);
249 for i in 0..len {
250 result.local_transforms[i] = self.local_transforms[i].lerp(other.local_transforms[i], t);
251 }
252 result
253 }
254
255 pub fn add_pose(&self, additive: &Pose, weight: f32) -> Pose {
259 let len = self.local_transforms.len().min(additive.local_transforms.len());
260 let mut result = self.clone();
261 for i in 0..len {
262 result.local_transforms[i] = self.local_transforms[i]
263 .add_weighted(additive.local_transforms[i], weight);
264 }
265 result
266 }
267
268 pub fn apply_mask(&self, other: &Pose, mask: &BoneMask) -> Pose {
273 let len = self.local_transforms.len().min(other.local_transforms.len());
274 let mut result = self.clone();
275 for i in 0..len {
276 let w = mask.weights.get(i).copied().unwrap_or(0.0);
277 result.local_transforms[i] = self.local_transforms[i].lerp(other.local_transforms[i], w);
278 }
279 result
280 }
281
282 pub fn override_with_mask(&self, other: &Pose, mask: &BoneMask, threshold: f32) -> Pose {
284 let len = self.local_transforms.len().min(other.local_transforms.len());
285 let mut result = self.clone();
286 for i in 0..len {
287 let w = mask.weights.get(i).copied().unwrap_or(0.0);
288 if w > threshold {
289 result.local_transforms[i] = other.local_transforms[i];
290 }
291 }
292 result
293 }
294}
295
296#[derive(Debug, Clone)]
303pub struct BoneMask {
304 pub weights: Vec<f32>,
306}
307
308impl BoneMask {
309 pub fn uniform(bone_count: usize, default_weight: f32) -> Self {
311 Self { weights: vec![default_weight.clamp(0.0, 1.0); bone_count] }
312 }
313
314 pub fn zero(bone_count: usize) -> Self {
316 Self::uniform(bone_count, 0.0)
317 }
318
319 pub fn full_body(bone_count: usize) -> Self {
321 Self::uniform(bone_count, 1.0)
322 }
323
324 pub fn upper_body(skeleton: &Skeleton) -> Self {
329 let mut mask = Self::zero(skeleton.len());
330 let upper_keywords = [
331 "spine", "chest", "neck", "head", "shoulder",
332 "arm", "hand", "finger", "thumb", "index", "middle",
333 "ring", "pinky", "clavicle", "elbow", "wrist",
334 ];
335 for bone in &skeleton.bones {
336 let name_lower = bone.name.to_lowercase();
337 let is_upper = upper_keywords.iter().any(|kw| name_lower.contains(kw));
338 if is_upper {
339 if let Some(w) = mask.weights.get_mut(bone.id.index()) {
340 *w = 1.0;
341 }
342 }
343 }
344 mask
345 }
346
347 pub fn lower_body(skeleton: &Skeleton) -> Self {
352 let mut mask = Self::zero(skeleton.len());
353 let lower_keywords = [
354 "hip", "pelvis", "leg", "thigh", "knee",
355 "shin", "calf", "ankle", "foot", "toe",
356 ];
357 for bone in &skeleton.bones {
358 let name_lower = bone.name.to_lowercase();
359 let is_lower = lower_keywords.iter().any(|kw| name_lower.contains(kw));
360 if is_lower {
361 if let Some(w) = mask.weights.get_mut(bone.id.index()) {
362 *w = 1.0;
363 }
364 }
365 }
366 mask
367 }
368
369 pub fn set_weight(&mut self, id: BoneId, weight: f32) {
371 if let Some(w) = self.weights.get_mut(id.index()) {
372 *w = weight.clamp(0.0, 1.0);
373 }
374 }
375
376 pub fn get_weight(&self, id: BoneId) -> f32 {
378 self.weights.get(id.index()).copied().unwrap_or(0.0)
379 }
380
381 pub fn scale(&self, factor: f32) -> Self {
383 Self {
384 weights: self.weights.iter().map(|&w| (w * factor).clamp(0.0, 1.0)).collect(),
385 }
386 }
387
388 pub fn union(&self, other: &BoneMask) -> Self {
390 let len = self.weights.len().max(other.weights.len());
391 let mut weights = vec![0.0f32; len];
392 for i in 0..len {
393 let a = self.weights.get(i).copied().unwrap_or(0.0);
394 let b = other.weights.get(i).copied().unwrap_or(0.0);
395 weights[i] = a.max(b);
396 }
397 Self { weights }
398 }
399
400 pub fn intersection(&self, other: &BoneMask) -> Self {
402 let len = self.weights.len().min(other.weights.len());
403 let weights = (0..len)
404 .map(|i| {
405 let a = self.weights.get(i).copied().unwrap_or(0.0);
406 let b = other.weights.get(i).copied().unwrap_or(0.0);
407 a.min(b)
408 })
409 .collect();
410 Self { weights }
411 }
412
413 pub fn invert(&self) -> Self {
415 Self {
416 weights: self.weights.iter().map(|&w| 1.0 - w).collect(),
417 }
418 }
419
420 pub fn from_pairs(bone_count: usize, pairs: &[(BoneId, f32)]) -> Self {
422 let mut mask = Self::zero(bone_count);
423 for &(id, weight) in pairs {
424 mask.set_weight(id, weight);
425 }
426 mask
427 }
428
429 pub fn from_bone_subtree(skeleton: &Skeleton, root_bones: &[BoneId], weight: f32) -> Self {
431 let mut mask = Self::zero(skeleton.len());
432 let mut stack: Vec<BoneId> = root_bones.to_vec();
433 while let Some(id) = stack.pop() {
434 mask.set_weight(id, weight);
435 for &child in skeleton.children_of(id) {
436 stack.push(child);
437 }
438 }
439 mask
440 }
441}
442
443#[derive(Debug, Clone)]
450pub struct SkinningMatrices {
451 pub matrices: Vec<Mat4>,
452}
453
454impl SkinningMatrices {
455 pub fn compute(skeleton: &Skeleton, pose: &Pose) -> Self {
460 let n = skeleton.len();
461 let mut world = vec![Mat4::IDENTITY; n];
462
463 for bone in &skeleton.bones {
465 let idx = bone.id.index();
466 let local_xform = pose.local_transforms.get(idx)
467 .copied()
468 .unwrap_or_else(Transform3D::identity);
469 let local_mat = local_xform.to_mat4();
470 world[idx] = match bone.parent {
471 None => local_mat,
472 Some(parent) => world[parent.index()] * local_mat,
473 };
474 }
475
476 let matrices = skeleton.bones.iter().map(|bone| {
479 world[bone.id.index()] * bone.inv_bind_matrix
480 }).collect();
481
482 Self { matrices }
483 }
484
485 pub fn len(&self) -> usize { self.matrices.len() }
487 pub fn is_empty(&self) -> bool { self.matrices.is_empty() }
488
489 pub fn get(&self, id: BoneId) -> Option<Mat4> {
491 self.matrices.get(id.index()).copied()
492 }
493
494 pub fn as_flat_slice(&self) -> Vec<f32> {
497 self.matrices.iter().flat_map(|m| m.to_cols_array()).collect()
498 }
499
500 pub fn as_arrays(&self) -> Vec<[f32; 16]> {
502 self.matrices.iter().map(|m| m.to_cols_array()).collect()
503 }
504}
505
506#[derive(Debug, Default)]
518pub struct SkeletonBuilder {
519 pending: Vec<(String, Option<String>, Transform3D)>,
521}
522
523impl SkeletonBuilder {
524 pub fn new() -> Self { Self::default() }
525
526 pub fn add_bone(
530 mut self,
531 name: impl Into<String>,
532 parent: Option<&str>,
533 local_bind_pose: Transform3D,
534 ) -> Self {
535 self.pending.push((name.into(), parent.map(str::to_owned), local_bind_pose));
536 self
537 }
538
539 pub fn add_bone_components(
541 self,
542 name: impl Into<String>,
543 parent: Option<&str>,
544 translation: Vec3,
545 rotation: Quat,
546 scale: Vec3,
547 ) -> Self {
548 self.add_bone(name, parent, Transform3D::new(translation, rotation, scale))
549 }
550
551 pub fn build(self) -> Skeleton {
553 let mut skeleton = Skeleton::new();
554
555 for (idx, (name, parent_name, local_bind_pose)) in self.pending.into_iter().enumerate() {
556 let id = BoneId(idx as u32);
557 let parent_id = parent_name.as_deref().and_then(|pn| skeleton.name_index.get(pn).copied());
558
559 let bone = Bone::new(id, name.clone(), parent_id, local_bind_pose);
560 skeleton.name_index.insert(name, id);
561
562 if let Some(pid) = parent_id {
564 if let Some(parent_bone) = skeleton.bones.get_mut(pid.index()) {
565 parent_bone.children.push(id);
566 }
567 }
568
569 skeleton.bones.push(bone);
570 }
571
572 skeleton.recompute_inv_bind_matrices();
573 skeleton
574 }
575}
576
577impl Skeleton {
580 pub fn standard_humanoid() -> Self {
582 SkeletonBuilder::new()
583 .add_bone("root", None, Transform3D::identity())
585 .add_bone("pelvis", Some("root"), Transform3D::new(Vec3::new(0.0, 1.0, 0.0), Quat::IDENTITY, Vec3::ONE))
586 .add_bone("spine_01", Some("pelvis"), Transform3D::new(Vec3::new(0.0, 0.15, 0.0), Quat::IDENTITY, Vec3::ONE))
588 .add_bone("spine_02", Some("spine_01"), Transform3D::new(Vec3::new(0.0, 0.15, 0.0), Quat::IDENTITY, Vec3::ONE))
589 .add_bone("spine_03", Some("spine_02"), Transform3D::new(Vec3::new(0.0, 0.15, 0.0), Quat::IDENTITY, Vec3::ONE))
590 .add_bone("neck", Some("spine_03"), Transform3D::new(Vec3::new(0.0, 0.10, 0.0), Quat::IDENTITY, Vec3::ONE))
592 .add_bone("head", Some("neck"), Transform3D::new(Vec3::new(0.0, 0.10, 0.0), Quat::IDENTITY, Vec3::ONE))
593 .add_bone("clavicle_l", Some("spine_03"), Transform3D::new(Vec3::new(-0.10, 0.05, 0.0), Quat::IDENTITY, Vec3::ONE))
595 .add_bone("upperarm_l", Some("clavicle_l"),Transform3D::new(Vec3::new(-0.15, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
596 .add_bone("lowerarm_l", Some("upperarm_l"),Transform3D::new(Vec3::new(-0.28, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
597 .add_bone("hand_l", Some("lowerarm_l"),Transform3D::new(Vec3::new(-0.25, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
598 .add_bone("clavicle_r", Some("spine_03"), Transform3D::new(Vec3::new( 0.10, 0.05, 0.0), Quat::IDENTITY, Vec3::ONE))
600 .add_bone("upperarm_r", Some("clavicle_r"),Transform3D::new(Vec3::new( 0.15, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
601 .add_bone("lowerarm_r", Some("upperarm_r"),Transform3D::new(Vec3::new( 0.28, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
602 .add_bone("hand_r", Some("lowerarm_r"),Transform3D::new(Vec3::new( 0.25, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
603 .add_bone("thigh_l", Some("pelvis"), Transform3D::new(Vec3::new(-0.10, -0.05, 0.0),Quat::IDENTITY, Vec3::ONE))
605 .add_bone("calf_l", Some("thigh_l"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
606 .add_bone("foot_l", Some("calf_l"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
607 .add_bone("toe_l", Some("foot_l"), Transform3D::new(Vec3::new(0.0, 0.0, 0.14), Quat::IDENTITY, Vec3::ONE))
608 .add_bone("thigh_r", Some("pelvis"), Transform3D::new(Vec3::new( 0.10, -0.05, 0.0),Quat::IDENTITY, Vec3::ONE))
610 .add_bone("calf_r", Some("thigh_r"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
611 .add_bone("foot_r", Some("calf_r"), Transform3D::new(Vec3::new(0.0, -0.42, 0.0), Quat::IDENTITY, Vec3::ONE))
612 .add_bone("toe_r", Some("foot_r"), Transform3D::new(Vec3::new(0.0, 0.0, 0.14), Quat::IDENTITY, Vec3::ONE))
613 .build()
614 }
615}
616
617#[cfg(test)]
620mod tests {
621 use super::*;
622
623 fn simple_skeleton() -> Skeleton {
624 SkeletonBuilder::new()
625 .add_bone("root", None, Transform3D::identity())
626 .add_bone("spine", Some("root"), Transform3D::new(Vec3::new(0.0, 1.0, 0.0), Quat::IDENTITY, Vec3::ONE))
627 .add_bone("head", Some("spine"), Transform3D::new(Vec3::new(0.0, 0.5, 0.0), Quat::IDENTITY, Vec3::ONE))
628 .add_bone("arm_l", Some("spine"), Transform3D::new(Vec3::new(-0.3, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
629 .add_bone("arm_r", Some("spine"), Transform3D::new(Vec3::new( 0.3, 0.0, 0.0), Quat::IDENTITY, Vec3::ONE))
630 .build()
631 }
632
633 #[test]
634 fn test_bone_id_index() {
635 assert_eq!(BoneId(3).index(), 3);
636 assert_eq!(BoneId::ROOT.index(), 0);
637 }
638
639 #[test]
640 fn test_skeleton_builder_creates_bones() {
641 let skeleton = simple_skeleton();
642 assert_eq!(skeleton.len(), 5);
643 assert!(skeleton.bone_by_name("root").is_some());
644 assert!(skeleton.bone_by_name("spine").is_some());
645 assert!(skeleton.bone_by_name("head").is_some());
646 }
647
648 #[test]
649 fn test_skeleton_name_index() {
650 let skeleton = simple_skeleton();
651 let id = skeleton.name_index["spine"];
652 assert_eq!(id.index(), 1);
653 }
654
655 #[test]
656 fn test_skeleton_parent_child_links() {
657 let skeleton = simple_skeleton();
658 let spine = skeleton.bone_by_name("spine").unwrap();
659 assert_eq!(spine.parent, Some(BoneId(0)));
660 assert!(spine.children.contains(&BoneId(2))); }
662
663 #[test]
664 fn test_skeleton_inv_bind_matrices_not_zero() {
665 let skeleton = simple_skeleton();
666 let root = &skeleton.bones[0];
668 assert!((root.inv_bind_matrix - Mat4::IDENTITY).abs_diff_eq(Mat4::ZERO, 1e-5));
670 let spine = &skeleton.bones[1];
672 let diff = spine.inv_bind_matrix - Mat4::IDENTITY;
673 let max_elem = [diff.x_axis, diff.y_axis, diff.z_axis, diff.w_axis]
674 .iter()
675 .flat_map(|col| [col.x, col.y, col.z, col.w])
676 .map(f32::abs)
677 .fold(0.0, f32::max);
678 assert!(max_elem > 0.01);
681 assert!(spine.inv_bind_matrix.w_axis.y < 0.0);
682 }
683
684 #[test]
685 fn test_rest_pose_matches_bind() {
686 let skeleton = simple_skeleton();
687 let pose = skeleton.rest_pose();
688 assert_eq!(pose.len(), skeleton.len());
689 for (i, bone) in skeleton.bones.iter().enumerate() {
690 assert_eq!(pose.local_transforms[i].translation, bone.local_bind_pose.translation);
691 }
692 }
693
694 #[test]
695 fn test_pose_blend_halfway() {
696 let n = 3;
697 let mut a = Pose::new(n);
698 let mut b = Pose::new(n);
699 a.local_transforms[0].translation = Vec3::ZERO;
700 b.local_transforms[0].translation = Vec3::new(2.0, 0.0, 0.0);
701 let blended = a.blend(&b, 0.5);
702 assert!((blended.local_transforms[0].translation.x - 1.0).abs() < 1e-5);
703 }
704
705 #[test]
706 fn test_pose_blend_extremes() {
707 let n = 2;
708 let mut a = Pose::new(n);
709 let mut b = Pose::new(n);
710 a.local_transforms[0].translation = Vec3::new(1.0, 0.0, 0.0);
711 b.local_transforms[0].translation = Vec3::new(3.0, 0.0, 0.0);
712 let at_zero = a.blend(&b, 0.0);
713 let at_one = a.blend(&b, 1.0);
714 assert!((at_zero.local_transforms[0].translation.x - 1.0).abs() < 1e-5);
715 assert!((at_one.local_transforms[0].translation.x - 3.0).abs() < 1e-5);
716 }
717
718 #[test]
719 fn test_pose_add_pose() {
720 let n = 2;
721 let mut base = Pose::new(n);
722 let mut additive = Pose::new(n);
723 base.local_transforms[0].translation = Vec3::new(1.0, 0.0, 0.0);
724 additive.local_transforms[0].translation = Vec3::new(0.5, 0.0, 0.0);
725 let result = base.add_pose(&additive, 1.0);
726 assert!(result.local_transforms[0].translation.x > 1.0);
728 }
729
730 #[test]
731 fn test_bone_mask_full_body() {
732 let skeleton = simple_skeleton();
733 let mask = BoneMask::full_body(skeleton.len());
734 for &w in &mask.weights {
735 assert!((w - 1.0).abs() < 1e-6);
736 }
737 }
738
739 #[test]
740 fn test_bone_mask_zero() {
741 let skeleton = simple_skeleton();
742 let mask = BoneMask::zero(skeleton.len());
743 for &w in &mask.weights {
744 assert!(w.abs() < 1e-6);
745 }
746 }
747
748 #[test]
749 fn test_skinning_matrices_identity_pose() {
750 let skeleton = simple_skeleton();
751 let pose = skeleton.rest_pose();
752 let skinning = SkinningMatrices::compute(&skeleton, &pose);
753 assert_eq!(skinning.len(), skeleton.len());
754 for m in &skinning.matrices {
757 assert!(m.abs_diff_eq(Mat4::IDENTITY, 1e-4),
759 "Expected near-identity skinning matrix for rest pose, got {:?}", m);
760 }
761 }
762
763 #[test]
764 fn test_skinning_flat_slice_length() {
765 let skeleton = simple_skeleton();
766 let pose = skeleton.rest_pose();
767 let skinning = SkinningMatrices::compute(&skeleton, &pose);
768 let flat = skinning.as_flat_slice();
769 assert_eq!(flat.len(), skeleton.len() * 16);
770 }
771
772 #[test]
773 fn test_standard_humanoid_bone_count() {
774 let skeleton = Skeleton::standard_humanoid();
775 assert_eq!(skeleton.len(), 23);
776 assert!(skeleton.bone_by_name("head").is_some());
777 assert!(skeleton.bone_by_name("hand_l").is_some());
778 assert!(skeleton.bone_by_name("foot_r").is_some());
779 }
780
781 #[test]
782 fn test_upper_body_mask_has_arm_bones() {
783 let skeleton = Skeleton::standard_humanoid();
784 let mask = BoneMask::upper_body(&skeleton);
785 let upperarm_l_id = skeleton.name_index["upperarm_l"];
786 assert!((mask.get_weight(upperarm_l_id) - 1.0).abs() < 1e-6);
787 }
788
789 #[test]
790 fn test_lower_body_mask_has_leg_bones() {
791 let skeleton = Skeleton::standard_humanoid();
792 let mask = BoneMask::lower_body(&skeleton);
793 let thigh_l_id = skeleton.name_index["thigh_l"];
794 assert!((mask.get_weight(thigh_l_id) - 1.0).abs() < 1e-6);
795 }
796
797 #[test]
798 fn test_mask_subtree() {
799 let skeleton = simple_skeleton();
800 let spine_id = skeleton.name_index["spine"];
801 let mask = BoneMask::from_bone_subtree(&skeleton, &[spine_id], 1.0);
802 assert!((mask.get_weight(spine_id) - 1.0).abs() < 1e-6);
804 let head_id = skeleton.name_index["head"];
805 assert!((mask.get_weight(head_id) - 1.0).abs() < 1e-6);
806 }
807}