1use heapless::Vec;
27use nalgebra::{Matrix4, Point3, UnitQuaternion, Vector3};
28
29#[allow(unused_imports)]
30use nalgebra::ComplexField;
31
32pub const MAX_BONE_INFLUENCES: usize = 4;
34
35#[derive(Debug, Clone, Copy, PartialEq, Eq)]
37pub struct BoneId(pub usize);
38
39#[derive(Debug, Clone)]
44pub struct Bone {
45 pub name: heapless::String<32>,
47
48 pub position: Vector3<f32>,
50
51 pub rotation: UnitQuaternion<f32>,
53
54 pub scale: Vector3<f32>,
56
57 pub parent: Option<BoneId>,
59
60 pub local_transform: Matrix4<f32>,
62
63 pub world_transform: Matrix4<f32>,
65
66 pub inverse_bind_pose: Matrix4<f32>,
68}
69
70impl Bone {
71 pub fn new(name: &str) -> Self {
73 let mut name_str = heapless::String::new();
74 let _ = name_str.push_str(name);
75
76 Self {
77 name: name_str,
78 position: Vector3::zeros(),
79 rotation: UnitQuaternion::identity(),
80 scale: Vector3::new(1.0, 1.0, 1.0),
81 parent: None,
82 local_transform: Matrix4::identity(),
83 world_transform: Matrix4::identity(),
84 inverse_bind_pose: Matrix4::identity(),
85 }
86 }
87
88 pub fn with_position(mut self, position: Vector3<f32>) -> Self {
90 self.position = position;
91 self.update_local_transform();
92 self
93 }
94
95 pub fn with_rotation(mut self, rotation: UnitQuaternion<f32>) -> Self {
97 self.rotation = rotation;
98 self.update_local_transform();
99 self
100 }
101
102 pub fn with_scale(mut self, scale: Vector3<f32>) -> Self {
104 self.scale = scale;
105 self.update_local_transform();
106 self
107 }
108
109 pub fn update_local_transform(&mut self) {
111 let translation = Matrix4::new_translation(&self.position);
113 let rotation = self.rotation.to_homogeneous();
114 let scale = Matrix4::new_nonuniform_scaling(&self.scale);
115
116 self.local_transform = translation * rotation * scale;
117 }
118
119 pub fn set_position(&mut self, position: Vector3<f32>) {
121 self.position = position;
122 self.update_local_transform();
123 }
124
125 pub fn set_rotation(&mut self, rotation: UnitQuaternion<f32>) {
127 self.rotation = rotation;
128 self.update_local_transform();
129 }
130}
131
132#[derive(Debug, Clone)]
136pub struct Skeleton<const N: usize> {
137 pub bones: Vec<Bone, N>,
138}
139
140impl<const N: usize> Skeleton<N> {
141 pub fn new() -> Self {
143 Self { bones: Vec::new() }
144 }
145
146 pub fn add_bone(&mut self, mut bone: Bone, parent: Option<BoneId>) -> Result<BoneId, ()> {
150 bone.parent = parent;
151 bone.update_local_transform();
152
153 let id = BoneId(self.bones.len());
154 self.bones.push(bone).map_err(|_| ())?;
155
156 Ok(id)
157 }
158
159 pub fn get_bone(&self, id: BoneId) -> Option<&Bone> {
161 self.bones.get(id.0)
162 }
163
164 pub fn get_bone_mut(&mut self, id: BoneId) -> Option<&mut Bone> {
166 self.bones.get_mut(id.0)
167 }
168
169 pub fn update_transforms(&mut self) {
173 for bone in self.bones.iter_mut() {
175 bone.update_local_transform();
176 }
177
178 for i in 0..self.bones.len() {
180 let parent_transform = if let Some(parent_id) = self.bones[i].parent {
181 self.bones[parent_id.0].world_transform
182 } else {
183 Matrix4::identity()
184 };
185
186 self.bones[i].world_transform = parent_transform * self.bones[i].local_transform;
187 }
188 }
189
190 pub fn compute_inverse_bind_poses(&mut self) {
194 self.update_transforms();
195
196 for bone in self.bones.iter_mut() {
197 bone.inverse_bind_pose = bone
198 .world_transform
199 .try_inverse()
200 .unwrap_or(Matrix4::identity());
201 }
202 }
203
204 pub fn get_skinning_matrix(&self, bone_id: BoneId) -> Matrix4<f32> {
206 if let Some(bone) = self.get_bone(bone_id) {
207 bone.world_transform * bone.inverse_bind_pose
208 } else {
209 Matrix4::identity()
210 }
211 }
212}
213
214impl<const N: usize> Default for Skeleton<N> {
215 fn default() -> Self {
216 Self::new()
217 }
218}
219
220#[derive(Debug, Clone, Copy)]
224pub struct VertexSkinning {
225 pub bone_indices: [usize; MAX_BONE_INFLUENCES],
227
228 pub bone_weights: [f32; MAX_BONE_INFLUENCES],
230
231 pub num_influences: usize,
233}
234
235impl VertexSkinning {
236 pub fn single_bone(bone_index: usize) -> Self {
238 Self {
239 bone_indices: [bone_index, 0, 0, 0],
240 bone_weights: [1.0, 0.0, 0.0, 0.0],
241 num_influences: 1,
242 }
243 }
244
245 pub fn two_bones(bone0: usize, weight0: f32, bone1: usize, weight1: f32) -> Self {
247 Self {
248 bone_indices: [bone0, bone1, 0, 0],
249 bone_weights: [weight0, weight1, 0.0, 0.0],
250 num_influences: 2,
251 }
252 }
253
254 pub fn new(
258 bone_indices: [usize; MAX_BONE_INFLUENCES],
259 bone_weights: [f32; MAX_BONE_INFLUENCES],
260 num_influences: usize,
261 ) -> Self {
262 Self {
263 bone_indices,
264 bone_weights,
265 num_influences: num_influences.min(MAX_BONE_INFLUENCES),
266 }
267 }
268}
269
270impl Default for VertexSkinning {
271 fn default() -> Self {
272 Self::single_bone(0)
273 }
274}
275
276#[derive(Debug, Clone)]
280pub struct SkinningData {
281 pub vertex_skinning: heapless::Vec<VertexSkinning, 512>,
283}
284
285impl SkinningData {
286 pub fn new() -> Self {
288 Self {
289 vertex_skinning: Vec::new(),
290 }
291 }
292
293 pub fn add_vertex(&mut self, skinning: VertexSkinning) -> Result<(), ()> {
295 self.vertex_skinning.push(skinning).map_err(|_| ())
296 }
297}
298
299impl Default for SkinningData {
300 fn default() -> Self {
301 Self::new()
302 }
303}
304
305pub fn apply_skinning<const N: usize>(
318 skeleton: &Skeleton<N>,
319 skinning_data: &SkinningData,
320 source_vertices: &[[f32; 3]],
321 output_vertices: &mut [[f32; 3]],
322) -> usize {
323 let count = source_vertices
324 .len()
325 .min(output_vertices.len())
326 .min(skinning_data.vertex_skinning.len());
327
328 for i in 0..count {
329 let vertex = Point3::new(
330 source_vertices[i][0],
331 source_vertices[i][1],
332 source_vertices[i][2],
333 );
334
335 let skinning = &skinning_data.vertex_skinning[i];
336 let mut deformed = Point3::new(0.0, 0.0, 0.0);
337
338 for j in 0..skinning.num_influences {
340 let bone_id = BoneId(skinning.bone_indices[j]);
341 let weight = skinning.bone_weights[j];
342
343 if weight > 0.0 {
344 let skinning_matrix = skeleton.get_skinning_matrix(bone_id);
345 let transformed = skinning_matrix.transform_point(&vertex);
346 deformed += transformed.coords * weight;
347 }
348 }
349
350 output_vertices[i] = [deformed.x, deformed.y, deformed.z];
351 }
352
353 count
354}
355
356pub fn apply_skinning_to_normals<const N: usize>(
370 skeleton: &Skeleton<N>,
371 skinning_data: &SkinningData,
372 source_normals: &[[f32; 3]],
373 output_normals: &mut [[f32; 3]],
374) -> usize {
375 let count = source_normals
376 .len()
377 .min(output_normals.len())
378 .min(skinning_data.vertex_skinning.len());
379
380 for i in 0..count {
381 let normal = Vector3::new(
382 source_normals[i][0],
383 source_normals[i][1],
384 source_normals[i][2],
385 );
386
387 let skinning = &skinning_data.vertex_skinning[i];
388 let mut deformed = Vector3::zeros();
389
390 for j in 0..skinning.num_influences {
391 let bone_id = BoneId(skinning.bone_indices[j]);
392 let weight = skinning.bone_weights[j];
393
394 if weight > 0.0 {
395 let skinning_matrix = skeleton.get_skinning_matrix(bone_id);
396
397 let rotation_part = skinning_matrix.fixed_view::<3, 3>(0, 0);
399 let transformed = rotation_part * normal;
400 deformed += transformed * weight;
401 }
402 }
403
404 let normalized = deformed.normalize();
406 output_normals[i] = [normalized.x, normalized.y, normalized.z];
407 }
408
409 count
410}
411
412#[cfg(feature = "anim-blend")]
413mod anim_blend_api {
414 use super::*;
415
416 #[derive(Debug, Clone, Copy)]
418 pub struct BonePose {
419 pub position: Vector3<f32>,
420 pub rotation: UnitQuaternion<f32>,
421 pub scale: Vector3<f32>,
422 }
423
424 impl BonePose {
425 pub fn identity() -> Self {
426 Self {
427 position: Vector3::zeros(),
428 rotation: UnitQuaternion::identity(),
429 scale: Vector3::new(1.0, 1.0, 1.0),
430 }
431 }
432
433 pub fn blend(a: Self, b: Self, t: f32) -> Self {
436 let t = t.clamp(0.0, 1.0);
437 let q1 = a.rotation.into_inner();
438 let mut q2 = b.rotation.into_inner();
439 if q1.coords.dot(&q2.coords) < 0.0 {
440 q2 = -q2;
441 }
442 let q = nalgebra::Quaternion::new(
443 q1.w + (q2.w - q1.w) * t,
444 q1.i + (q2.i - q1.i) * t,
445 q1.j + (q2.j - q1.j) * t,
446 q1.k + (q2.k - q1.k) * t,
447 );
448 Self {
449 position: a.position + (b.position - a.position) * t,
450 rotation: UnitQuaternion::new_normalize(q),
451 scale: a.scale + (b.scale - a.scale) * t,
452 }
453 }
454 }
455
456 #[derive(Debug, Clone, Copy)]
458 pub struct SkeletonKeyframe<'a> {
459 pub time: f32,
460 pub poses: &'a [BonePose],
461 }
462
463 #[derive(Debug, Clone, Copy)]
465 pub struct AnimClip<'a> {
466 pub keyframes: &'a [SkeletonKeyframe<'a>],
467 pub looping: bool,
468 }
469
470 impl<'a> AnimClip<'a> {
471 pub fn duration(&self) -> f32 {
472 self.keyframes.last().map(|k| k.time).unwrap_or(0.0)
473 }
474
475 pub fn sample_bone(&self, time: f32, bone_index: usize) -> Option<BonePose> {
477 if self.keyframes.is_empty() {
478 return None;
479 }
480 let duration = self.duration();
481 let t = if self.looping {
482 if duration > 0.0 { time % duration } else { 0.0 }
483 } else {
484 time.clamp(0.0, duration)
485 };
486
487 let idx = self.keyframes.partition_point(|kf| kf.time <= t);
488 let i1 = idx.saturating_sub(1);
489 let i2 = idx.min(self.keyframes.len() - 1).max(i1);
490 let k1 = &self.keyframes[i1];
491 let k2 = &self.keyframes[i2];
492 let p1 = *k1.poses.get(bone_index)?;
493 if i1 == i2 {
494 return Some(p1);
495 }
496 let p2 = *k2.poses.get(bone_index)?;
497 let alpha = if k2.time > k1.time {
498 (t - k1.time) / (k2.time - k1.time)
499 } else {
500 0.0
501 };
502 Some(BonePose::blend(p1, p2, alpha))
503 }
504 }
505
506 pub fn blend_clips_onto_skeleton<const N: usize, const C: usize>(
510 skeleton: &mut Skeleton<N>,
511 layers: &[(&AnimClip<'_>, f32, f32)],
512 ) {
513 let mut accum: heapless::Vec<(BonePose, f32), 4> = heapless::Vec::new();
514 for bone_i in 0..skeleton.bones.len() {
515 accum.clear();
516 let mut wsum = 0.0f32;
517 for &(clip, time, w) in layers.iter().take(C.min(4)) {
518 if w <= 0.0 {
519 continue;
520 }
521 if let Some(pose) = clip.sample_bone(time, bone_i) {
522 let _ = accum.push((pose, w));
523 wsum += w;
524 }
525 }
526 if accum.is_empty() || wsum <= 0.0 {
527 continue;
528 }
529 let mut blended = accum[0].0;
530 let mut acc_w = accum[0].1 / wsum;
531 for i in 1..accum.len() {
532 let w = accum[i].1 / wsum;
533 let t = w / (acc_w + w);
534 blended = BonePose::blend(blended, accum[i].0, t);
535 acc_w += w;
536 }
537 if let Some(bone) = skeleton.get_bone_mut(BoneId(bone_i)) {
538 bone.position = blended.position;
539 bone.rotation = blended.rotation;
540 bone.scale = blended.scale;
541 bone.update_local_transform();
542 }
543 }
544 }
545
546 impl Bone {
548 pub fn slerp_rotation(&mut self, target: UnitQuaternion<f32>, t: f32) {
549 self.rotation = self.rotation.slerp(&target, t.clamp(0.0, 1.0));
550 self.update_local_transform();
551 }
552 }
553
554 #[derive(Debug, Clone, Copy)]
556 pub struct JointAabb {
557 pub center: Vector3<f32>,
558 pub half_extents: Vector3<f32>,
559 }
560
561 impl JointAabb {
562 pub fn from_aabb(aabb: crate::bounds::Aabb) -> Self {
563 Self {
564 center: aabb.center,
565 half_extents: aabb.half_extents,
566 }
567 }
568
569 pub fn to_aabb(self) -> crate::bounds::Aabb {
570 crate::bounds::Aabb {
571 center: self.center,
572 half_extents: self.half_extents,
573 }
574 }
575 }
576
577 pub fn compute_joint_aabbs<const N: usize>(
581 skeleton_bones: usize,
582 skinning: &SkinningData,
583 vertices: &[[f32; 3]],
584 ) -> heapless::Vec<Option<JointAabb>, N> {
585 let mut mins = [Vector3::new(f32::MAX, f32::MAX, f32::MAX); 64];
586 let mut maxs = [Vector3::new(f32::MIN, f32::MIN, f32::MIN); 64];
587 let mut used = [false; 64];
588 let n = skeleton_bones.min(64);
589
590 for (vi, skin) in skinning.vertex_skinning.iter().enumerate() {
591 if vi >= vertices.len() {
592 break;
593 }
594 let p = Vector3::new(vertices[vi][0], vertices[vi][1], vertices[vi][2]);
595 for j in 0..skin.num_influences {
596 if skin.bone_weights[j] <= 0.0 {
597 continue;
598 }
599 let bi = skin.bone_indices[j];
600 if bi >= n {
601 continue;
602 }
603 used[bi] = true;
604 mins[bi].x = mins[bi].x.min(p.x);
605 mins[bi].y = mins[bi].y.min(p.y);
606 mins[bi].z = mins[bi].z.min(p.z);
607 maxs[bi].x = maxs[bi].x.max(p.x);
608 maxs[bi].y = maxs[bi].y.max(p.y);
609 maxs[bi].z = maxs[bi].z.max(p.z);
610 }
611 }
612
613 let mut out: heapless::Vec<Option<JointAabb>, N> = heapless::Vec::new();
614 for i in 0..skeleton_bones.min(N) {
615 let entry = if i < 64 && used[i] {
616 Some(JointAabb::from_aabb(crate::bounds::Aabb::from_min_max(
617 mins[i], maxs[i],
618 )))
619 } else {
620 None
621 };
622 let _ = out.push(entry);
623 }
624 out
625 }
626
627 pub fn skinned_model_aabb<const N: usize>(
630 skeleton: &Skeleton<N>,
631 joint_aabbs: &[Option<JointAabb>],
632 ) -> Option<crate::bounds::Aabb> {
633 let mut acc: Option<crate::bounds::Aabb> = None;
634 for (i, ja) in joint_aabbs.iter().enumerate() {
635 let Some(ja) = ja else { continue };
636 let Some(bone) = skeleton.get_bone(BoneId(i)) else {
637 continue;
638 };
639 let skin = bone.world_transform * bone.inverse_bind_pose;
640 let local = ja.to_aabb();
641 let world = local.transformed(&skin);
642 acc = Some(match acc {
643 Some(a) => a.merge(world),
644 None => world,
645 });
646 }
647 acc
648 }
649}
650
651#[cfg(feature = "anim-blend")]
652pub use anim_blend_api::*;
653
654#[cfg(all(feature = "dsp", feature = "anim-blend"))]
655impl Bone {
656 pub fn interpolate_rotation_dsp(&mut self, target_rotation: UnitQuaternion<f32>, t: f32) {
658 self.slerp_rotation(target_rotation, t);
659 }
660}
661
662#[cfg(all(feature = "dsp", not(feature = "anim-blend")))]
663impl Bone {
664 pub fn interpolate_rotation_dsp(&mut self, target_rotation: UnitQuaternion<f32>, t: f32) {
666 let q1 = [
667 self.rotation.w,
668 self.rotation.i,
669 self.rotation.j,
670 self.rotation.k,
671 ];
672 let q2 = [
673 target_rotation.w,
674 target_rotation.i,
675 target_rotation.j,
676 target_rotation.k,
677 ];
678 let dot = q1[0] * q2[0] + q1[1] * q2[1] + q1[2] * q2[2] + q1[3] * q2[3];
679 let q2_adj = if dot < 0.0 {
680 [-q2[0], -q2[1], -q2[2], -q2[3]]
681 } else {
682 q2
683 };
684 let t_clamped = t.clamp(0.0, 1.0);
685 let mut interpolated = [
686 q1[0] + (q2_adj[0] - q1[0]) * t_clamped,
687 q1[1] + (q2_adj[1] - q1[1]) * t_clamped,
688 q1[2] + (q2_adj[2] - q1[2]) * t_clamped,
689 q1[3] + (q2_adj[3] - q1[3]) * t_clamped,
690 ];
691 let _ = embedded_dsp::quaternion_normalize_f32(&mut interpolated);
692
693 self.rotation = UnitQuaternion::new_normalize(nalgebra::Quaternion::new(
694 interpolated[0],
695 interpolated[1],
696 interpolated[2],
697 interpolated[3],
698 ));
699 self.update_local_transform();
700 }
701}
702
703#[cfg(test)]
704mod tests {
705 use super::*;
706
707 #[test]
708 fn test_bone_creation() {
709 let bone = Bone::new("test_bone");
710 assert_eq!(bone.name.as_str(), "test_bone");
711 assert_eq!(bone.position, Vector3::zeros());
712 assert_eq!(bone.parent, None);
713 }
714
715 #[test]
716 fn test_skeleton_add_bone() {
717 let mut skeleton = Skeleton::<4>::new();
718
719 let root = skeleton.add_bone(Bone::new("root"), None);
720 assert!(root.is_ok());
721
722 let root_id = root.unwrap();
723 let child = skeleton.add_bone(Bone::new("child"), Some(root_id));
724 assert!(child.is_ok());
725
726 assert_eq!(skeleton.bones.len(), 2);
727 }
728
729 #[test]
730 fn test_hierarchy_transforms() {
731 let mut skeleton = Skeleton::<4>::new();
732
733 let root = skeleton.add_bone(Bone::new("root"), None).unwrap();
735
736 let child = skeleton
738 .add_bone(
739 Bone::new("child").with_position(Vector3::new(1.0, 0.0, 0.0)),
740 Some(root),
741 )
742 .unwrap();
743
744 skeleton.update_transforms();
745
746 let child_bone = skeleton.get_bone(child).unwrap();
748 let world_pos = child_bone.world_transform.column(3);
749 assert!((world_pos.x - 1.0).abs() < 0.001);
750 assert!(world_pos.y.abs() < 0.001);
751 assert!(world_pos.z.abs() < 0.001);
752 }
753
754 #[test]
755 fn test_vertex_skinning_single_bone() {
756 let skinning = VertexSkinning::single_bone(0);
757 assert_eq!(skinning.num_influences, 1);
758 assert_eq!(skinning.bone_weights[0], 1.0);
759 }
760
761 #[test]
762 fn test_vertex_skinning_two_bones() {
763 let skinning = VertexSkinning::two_bones(0, 0.7, 1, 0.3);
764 assert_eq!(skinning.num_influences, 2);
765 assert_eq!(skinning.bone_weights[0], 0.7);
766 assert_eq!(skinning.bone_weights[1], 0.3);
767 }
768}