1use gizmo_math::{Quat, Vec3};
2
3use crate::hermite::{hermite_quat, hermite_vec3};
4
5#[derive(Debug, Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub enum TrackError {
15 LengthMismatch {
18 timestamps: usize,
20 values: usize,
22 },
23 NonFiniteTimestamp {
25 index: usize,
27 },
28 UnsortedTimestamps {
30 index: usize,
32 },
33}
34
35impl std::fmt::Display for TrackError {
36 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
37 match self {
38 TrackError::LengthMismatch { timestamps, values } => write!(
39 f,
40 "keyframe timestamp count ({timestamps}) does not match keyframe value count ({values})"
41 ),
42 TrackError::NonFiniteTimestamp { index } => {
43 write!(f, "keyframe timestamp at index {index} is not finite")
44 }
45 TrackError::UnsortedTimestamps { index } => write!(
46 f,
47 "keyframe timestamp at index {index} is not in ascending order"
48 ),
49 }
50 }
51}
52
53impl std::error::Error for TrackError {}
54
55#[derive(Clone, Debug)]
61#[non_exhaustive]
62pub enum Keyframes {
63 Translation(Vec<Vec3>),
65 Rotation(Vec<Quat>),
67 Scale(Vec<Vec3>),
69}
70
71impl Keyframes {
72 pub fn len(&self) -> usize {
74 match self {
75 Keyframes::Translation(v) => v.len(),
76 Keyframes::Rotation(v) => v.len(),
77 Keyframes::Scale(v) => v.len(),
78 }
79 }
80
81 pub fn is_empty(&self) -> bool {
83 self.len() == 0
84 }
85}
86
87#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
91pub enum Interpolation {
92 #[default]
94 Linear,
95 Step,
97 CubicSpline,
100}
101
102#[derive(Clone, Debug)]
109pub struct CubicTangents {
110 pub in_tangents: Keyframes,
112 pub out_tangents: Keyframes,
114}
115
116#[derive(Clone, Debug)]
118#[non_exhaustive]
119pub struct Track {
120 pub target_name: String,
122 pub keyframe_timestamps: Vec<f32>,
124 pub keyframes: Keyframes,
126 pub interpolation: Interpolation,
128 pub tangents: Option<CubicTangents>,
131}
132
133impl Track {
134 pub fn new(
145 target_name: impl Into<String>,
146 keyframe_timestamps: Vec<f32>,
147 keyframes: Keyframes,
148 ) -> Result<Self, TrackError> {
149 let values = keyframes.len();
150 let timestamps = keyframe_timestamps.len();
151 if timestamps != values {
152 return Err(TrackError::LengthMismatch { timestamps, values });
153 }
154
155 let mut prev: Option<f32> = None;
156 for (index, &ts) in keyframe_timestamps.iter().enumerate() {
157 if !ts.is_finite() {
158 return Err(TrackError::NonFiniteTimestamp { index });
159 }
160 if let Some(p) = prev {
161 if ts < p {
162 return Err(TrackError::UnsortedTimestamps { index });
163 }
164 }
165 prev = Some(ts);
166 }
167
168 Ok(Self {
169 target_name: target_name.into(),
170 keyframe_timestamps,
171 keyframes,
172 interpolation: Interpolation::Linear,
173 tangents: None,
174 })
175 }
176
177 pub fn with_interpolation(mut self, interpolation: Interpolation) -> Self {
179 self.interpolation = interpolation;
180 self
181 }
182
183 pub fn with_cubic_tangents(mut self, in_tangents: Keyframes, out_tangents: Keyframes) -> Self {
185 self.interpolation = Interpolation::CubicSpline;
186 self.tangents = Some(CubicTangents { in_tangents, out_tangents });
187 self
188 }
189
190 pub fn duration(&self) -> f32 {
192 self.keyframe_timestamps.last().copied().unwrap_or(0.0)
193 }
194
195 pub fn sample(&self, t: f32) -> InterpolatedValue {
200 if self.keyframe_timestamps.is_empty() {
201 return InterpolatedValue::None;
202 }
203
204 if t <= *self.keyframe_timestamps.first().unwrap() {
205 return self.get_value(0);
206 }
207
208 if t >= *self.keyframe_timestamps.last().unwrap() {
209 return self.get_value(self.keyframe_timestamps.len() - 1);
210 }
211
212 let idx = self.keyframe_timestamps.partition_point(|&ts| ts <= t);
214 let idx1 = idx.clamp(1, self.keyframe_timestamps.len() - 1);
218 let idx0 = idx1 - 1;
219
220 let t0 = self.keyframe_timestamps[idx0];
221 let t1 = self.keyframe_timestamps[idx1];
222 let segment = t1 - t0;
226 let factor = if segment.abs() > f32::EPSILON {
227 ((t - t0) / segment).clamp(0.0, 1.0)
228 } else {
229 0.0
230 };
231
232 match self.effective_interpolation() {
233 Interpolation::Step => self.get_value(idx0),
234 Interpolation::Linear => self.interpolate_linear(idx0, idx1, factor),
235 Interpolation::CubicSpline => self.interpolate_cubic(idx0, idx1, factor, segment),
236 }
237 }
238
239 fn effective_interpolation(&self) -> Interpolation {
242 match self.interpolation {
243 Interpolation::CubicSpline if self.tangents.is_none() => Interpolation::Linear,
244 other => other,
245 }
246 }
247
248 fn get_value(&self, index: usize) -> InterpolatedValue {
249 match &self.keyframes {
253 Keyframes::Translation(v) => match v.get(index) {
254 Some(&val) => InterpolatedValue::Translation(val),
255 None => InterpolatedValue::None,
256 },
257 Keyframes::Rotation(v) => match v.get(index) {
258 Some(&val) => InterpolatedValue::Rotation(val),
259 None => InterpolatedValue::None,
260 },
261 Keyframes::Scale(v) => match v.get(index) {
262 Some(&val) => InterpolatedValue::Scale(val),
263 None => InterpolatedValue::None,
264 },
265 }
266 }
267
268 fn interpolate_linear(&self, idx0: usize, idx1: usize, factor: f32) -> InterpolatedValue {
269 match &self.keyframes {
272 Keyframes::Translation(v) => match (v.get(idx0), v.get(idx1)) {
273 (Some(&v0), Some(&v1)) => InterpolatedValue::Translation(v0.lerp(v1, factor)),
274 _ => InterpolatedValue::None,
275 },
276 Keyframes::Rotation(v) => match (v.get(idx0), v.get(idx1)) {
277 (Some(&v0), Some(&v1)) => InterpolatedValue::Rotation(v0.slerp(v1, factor)),
278 _ => InterpolatedValue::None,
279 },
280 Keyframes::Scale(v) => match (v.get(idx0), v.get(idx1)) {
281 (Some(&v0), Some(&v1)) => InterpolatedValue::Scale(v0.lerp(v1, factor)),
282 _ => InterpolatedValue::None,
283 },
284 }
285 }
286
287 fn interpolate_cubic(
295 &self,
296 idx0: usize,
297 idx1: usize,
298 factor: f32,
299 segment: f32,
300 ) -> InterpolatedValue {
301 let tangents = self.tangents.as_ref().expect("cubic requires tangents");
303 match (&self.keyframes, &tangents.in_tangents, &tangents.out_tangents) {
304 (Keyframes::Translation(v), Keyframes::Translation(in_t), Keyframes::Translation(out_t)) => {
305 match (v.get(idx0), out_t.get(idx0), v.get(idx1), in_t.get(idx1)) {
306 (Some(&p0), Some(&m0), Some(&p1), Some(&m1)) => InterpolatedValue::Translation(
307 hermite_vec3(p0, m0 * segment, p1, m1 * segment, factor),
308 ),
309 _ => self.interpolate_linear(idx0, idx1, factor),
310 }
311 }
312 (Keyframes::Scale(v), Keyframes::Scale(in_t), Keyframes::Scale(out_t)) => {
313 match (v.get(idx0), out_t.get(idx0), v.get(idx1), in_t.get(idx1)) {
314 (Some(&p0), Some(&m0), Some(&p1), Some(&m1)) => InterpolatedValue::Scale(
315 hermite_vec3(p0, m0 * segment, p1, m1 * segment, factor),
316 ),
317 _ => self.interpolate_linear(idx0, idx1, factor),
318 }
319 }
320 (Keyframes::Rotation(v), Keyframes::Rotation(in_t), Keyframes::Rotation(out_t)) => {
321 let scale = |q: Quat, s: f32| Quat::from_xyzw(q.x * s, q.y * s, q.z * s, q.w * s);
322 match (v.get(idx0), out_t.get(idx0), v.get(idx1), in_t.get(idx1)) {
323 (Some(&p0), Some(&m0), Some(&p1), Some(&m1)) => InterpolatedValue::Rotation(
324 hermite_quat(p0, scale(m0, segment), p1, scale(m1, segment), factor),
325 ),
326 _ => self.interpolate_linear(idx0, idx1, factor),
327 }
328 }
329 _ => self.interpolate_linear(idx0, idx1, factor),
331 }
332 }
333}
334
335#[derive(Clone, Copy, Debug, PartialEq)]
339#[non_exhaustive]
340pub enum InterpolatedValue {
341 None,
343 Translation(Vec3),
345 Rotation(Quat),
347 Scale(Vec3),
349}
350
351#[derive(Clone, Debug, Default)]
353#[non_exhaustive]
354pub struct AnimationClip {
355 pub name: String,
357 pub tracks: Vec<Track>,
359}
360
361impl AnimationClip {
362 pub fn duration(&self) -> f32 {
364 self.tracks.iter().map(|t| t.duration()).fold(0.0, f32::max)
365 }
366}
367
368#[cfg(test)]
369mod tests {
370 use super::*;
371
372 const TOL: f32 = 1e-4;
373
374 fn scale_track(interp: Interpolation) -> Track {
375 Track::new(
376 "bone",
377 vec![0.0, 1.0],
378 Keyframes::Scale(vec![Vec3::new(1.0, 1.0, 1.0), Vec3::new(2.0, 4.0, 8.0)]),
379 )
380 .expect("valid track")
381 .with_interpolation(interp)
382 }
383
384 #[test]
385 fn scale_track_linear_non_uniform() {
386 let track = scale_track(Interpolation::Linear);
389 match track.sample(0.5) {
390 InterpolatedValue::Scale(s) => {
391 assert!((s - Vec3::new(1.5, 2.5, 4.5)).length() < TOL, "got {s:?}");
392 }
393 other => panic!("expected Scale, got {other:?}"),
394 }
395 }
396
397 #[test]
398 fn scale_track_endpoints_and_clamp() {
399 let track = scale_track(Interpolation::Linear);
400 assert_eq!(track.sample(0.0), InterpolatedValue::Scale(Vec3::new(1.0, 1.0, 1.0)));
401 assert_eq!(track.sample(1.0), InterpolatedValue::Scale(Vec3::new(2.0, 4.0, 8.0)));
402 assert_eq!(track.sample(-5.0), InterpolatedValue::Scale(Vec3::new(1.0, 1.0, 1.0)));
404 assert_eq!(track.sample(9.0), InterpolatedValue::Scale(Vec3::new(2.0, 4.0, 8.0)));
405 }
406
407 #[test]
408 fn scale_track_step_holds_previous() {
409 let track = scale_track(Interpolation::Step);
412 match track.sample(0.5) {
413 InterpolatedValue::Scale(s) => {
414 assert!((s - Vec3::new(1.0, 1.0, 1.0)).length() < TOL, "step should hold prev, got {s:?}");
415 }
416 other => panic!("expected Scale, got {other:?}"),
417 }
418 }
419
420 #[test]
421 fn scale_track_cubic_uses_real_tangents() {
422 let values = Keyframes::Scale(vec![Vec3::new(1.0, 1.0, 1.0), Vec3::new(2.0, 4.0, 8.0)]);
426 let in_t = Keyframes::Scale(vec![Vec3::ZERO, Vec3::ZERO]);
429 let out_t = Keyframes::Scale(vec![Vec3::new(4.0, 4.0, 4.0), Vec3::ZERO]);
430 let track = Track::new("bone", vec![0.0, 1.0], values)
431 .unwrap()
432 .with_cubic_tangents(in_t, out_t);
433
434 let cubic = match track.sample(0.5) {
435 InterpolatedValue::Scale(s) => s,
436 other => panic!("expected Scale, got {other:?}"),
437 };
438 let linear = Vec3::new(1.5, 2.5, 4.5);
439 assert!((cubic - linear).length() > 0.1, "cubic must differ from linear, got {cubic:?}");
440
441 assert!((cubic.x - 2.0).abs() < TOL, "cubic X expected 2.0, got {}", cubic.x);
445 }
446
447 #[test]
448 fn cubic_without_tangents_falls_back_to_linear() {
449 let mut track = scale_track(Interpolation::CubicSpline);
450 track.tangents = None; match track.sample(0.5) {
452 InterpolatedValue::Scale(s) => assert!((s - Vec3::new(1.5, 2.5, 4.5)).length() < TOL),
453 other => panic!("expected Scale, got {other:?}"),
454 }
455 }
456
457 #[test]
458 fn empty_track_samples_none() {
459 let track = Track::new("bone", vec![], Keyframes::Scale(vec![])).unwrap();
460 assert_eq!(track.sample(0.5), InterpolatedValue::None);
461 }
462}