1use super::skeleton::Pose;
25
26#[derive(Copy, Clone, Debug, Eq, PartialEq)]
28pub enum Channel {
29 Translation,
31 Rotation,
33 Scale,
35}
36
37#[derive(Copy, Clone, Debug, Eq, PartialEq)]
39pub enum Interpolation {
40 Step,
42 Linear,
44}
45
46#[derive(Copy, Clone, Debug)]
49pub enum TrackValue {
50 Vec3(glam::Vec3),
52 Quat(glam::Quat),
54}
55
56#[derive(Clone, Debug)]
59pub enum TrackValues {
60 Vec3(Vec<glam::Vec3>),
62 Quat(Vec<glam::Quat>),
64}
65
66impl TrackValues {
67 fn len(&self) -> usize {
68 match self {
69 TrackValues::Vec3(v) => v.len(),
70 TrackValues::Quat(v) => v.len(),
71 }
72 }
73}
74
75#[derive(Clone, Debug)]
80pub struct Sampler {
81 pub interpolation: Interpolation,
83 pub times: Vec<f32>,
85 pub values: TrackValues,
87}
88
89impl Sampler {
90 pub fn sample(&self, t: f32) -> TrackValue {
93 debug_assert!(!self.times.is_empty(), "sampler has no keyframes");
94 debug_assert_eq!(
95 self.times.len(),
96 self.values.len(),
97 "times/values length mismatch"
98 );
99
100 let n = self.times.len();
101 if t <= self.times[0] {
102 return self.value_at(0);
103 }
104 if t >= self.times[n - 1] {
105 return self.value_at(n - 1);
106 }
107
108 let i = self.times.partition_point(|&x| x <= t).saturating_sub(1);
110 let j = i + 1;
111 match self.interpolation {
112 Interpolation::Step => self.value_at(i),
113 Interpolation::Linear => {
114 let t0 = self.times[i];
115 let t1 = self.times[j];
116 let alpha = (t - t0) / (t1 - t0);
117 self.lerp(i, j, alpha)
118 }
119 }
120 }
121
122 fn value_at(&self, i: usize) -> TrackValue {
123 match &self.values {
124 TrackValues::Vec3(v) => TrackValue::Vec3(v[i]),
125 TrackValues::Quat(v) => TrackValue::Quat(v[i]),
126 }
127 }
128
129 fn lerp(&self, a: usize, b: usize, alpha: f32) -> TrackValue {
130 match &self.values {
131 TrackValues::Vec3(v) => TrackValue::Vec3(v[a].lerp(v[b], alpha)),
132 TrackValues::Quat(v) => TrackValue::Quat(v[a].slerp(v[b], alpha)),
133 }
134 }
135}
136
137#[derive(Clone, Debug)]
139pub struct Track {
140 pub joint: usize,
142 pub channel: Channel,
144 pub sampler: Sampler,
146}
147
148#[derive(Clone, Debug)]
150pub struct AnimationClip {
151 pub duration: f32,
153 pub tracks: Vec<Track>,
155}
156
157impl AnimationClip {
158 pub fn sample_into(&self, t: f32, pose: &mut Pose) {
169 let mut affected: Vec<(usize, [Option<TrackValue>; 3])> = Vec::new();
172
173 for track in &self.tracks {
174 if track.joint >= pose.local_transforms.len() {
175 continue;
176 }
177 let v = track.sampler.sample(t);
178 let slot = channel_slot(track.channel);
179
180 if let Some((_, channels)) = affected.iter_mut().find(|(j, _)| *j == track.joint) {
181 channels[slot] = Some(v);
182 } else {
183 let mut channels = [None, None, None];
184 channels[slot] = Some(v);
185 affected.push((track.joint, channels));
186 }
187 }
188
189 for (joint_idx, channels) in affected {
190 let local = &mut pose.local_transforms[joint_idx];
191 let (s_old, r_old, t_old) = local.to_scale_rotation_translation();
192 let t_new = match channels[0] {
193 Some(TrackValue::Vec3(v)) => v,
194 _ => t_old,
195 };
196 let r_new = match channels[1] {
197 Some(TrackValue::Quat(q)) => q,
198 _ => r_old,
199 };
200 let s_new = match channels[2] {
201 Some(TrackValue::Vec3(v)) => v,
202 _ => s_old,
203 };
204 *local = glam::Affine3A::from_scale_rotation_translation(s_new, r_new, t_new);
205 }
206 }
207}
208
209fn channel_slot(c: Channel) -> usize {
210 match c {
211 Channel::Translation => 0,
212 Channel::Rotation => 1,
213 Channel::Scale => 2,
214 }
215}
216
217#[cfg(test)]
218mod tests {
219 use super::*;
220 use glam::{Quat, Vec3};
221
222 fn approx_eq_vec3(a: Vec3, b: Vec3, eps: f32) -> bool {
223 (a - b).length() < eps
224 }
225
226 fn approx_eq_quat(a: Quat, b: Quat, eps: f32) -> bool {
227 (a.dot(b).abs() - 1.0).abs() < eps
229 }
230
231 #[test]
232 fn linear_sampler_lerps_vec3_between_keyframes() {
233 let s = Sampler {
234 interpolation: Interpolation::Linear,
235 times: vec![0.0, 1.0, 2.0],
236 values: TrackValues::Vec3(vec![Vec3::ZERO, Vec3::new(2.0, 0.0, 0.0), Vec3::ZERO]),
237 };
238 match s.sample(0.5) {
239 TrackValue::Vec3(v) => assert!(approx_eq_vec3(v, Vec3::new(1.0, 0.0, 0.0), 1e-5)),
240 _ => panic!("wrong variant"),
241 }
242 match s.sample(1.75) {
243 TrackValue::Vec3(v) => assert!(approx_eq_vec3(v, Vec3::new(0.5, 0.0, 0.0), 1e-5)),
244 _ => panic!("wrong variant"),
245 }
246 }
247
248 #[test]
249 fn step_sampler_holds_lower_keyframe() {
250 let s = Sampler {
251 interpolation: Interpolation::Step,
252 times: vec![0.0, 1.0, 2.0],
253 values: TrackValues::Vec3(vec![Vec3::ZERO, Vec3::X, Vec3::Y]),
254 };
255 match s.sample(0.999) {
257 TrackValue::Vec3(v) => assert_eq!(v, Vec3::ZERO),
258 _ => panic!(),
259 }
260 match s.sample(1.0) {
262 TrackValue::Vec3(v) => assert_eq!(v, Vec3::X),
263 _ => panic!(),
264 }
265 match s.sample(1.5) {
266 TrackValue::Vec3(v) => assert_eq!(v, Vec3::X),
267 _ => panic!(),
268 }
269 }
270
271 #[test]
272 fn sampler_clamps_outside_range() {
273 let s = Sampler {
274 interpolation: Interpolation::Linear,
275 times: vec![0.0, 1.0],
276 values: TrackValues::Vec3(vec![Vec3::ZERO, Vec3::X]),
277 };
278 match s.sample(-5.0) {
279 TrackValue::Vec3(v) => assert_eq!(v, Vec3::ZERO),
280 _ => panic!(),
281 }
282 match s.sample(100.0) {
283 TrackValue::Vec3(v) => assert_eq!(v, Vec3::X),
284 _ => panic!(),
285 }
286 }
287
288 #[test]
289 fn quat_sampler_slerps_between_keyframes() {
290 let s = Sampler {
291 interpolation: Interpolation::Linear,
292 times: vec![0.0, 1.0],
293 values: TrackValues::Quat(vec![
294 Quat::IDENTITY,
295 Quat::from_rotation_x(std::f32::consts::FRAC_PI_2),
296 ]),
297 };
298 match s.sample(0.5) {
299 TrackValue::Quat(q) => {
300 let expected = Quat::from_rotation_x(std::f32::consts::FRAC_PI_4);
301 assert!(approx_eq_quat(q, expected, 1e-4), "got {q:?}");
302 }
303 _ => panic!(),
304 }
305 }
306
307 #[test]
308 fn sample_into_overwrites_only_animated_channels() {
309 let mut pose = Pose::identity(2);
311 pose.local_transforms[1] = glam::Affine3A::from_translation(Vec3::new(0.0, 0.0, 2.0));
312
313 let clip = AnimationClip {
316 duration: 1.0,
317 tracks: vec![Track {
318 joint: 1,
319 channel: Channel::Rotation,
320 sampler: Sampler {
321 interpolation: Interpolation::Linear,
322 times: vec![0.0, 1.0],
323 values: TrackValues::Quat(vec![
324 Quat::IDENTITY,
325 Quat::from_rotation_x(std::f32::consts::FRAC_PI_2),
326 ]),
327 },
328 }],
329 };
330
331 clip.sample_into(0.5, &mut pose);
332
333 let (scale, rot, trans) = pose.local_transforms[1].to_scale_rotation_translation();
334 assert!(approx_eq_vec3(trans, Vec3::new(0.0, 0.0, 2.0), 1e-4));
336 let expected_rot = Quat::from_rotation_x(std::f32::consts::FRAC_PI_4);
338 assert!(approx_eq_quat(rot, expected_rot, 1e-4));
339 assert!(approx_eq_vec3(scale, Vec3::ONE, 1e-4));
341 }
342
343 #[test]
344 fn sample_into_two_tracks_compose_correctly() {
345 let mut pose = Pose::identity(1);
347 let clip = AnimationClip {
348 duration: 2.0,
349 tracks: vec![
350 Track {
351 joint: 0,
352 channel: Channel::Translation,
353 sampler: Sampler {
354 interpolation: Interpolation::Linear,
355 times: vec![0.0, 2.0],
356 values: TrackValues::Vec3(vec![Vec3::ZERO, Vec3::new(4.0, 0.0, 0.0)]),
357 },
358 },
359 Track {
360 joint: 0,
361 channel: Channel::Scale,
362 sampler: Sampler {
363 interpolation: Interpolation::Step,
364 times: vec![0.0, 1.0],
365 values: TrackValues::Vec3(vec![Vec3::ONE, Vec3::splat(2.0)]),
366 },
367 },
368 ],
369 };
370
371 clip.sample_into(1.5, &mut pose);
372
373 let (scale, _rot, trans) = pose.local_transforms[0].to_scale_rotation_translation();
374 assert!(approx_eq_vec3(trans, Vec3::new(3.0, 0.0, 0.0), 1e-4));
375 assert!(approx_eq_vec3(scale, Vec3::splat(2.0), 1e-4));
376 }
377
378 #[test]
379 fn out_of_range_joint_index_is_skipped() {
380 let mut pose = Pose::identity(2);
381 let clip = AnimationClip {
382 duration: 1.0,
383 tracks: vec![Track {
384 joint: 99,
385 channel: Channel::Translation,
386 sampler: Sampler {
387 interpolation: Interpolation::Step,
388 times: vec![0.0],
389 values: TrackValues::Vec3(vec![Vec3::new(1.0, 2.0, 3.0)]),
390 },
391 }],
392 };
393 clip.sample_into(0.5, &mut pose);
394 assert_eq!(pose.local_transforms[0], glam::Affine3A::IDENTITY);
396 assert_eq!(pose.local_transforms[1], glam::Affine3A::IDENTITY);
397 }
398}