1use crate::{
5 M2Model, Result,
6 chunks::{
7 bone::M2Bone,
8 m2_track_resolver::{M2TrackQuatExt, M2TrackVec3Ext},
9 },
10 common::C3Vector,
11};
12use std::io::{Cursor, Read, Seek};
13
14#[derive(Debug, Clone)]
16pub struct ResolvedBoneAnimation {
17 pub bone_id: i32,
19 pub parent_bone: i16,
21 pub pivot: C3Vector,
23 pub translation: Option<(Vec<u32>, Vec<C3Vector>)>,
25 pub rotation: Option<(Vec<u32>, Vec<[i16; 4]>)>,
27 pub scale: Option<(Vec<u32>, Vec<C3Vector>)>,
29 pub bind_pose_translation: C3Vector,
31 pub bind_pose_rotation: [f32; 4],
33 pub bind_pose_scale: C3Vector,
35}
36
37impl ResolvedBoneAnimation {
38 pub fn from_bone<R: Read + Seek>(bone: &M2Bone, reader: &mut R) -> Result<Self> {
40 let (translation, bind_pose_translation) = if bone.translation.has_data() {
42 let (timestamps, values, _ranges) = bone.translation.resolve_data(reader)?;
43 let bind_trans = if !values.is_empty() {
44 values[0]
45 } else {
46 C3Vector {
47 x: 0.0,
48 y: 0.0,
49 z: 0.0,
50 }
51 };
52 (Some((timestamps, values)), bind_trans)
53 } else {
54 (
55 None,
56 C3Vector {
57 x: 0.0,
58 y: 0.0,
59 z: 0.0,
60 },
61 )
62 };
63
64 let (rotation, bind_pose_rotation) = if bone.rotation.has_data() {
66 let (timestamps, values, _ranges) = bone.rotation.resolve_data(reader)?;
67
68 let quat_values: Vec<[i16; 4]> = values.iter().map(|q| [q.x, q.y, q.z, q.w]).collect();
70
71 let bind_rot = if !values.is_empty() {
72 let q = &values[0];
74 let quat = [
75 q.x as f32 / 32767.0,
76 q.y as f32 / 32767.0,
77 q.z as f32 / 32767.0,
78 q.w as f32 / 32767.0,
79 ];
80
81 if quat == [0.0, 0.0, 0.0, 0.0] {
83 [0.0, 0.0, 0.0, 1.0] } else {
85 quat
86 }
87 } else {
88 [0.0, 0.0, 0.0, 1.0] };
90
91 (Some((timestamps, quat_values)), bind_rot)
92 } else {
93 (None, [0.0, 0.0, 0.0, 1.0])
94 };
95
96 let (scale, bind_pose_scale) = if bone.scale.has_data() {
98 let (timestamps, values, _ranges) = bone.scale.resolve_data(reader)?;
99 let bind_scale = if !values.is_empty() {
100 values[0]
101 } else {
102 C3Vector {
103 x: 1.0,
104 y: 1.0,
105 z: 1.0,
106 }
107 };
108 (Some((timestamps, values)), bind_scale)
109 } else {
110 (
111 None,
112 C3Vector {
113 x: 1.0,
114 y: 1.0,
115 z: 1.0,
116 },
117 )
118 };
119
120 Ok(ResolvedBoneAnimation {
121 bone_id: bone.bone_id,
122 parent_bone: bone.parent_bone,
123 pivot: bone.pivot,
124 translation,
125 rotation,
126 scale,
127 bind_pose_translation,
128 bind_pose_rotation,
129 bind_pose_scale,
130 })
131 }
132
133 pub fn has_animation(&self) -> bool {
135 self.translation.is_some() || self.rotation.is_some() || self.scale.is_some()
136 }
137
138 pub fn translation_keyframe_count(&self) -> usize {
140 self.translation.as_ref().map(|(t, _)| t.len()).unwrap_or(0)
141 }
142
143 pub fn rotation_keyframe_count(&self) -> usize {
145 self.rotation.as_ref().map(|(t, _)| t.len()).unwrap_or(0)
146 }
147
148 pub fn scale_keyframe_count(&self) -> usize {
150 self.scale.as_ref().map(|(t, _)| t.len()).unwrap_or(0)
151 }
152}
153
154pub trait M2ModelAnimationExt {
156 fn resolve_bone_animations(&self, data: &[u8]) -> Result<Vec<ResolvedBoneAnimation>>;
158
159 fn get_bind_pose(&self, data: &[u8]) -> Result<Vec<ResolvedBoneAnimation>>;
161}
162
163impl M2ModelAnimationExt for M2Model {
164 fn resolve_bone_animations(&self, data: &[u8]) -> Result<Vec<ResolvedBoneAnimation>> {
165 let mut cursor = Cursor::new(data);
166 let mut resolved = Vec::with_capacity(self.bones.len());
167
168 for bone in &self.bones {
169 resolved.push(ResolvedBoneAnimation::from_bone(bone, &mut cursor)?);
170 }
171
172 Ok(resolved)
173 }
174
175 fn get_bind_pose(&self, data: &[u8]) -> Result<Vec<ResolvedBoneAnimation>> {
176 self.resolve_bone_animations(data)
178 }
179}
180
181#[cfg(test)]
182mod tests {
183 use super::*;
184 use crate::chunks::bone::M2BoneFlags;
185 use crate::chunks::m2_track::{M2TrackQuat, M2TrackVec3};
186
187 #[test]
188 fn test_bind_pose_defaults() {
189 let bone = M2Bone {
190 bone_id: 0,
191 flags: M2BoneFlags::TRANSFORMED,
192 parent_bone: -1,
193 submesh_id: 0,
194 unknown: [0, 0],
195 bone_name_crc: None,
196 translation: M2TrackVec3 {
197 base: crate::chunks::m2_track::M2TrackBase {
198 interpolation_type: crate::chunks::animation::M2InterpolationType::None,
199 global_sequence: 0xFFFF,
200 },
201 ranges: None,
202 timestamps: crate::common::M2Array::new(0, 0),
203 values: crate::common::M2Array::new(0, 0),
204 },
205 rotation: M2TrackQuat {
206 base: crate::chunks::m2_track::M2TrackBase {
207 interpolation_type: crate::chunks::animation::M2InterpolationType::None,
208 global_sequence: 0xFFFF,
209 },
210 ranges: None,
211 timestamps: crate::common::M2Array::new(0, 0),
212 values: crate::common::M2Array::new(0, 0),
213 },
214 scale: M2TrackVec3 {
215 base: crate::chunks::m2_track::M2TrackBase {
216 interpolation_type: crate::chunks::animation::M2InterpolationType::None,
217 global_sequence: 0xFFFF,
218 },
219 ranges: None,
220 timestamps: crate::common::M2Array::new(0, 0),
221 values: crate::common::M2Array::new(0, 0),
222 },
223 pivot: C3Vector {
224 x: 1.0,
225 y: 2.0,
226 z: 3.0,
227 },
228 };
229
230 let data = vec![0u8; 1000];
231 let mut cursor = Cursor::new(&data);
232
233 let resolved = ResolvedBoneAnimation::from_bone(&bone, &mut cursor).unwrap();
234
235 assert_eq!(
237 resolved.bind_pose_translation,
238 C3Vector {
239 x: 0.0,
240 y: 0.0,
241 z: 0.0
242 }
243 );
244 assert_eq!(resolved.bind_pose_rotation, [0.0, 0.0, 0.0, 1.0]);
245 assert_eq!(
246 resolved.bind_pose_scale,
247 C3Vector {
248 x: 1.0,
249 y: 1.0,
250 z: 1.0
251 }
252 );
253 assert_eq!(
254 resolved.pivot,
255 C3Vector {
256 x: 1.0,
257 y: 2.0,
258 z: 3.0
259 }
260 );
261 assert!(!resolved.has_animation());
262 }
263}