1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
3pub enum AnimationError {
4 Empty,
5 InvalidKey,
6 UnorderedKeys,
7 InvalidTime,
8}
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum LoopMode {
11 Clamp,
12 Repeat,
13}
14#[derive(Clone, Copy, Debug, PartialEq, Eq)]
15pub enum Interpolation {
16 Step,
17 Linear,
18}
19#[derive(Clone, Copy, Debug, PartialEq)]
20pub struct Keyframe<T> {
21 pub time: f32,
22 pub value: T,
23}
24mod sealed {
25 pub trait Sample {}
26}
27pub trait Sample: sealed::Sample + Copy {
28 fn valid(self) -> bool;
29 fn blend(a: Self, b: Self, amount: f32) -> Self;
30}
31impl sealed::Sample for f32 {}
32impl Sample for f32 {
33 fn valid(self) -> bool {
34 self.is_finite()
35 }
36 fn blend(a: Self, b: Self, amount: f32) -> Self {
37 lerp(a, b, amount)
38 }
39}
40impl sealed::Sample for [f32; 3] {}
41impl Sample for [f32; 3] {
42 fn valid(self) -> bool {
43 self.into_iter().all(f32::is_finite)
44 }
45 fn blend(a: Self, b: Self, amount: f32) -> Self {
46 [
47 lerp(a[0], b[0], amount),
48 lerp(a[1], b[1], amount),
49 lerp(a[2], b[2], amount),
50 ]
51 }
52}
53impl sealed::Sample for [f32; 4] {}
54impl Sample for [f32; 4] {
55 fn valid(self) -> bool {
56 self.into_iter().all(f32::is_finite)
57 && self.into_iter().map(|v| f64::from(v).powi(2)).sum::<f64>() > 1e-20
58 }
59 fn blend(a: Self, b: Self, amount: f32) -> Self {
60 slerp(a, b, amount)
61 }
62}
63
64#[derive(Clone, Debug)]
65pub struct Track<T: Sample> {
66 keys: Vec<Keyframe<T>>,
67 interpolation: Interpolation,
68}
69impl<T: Sample> Track<T> {
70 pub fn new(
71 keys: Vec<Keyframe<T>>,
72 interpolation: Interpolation,
73 ) -> Result<Self, AnimationError> {
74 if keys.is_empty() {
75 return Err(AnimationError::Empty);
76 }
77 if keys
78 .iter()
79 .any(|key| !key.time.is_finite() || key.time < 0.0 || !key.value.valid())
80 {
81 return Err(AnimationError::InvalidKey);
82 }
83 if keys.windows(2).any(|pair| pair[0].time >= pair[1].time) {
84 return Err(AnimationError::UnorderedKeys);
85 }
86 Ok(Self {
87 keys,
88 interpolation,
89 })
90 }
91 pub fn duration(&self) -> f32 {
92 self.keys.last().expect("nonempty track").time
93 }
94 pub fn sample(&self, time: f32, mode: LoopMode) -> Result<T, AnimationError> {
95 let time = playback_time(time, self.duration(), mode)?;
96 let upper = self.keys.partition_point(|key| key.time <= time);
97 if upper == 0 {
98 return Ok(T::blend(self.keys[0].value, self.keys[0].value, 0.0));
99 }
100 let a = &self.keys[upper - 1];
101 if upper == self.keys.len() || self.interpolation == Interpolation::Step {
102 return Ok(T::blend(a.value, a.value, 0.0));
103 }
104 let b = &self.keys[upper];
105 Ok(T::blend(
106 a.value,
107 b.value,
108 (time - a.time) / (b.time - a.time),
109 ))
110 }
111}
112pub type ScalarTrack = Track<f32>;
113pub type Vec3Track = Track<[f32; 3]>;
114pub type QuatTrack = Track<[f32; 4]>;
116
117#[derive(Clone, Copy, Debug, PartialEq)]
118pub struct SpriteFrame {
119 pub index: u32,
120 pub duration: f32,
121}
122#[derive(Clone, Debug)]
123pub struct SpriteClip {
124 frames: Vec<SpriteFrame>,
125 ends: Vec<f32>,
126 duration: f32,
127}
128impl SpriteClip {
129 pub fn new(frames: Vec<SpriteFrame>) -> Result<Self, AnimationError> {
130 if frames.is_empty() {
131 return Err(AnimationError::Empty);
132 }
133 let mut duration = 0.0;
134 let mut ends = Vec::with_capacity(frames.len());
135 for frame in &frames {
136 if !frame.duration.is_finite() || frame.duration <= 0.0 {
137 return Err(AnimationError::InvalidKey);
138 }
139 duration += frame.duration;
140 if !duration.is_finite() || ends.last().is_some_and(|previous| *previous >= duration) {
141 return Err(AnimationError::InvalidKey);
142 }
143 ends.push(duration);
144 }
145 Ok(Self {
146 frames,
147 ends,
148 duration,
149 })
150 }
151 pub fn duration(&self) -> f32 {
152 self.duration
153 }
154 pub fn frame(&self, time: f32, mode: LoopMode) -> Result<u32, AnimationError> {
155 let time = playback_time(time, self.duration, mode)?;
156 let index = self
157 .ends
158 .partition_point(|end| *end <= time)
159 .min(self.frames.len() - 1);
160 Ok(self.frames[index].index)
161 }
162}
163fn playback_time(time: f32, duration: f32, mode: LoopMode) -> Result<f32, AnimationError> {
164 if !time.is_finite() {
165 return Err(AnimationError::InvalidTime);
166 }
167 Ok(if mode == LoopMode::Repeat && duration > 0.0 {
168 time.rem_euclid(duration)
169 } else {
170 time.clamp(0.0, duration)
171 })
172}
173fn lerp(a: f32, b: f32, amount: f32) -> f32 {
174 (f64::from(a) * (1.0 - f64::from(amount)) + f64::from(b) * f64::from(amount)) as f32
175}
176fn normalize(q: [f32; 4]) -> [f32; 4] {
177 let length = q
178 .into_iter()
179 .map(|v| f64::from(v).powi(2))
180 .sum::<f64>()
181 .sqrt();
182 q.map(|v| (f64::from(v) / length) as f32)
183}
184fn slerp(a: [f32; 4], b: [f32; 4], amount: f32) -> [f32; 4] {
185 let a = normalize(a);
186 let mut b = normalize(b);
187 let mut dot = a.iter().zip(b).map(|(a, b)| a * b).sum::<f32>();
188 if dot < 0.0 {
189 b = b.map(|v| -v);
190 dot = -dot;
191 }
192 dot = dot.clamp(-1.0, 1.0);
193 let (wa, wb) = if dot > 0.9995 {
194 (1.0 - amount, amount)
195 } else {
196 let angle = dot.acos();
197 (
198 ((1.0 - amount) * angle).sin() / angle.sin(),
199 (amount * angle).sin() / angle.sin(),
200 )
201 };
202 normalize(std::array::from_fn(|i| a[i] * wa + b[i] * wb))
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 #[test]
209 fn vector_keys_clamp_interpolate_and_repeat() {
210 let track = Vec3Track::new(
211 vec![
212 Keyframe {
213 time: 0.0,
214 value: [0.0; 3],
215 },
216 Keyframe {
217 time: 2.0,
218 value: [2.0, 4.0, 6.0],
219 },
220 ],
221 Interpolation::Linear,
222 )
223 .unwrap();
224 assert_eq!(track.sample(1.0, LoopMode::Clamp).unwrap(), [1.0, 2.0, 3.0]);
225 assert_eq!(track.sample(-1.0, LoopMode::Clamp).unwrap(), [0.0; 3]);
226 assert_eq!(
227 track.sample(10.0, LoopMode::Clamp).unwrap(),
228 [2.0, 4.0, 6.0]
229 );
230 assert_eq!(
231 track.sample(3.0, LoopMode::Repeat).unwrap(),
232 [1.0, 2.0, 3.0]
233 );
234 assert_eq!(
235 track.sample(f32::NAN, LoopMode::Clamp),
236 Err(AnimationError::InvalidTime)
237 );
238 }
239 #[test]
240 fn quaternion_uses_shortest_arc_and_normalizes_keys() {
241 let track = QuatTrack::new(
242 vec![
243 Keyframe {
244 time: 0.0,
245 value: [0.0, 0.0, 0.0, 2.0],
246 },
247 Keyframe {
248 time: 1.0,
249 value: [0.0, 0.0, 0.0, -3.0],
250 },
251 ],
252 Interpolation::Linear,
253 )
254 .unwrap();
255 for time in [0.0, 0.5, 1.0] {
256 assert!((track.sample(time, LoopMode::Clamp).unwrap()[3].abs() - 1.0).abs() < 1e-6);
257 }
258 let half_turn = QuatTrack::new(
259 vec![
260 Keyframe {
261 time: 0.0,
262 value: [0.0, 0.0, 0.0, 1.0],
263 },
264 Keyframe {
265 time: 1.0,
266 value: [0.0, 0.0, 1.0, 0.0],
267 },
268 ],
269 Interpolation::Linear,
270 )
271 .unwrap();
272 let q = half_turn.sample(0.5, LoopMode::Clamp).unwrap();
273 assert!((q[2] - std::f32::consts::FRAC_1_SQRT_2).abs() < 1e-5);
274 assert!((q[3] - std::f32::consts::FRAC_1_SQRT_2).abs() < 1e-5);
275 }
276 #[test]
277 fn sprite_frames_use_individual_durations_and_exact_boundaries() {
278 let clip = SpriteClip::new(vec![
279 SpriteFrame {
280 index: 4,
281 duration: 0.25,
282 },
283 SpriteFrame {
284 index: 7,
285 duration: 0.75,
286 },
287 ])
288 .unwrap();
289 assert_eq!(clip.frame(0.24, LoopMode::Clamp).unwrap(), 4);
290 assert_eq!(clip.frame(0.25, LoopMode::Clamp).unwrap(), 7);
291 assert_eq!(clip.frame(1.0, LoopMode::Clamp).unwrap(), 7);
292 assert_eq!(clip.frame(1.0, LoopMode::Repeat).unwrap(), 4);
293 }
294 #[test]
295 fn malformed_keys_and_zero_quaternions_are_rejected() {
296 assert!(matches!(
297 QuatTrack::new(
298 vec![Keyframe {
299 time: 0.0,
300 value: [0.0; 4]
301 }],
302 Interpolation::Linear
303 ),
304 Err(AnimationError::InvalidKey)
305 ));
306 assert!(matches!(
307 ScalarTrack::new(
308 vec![
309 Keyframe {
310 time: 1.0,
311 value: 1.0
312 },
313 Keyframe {
314 time: 1.0,
315 value: 2.0
316 }
317 ],
318 Interpolation::Linear
319 ),
320 Err(AnimationError::UnorderedKeys)
321 ));
322 assert!(matches!(
323 SpriteClip::new(vec![SpriteFrame {
324 index: 0,
325 duration: 0.0
326 }]),
327 Err(AnimationError::InvalidKey)
328 ));
329 }
330}