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