1use super::skeleton::{Skeleton, Transform};
6
7#[derive(Copy, Clone, Debug, PartialEq, Eq)]
13pub enum Interpolation {
14 Linear,
15 Step,
16}
17
18#[derive(Copy, Clone, Debug)]
20pub struct Keyframe<T> {
21 pub time: f32,
22 pub value: T,
23}
24
25pub struct JointTrack {
32 pub joint_index: usize,
34 pub translation: Vec<Keyframe<glam::Vec3>>,
35 pub translation_interpolation: Interpolation,
36 pub rotation: Vec<Keyframe<glam::Quat>>,
37 pub rotation_interpolation: Interpolation,
38 pub scale: Vec<Keyframe<glam::Vec3>>,
39 pub scale_interpolation: Interpolation,
40}
41
42pub struct AnimationClip {
47 pub name: String,
48 pub duration: f32,
49 tracks: Vec<JointTrack>,
50}
51
52impl AnimationClip {
53 pub fn new(name: String, tracks: Vec<JointTrack>) -> Self {
57 let duration = tracks
58 .iter()
59 .flat_map(|t| {
60 t.translation
61 .iter()
62 .map(|k| k.time)
63 .chain(t.rotation.iter().map(|k| k.time))
64 .chain(t.scale.iter().map(|k| k.time))
65 })
66 .fold(0.0f32, f32::max);
67 Self { name, duration, tracks }
68 }
69
70 pub fn sample(&self, time: f32, skeleton: &Skeleton) -> Vec<Transform> {
78 let time = time.clamp(0.0, self.duration);
79 (0..skeleton.joint_count())
80 .map(|i| {
81 let bind = skeleton.joint(i).local_bind_transform;
82 match self.tracks.iter().find(|t| t.joint_index == i) {
83 Some(track) => Transform {
84 translation: sample_vec3(&track.translation, track.translation_interpolation, time)
85 .unwrap_or(bind.translation),
86 rotation: sample_quat(&track.rotation, track.rotation_interpolation, time)
87 .unwrap_or(bind.rotation),
88 scale: sample_vec3(&track.scale, track.scale_interpolation, time).unwrap_or(bind.scale),
89 },
90 None => bind,
91 }
92 })
93 .collect()
94 }
95}
96
97enum Bracket<'a, T> {
100 Empty,
102 Single(&'a T),
105 Between { left: &'a T, right: &'a T, t: f32 },
108}
109
110fn bracket<T>(keyframes: &[Keyframe<T>], time: f32) -> Bracket<'_, T> {
111 if keyframes.is_empty() {
112 return Bracket::Empty;
113 }
114 if keyframes.len() == 1 || time <= keyframes[0].time {
115 return Bracket::Single(&keyframes[0].value);
116 }
117 let last = keyframes.len() - 1;
118 if time >= keyframes[last].time {
119 return Bracket::Single(&keyframes[last].value);
120 }
121
122 let right_index = keyframes.partition_point(|k| k.time <= time);
125 let left = &keyframes[right_index - 1];
126 let right = &keyframes[right_index];
127 let span = right.time - left.time;
128 let t = if span > 0.0 { (time - left.time) / span } else { 0.0 };
129 Bracket::Between { left: &left.value, right: &right.value, t }
130}
131
132fn sample_vec3(keyframes: &[Keyframe<glam::Vec3>], interpolation: Interpolation, time: f32) -> Option<glam::Vec3> {
133 Some(match bracket(keyframes, time) {
134 Bracket::Empty => return None,
135 Bracket::Single(v) => *v,
136 Bracket::Between { left, right, t } => match interpolation {
137 Interpolation::Step => *left,
138 Interpolation::Linear => left.lerp(*right, t),
139 },
140 })
141}
142
143fn sample_quat(keyframes: &[Keyframe<glam::Quat>], interpolation: Interpolation, time: f32) -> Option<glam::Quat> {
144 Some(match bracket(keyframes, time) {
145 Bracket::Empty => return None,
146 Bracket::Single(v) => *v,
147 Bracket::Between { left, right, t } => match interpolation {
148 Interpolation::Step => *left,
149 Interpolation::Linear => left.slerp(*right, t),
150 },
151 })
152}
153
154#[cfg(test)]
155mod tests {
156 use super::*;
157 use super::super::skeleton::Joint;
158
159 fn skeleton_with_one_joint() -> Skeleton {
160 Skeleton::new(vec![Joint {
161 name: "root".to_string(),
162 parent: None,
163 inverse_bind_matrix: glam::Mat4::IDENTITY,
164 local_bind_transform: Transform {
165 translation: glam::Vec3::new(9.0, 9.0, 9.0),
166 ..Transform::IDENTITY
167 },
168 }])
169 }
170
171 fn track_with_translation(keyframes: Vec<Keyframe<glam::Vec3>>, interpolation: Interpolation) -> JointTrack {
172 JointTrack {
173 joint_index: 0,
174 translation: keyframes,
175 translation_interpolation: interpolation,
176 rotation: Vec::new(),
177 rotation_interpolation: Interpolation::Linear,
178 scale: Vec::new(),
179 scale_interpolation: Interpolation::Linear,
180 }
181 }
182
183 #[test]
184 fn time_before_the_first_keyframe_clamps_to_it() {
185 let skeleton = skeleton_with_one_joint();
186 let track = track_with_translation(
187 vec![
188 Keyframe { time: 1.0, value: glam::Vec3::new(1.0, 0.0, 0.0) },
189 Keyframe { time: 2.0, value: glam::Vec3::new(2.0, 0.0, 0.0) },
190 ],
191 Interpolation::Linear,
192 );
193 let clip = AnimationClip::new("clip".to_string(), vec![track]);
194
195 let poses = clip.sample(-5.0, &skeleton);
196 assert_eq!(poses[0].translation, glam::Vec3::new(1.0, 0.0, 0.0));
197 }
198
199 #[test]
200 fn time_after_the_last_keyframe_clamps_to_it() {
201 let skeleton = skeleton_with_one_joint();
202 let track = track_with_translation(
203 vec![
204 Keyframe { time: 1.0, value: glam::Vec3::new(1.0, 0.0, 0.0) },
205 Keyframe { time: 2.0, value: glam::Vec3::new(2.0, 0.0, 0.0) },
206 ],
207 Interpolation::Linear,
208 );
209 let clip = AnimationClip::new("clip".to_string(), vec![track]);
210
211 let poses = clip.sample(100.0, &skeleton);
212 assert_eq!(poses[0].translation, glam::Vec3::new(2.0, 0.0, 0.0));
213 }
214
215 #[test]
216 fn exactly_on_a_keyframe_returns_the_exact_value_no_drift() {
217 let skeleton = skeleton_with_one_joint();
218 let track = track_with_translation(
219 vec![
220 Keyframe { time: 0.0, value: glam::Vec3::new(1.0, 0.0, 0.0) },
221 Keyframe { time: 1.0, value: glam::Vec3::new(2.0, 0.0, 0.0) },
222 Keyframe { time: 2.0, value: glam::Vec3::new(3.0, 0.0, 0.0) },
223 ],
224 Interpolation::Linear,
225 );
226 let clip = AnimationClip::new("clip".to_string(), vec![track]);
227
228 let poses = clip.sample(1.0, &skeleton);
229 assert_eq!(poses[0].translation, glam::Vec3::new(2.0, 0.0, 0.0));
230 }
231
232 #[test]
233 fn midpoint_lerps_linearly() {
234 let skeleton = skeleton_with_one_joint();
235 let track = track_with_translation(
236 vec![
237 Keyframe { time: 0.0, value: glam::Vec3::new(0.0, 0.0, 0.0) },
238 Keyframe { time: 2.0, value: glam::Vec3::new(10.0, 0.0, 0.0) },
239 ],
240 Interpolation::Linear,
241 );
242 let clip = AnimationClip::new("clip".to_string(), vec![track]);
243
244 let poses = clip.sample(1.0, &skeleton);
245 assert_eq!(poses[0].translation, glam::Vec3::new(5.0, 0.0, 0.0));
246 }
247
248 #[test]
249 fn step_interpolation_holds_the_left_keyframe() {
250 let skeleton = skeleton_with_one_joint();
251 let track = track_with_translation(
252 vec![
253 Keyframe { time: 0.0, value: glam::Vec3::new(0.0, 0.0, 0.0) },
254 Keyframe { time: 2.0, value: glam::Vec3::new(10.0, 0.0, 0.0) },
255 ],
256 Interpolation::Step,
257 );
258 let clip = AnimationClip::new("clip".to_string(), vec![track]);
259
260 let poses = clip.sample(1.9, &skeleton);
261 assert_eq!(poses[0].translation, glam::Vec3::new(0.0, 0.0, 0.0));
262 }
263
264 #[test]
265 fn a_partially_animated_joint_falls_back_to_bind_pose_for_untracked_properties() {
266 let skeleton = skeleton_with_one_joint();
267 let track = track_with_translation(
269 vec![Keyframe { time: 0.0, value: glam::Vec3::new(1.0, 2.0, 3.0) }],
270 Interpolation::Linear,
271 );
272 let clip = AnimationClip::new("clip".to_string(), vec![track]);
273
274 let poses = clip.sample(0.0, &skeleton);
275 assert_eq!(poses[0].translation, glam::Vec3::new(1.0, 2.0, 3.0));
276 assert_eq!(poses[0].rotation, glam::Quat::IDENTITY);
277 assert_eq!(poses[0].scale, glam::Vec3::ONE);
278 }
279
280 #[test]
281 fn a_joint_with_no_track_at_all_is_fully_bind_pose() {
282 let skeleton = skeleton_with_one_joint();
283 let clip = AnimationClip::new("clip".to_string(), vec![]);
284
285 let poses = clip.sample(0.0, &skeleton);
286 assert_eq!(poses[0], Transform { translation: glam::Vec3::new(9.0, 9.0, 9.0), ..Transform::IDENTITY });
287 }
288
289 #[test]
290 fn duration_is_the_max_keyframe_time_across_every_track_and_property() {
291 let track = JointTrack {
292 joint_index: 0,
293 translation: vec![Keyframe { time: 1.0, value: glam::Vec3::ZERO }],
294 translation_interpolation: Interpolation::Linear,
295 rotation: vec![Keyframe { time: 5.0, value: glam::Quat::IDENTITY }],
296 rotation_interpolation: Interpolation::Linear,
297 scale: vec![Keyframe { time: 3.0, value: glam::Vec3::ONE }],
298 scale_interpolation: Interpolation::Linear,
299 };
300 let clip = AnimationClip::new("clip".to_string(), vec![track]);
301 assert_eq!(clip.duration, 5.0);
302 }
303}