spinal/
state.rs

1use crate::skeleton::{Attachment, Bone, ParentTransform, Skeleton, Slot};
2use bevy_math::{Affine3A, Quat, Vec2};
3use bevy_utils::HashMap;
4use tracing::{trace, warn};
5
6/// A state manager when you can't use a lifetime reference to a `Skeleton`.
7///
8/// Instead you must pass in a reference to the skeleton when calling methods on here.
9///
10/// Care is needed to make sure it's the correct `Skeleton` instance, otherwise there will be errors.
11#[derive(Debug, Clone, Default)]
12pub struct DetachedSkeletonState {
13    bones: HashMap<usize, BoneState>,
14    pub slots: Vec<(usize, usize, BoneState, usize, usize)>,
15}
16
17impl DetachedSkeletonState {
18    pub fn new() -> Self {
19        Self::default()
20    }
21
22    pub fn bones<'a>(&'a self, skeleton: &'a Skeleton) -> Vec<(&'a Bone, &'a BoneState)> {
23        self.bones
24            .iter()
25            .map(|(id, state)| (&skeleton.bones[*id], state))
26            .collect()
27    }
28
29    pub fn pose(&mut self, skeleton: &Skeleton) {
30        if skeleton.bones.len() == 0 {
31            warn!("No bones in skeleton.");
32            return;
33        };
34
35        self.pose_bone(skeleton, 0, BoneState::default());
36
37        //
38
39        self.slots.clear();
40        for (slot_idx, slot) in skeleton.slots.iter().enumerate() {
41            let bone = &skeleton.bones[slot.bone];
42            let bone_state = match self.bones.get(&slot.bone) {
43                Some(b) => b.clone(),
44                None => {
45                    warn!("Slot bone not found in bones.");
46                    continue;
47                }
48            };
49            let skin = &skeleton.skins[0]; // TODO: support multiple skins
50            let skin_slot = &skin.slots.iter().find(|s| s.slot == slot_idx).unwrap();
51            let slot_attachment_name = match slot.attachment.as_ref() {
52                Some(s) => s,
53                None => {
54                    warn!("Slot attachment name not set. Assumed no attachment for set up pose.");
55                    continue;
56                }
57            };
58
59            let slot_attachment = skin_slot
60                .attachments
61                .iter()
62                .find(|attachment| &attachment.attachment_name == slot_attachment_name);
63
64            if let Some(attachment) = slot_attachment {
65                self.slots.push((
66                    slot_idx,
67                    slot.bone,
68                    bone_state,
69                    slot_idx,
70                    todo!(), /*attachment_idx*/
71                ));
72            } else {
73                warn!("Slot attachment not found in skin.");
74                continue;
75            }
76
77            dbg!(self.slots.len());
78        }
79    }
80
81    fn pose_bone(&mut self, skeleton: &Skeleton, bone_idx: usize, parent_state: BoneState) {
82        let bone = &skeleton.bones[bone_idx];
83        let (affinity, rotation) = match bone.transform {
84            ParentTransform::Normal => (
85                Affine3A::from_scale_rotation_translation(
86                    bone.scale.extend(1.),
87                    Quat::from_rotation_z(bone.rotation.to_radians()),
88                    bone.position.extend(0.),
89                ),
90                bone.rotation.to_radians(),
91            ),
92            _ => {
93                // TODO: handle different parent transforms
94                warn!(
95                    "Unhandled transform: {:?} in bone: {}",
96                    bone.transform, bone.name
97                );
98                return;
99            }
100        };
101        let bone_state = BoneState {
102            affinity: parent_state.affinity * affinity,
103            rotation: parent_state.rotation + rotation,
104        };
105
106        self.bones.insert(bone_idx, bone_state.clone());
107        trace!("Bone: {} {:?}", bone_idx, bone.name);
108
109        if let Some(children) = skeleton.bones_tree.get(&bone_idx) {
110            for child_idx in children {
111                self.pose_bone(skeleton, *child_idx, bone_state);
112            }
113        }
114    }
115}
116
117#[derive(Debug, Clone)]
118pub struct SkeletonState<'a> {
119    skeleton: &'a Skeleton,
120
121    internal: DetachedSkeletonState,
122}
123
124impl<'a> SkeletonState<'a> {
125    pub fn new(skeleton: &'a Skeleton) -> Self {
126        Self {
127            skeleton,
128            internal: DetachedSkeletonState::default(),
129        }
130    }
131
132    pub fn pose(&mut self) {
133        self.internal.pose(self.skeleton)
134    }
135
136    pub fn bones(&'a self) -> Vec<(&'a Bone, &'a BoneState)> {
137        self.internal.bones(self.skeleton)
138    }
139}
140
141#[derive(Debug, Clone, Copy, Default)]
142pub struct BoneState {
143    pub affinity: Affine3A,
144
145    /// Global rotation of the bone.
146    // I don't know how to extract rotation out of an Affine2, so I'm just tracking this separately.
147    pub rotation: f32,
148}
149
150#[cfg(test)]
151mod tests {
152    use super::*;
153    use crate::BinaryParser;
154    use test_log::test;
155
156    #[test]
157    fn spineboy() {
158        let b = include_bytes!("../../assets/spineboy-pro-4.1/spineboy-pro.skel");
159        let skeleton = BinaryParser::parse(b).unwrap();
160        let mut state = SkeletonState::new(&skeleton);
161        state.pose();
162    }
163}