Skip to main content

ling_graphics/
animation.rs

1use crate::color::Color;
2use crate::scene::Transform;
3use glam::{Quat, Vec2, Vec3, Vec4};
4
5// ── Lerp trait ────────────────────────────────────────────────────────────────
6
7pub trait Lerp: Clone + Send + Sync + 'static {
8    fn lerp_by(&self, other: &Self, t: f32) -> Self;
9}
10
11impl Lerp for f32 {
12    fn lerp_by(&self, o: &Self, t: f32) -> Self {
13        self + (o - self) * t
14    }
15}
16impl Lerp for f64 {
17    fn lerp_by(&self, o: &Self, t: f32) -> Self {
18        self + (o - self) * t as f64
19    }
20}
21impl Lerp for Vec2 {
22    fn lerp_by(&self, o: &Self, t: f32) -> Self {
23        self.lerp(*o, t)
24    }
25}
26impl Lerp for Vec3 {
27    fn lerp_by(&self, o: &Self, t: f32) -> Self {
28        self.lerp(*o, t)
29    }
30}
31impl Lerp for Vec4 {
32    fn lerp_by(&self, o: &Self, t: f32) -> Self {
33        self.lerp(*o, t)
34    }
35}
36impl Lerp for Quat {
37    fn lerp_by(&self, o: &Self, t: f32) -> Self {
38        self.slerp(*o, t)
39    }
40}
41impl Lerp for Color {
42    fn lerp_by(&self, o: &Self, t: f32) -> Self {
43        self.lerp(*o, t)
44    }
45}
46impl Lerp for Transform {
47    fn lerp_by(&self, o: &Self, t: f32) -> Self {
48        self.lerp(o, t)
49    }
50}
51
52// ── Ease functions ────────────────────────────────────────────────────────────
53
54#[derive(Debug, Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
55pub enum EaseFunction {
56    Linear,
57    Step,
58    QuadIn,
59    QuadOut,
60    QuadInOut,
61    CubicIn,
62    CubicOut,
63    CubicInOut,
64    SineIn,
65    SineOut,
66    SineInOut,
67    ExpoIn,
68    ExpoOut,
69    ExpoInOut,
70    ElasticIn,
71    ElasticOut,
72    BackIn,
73    BackOut,
74    BackInOut,
75    BounceOut,
76    BounceIn,
77}
78
79impl EaseFunction {
80    pub fn apply(self, t: f32) -> f32 {
81        let t = t.clamp(0.0, 1.0);
82        match self {
83            EaseFunction::Linear => t,
84            EaseFunction::Step => {
85                if t < 1.0 {
86                    0.0
87                } else {
88                    1.0
89                }
90            },
91            EaseFunction::QuadIn => t * t,
92            EaseFunction::QuadOut => t * (2.0 - t),
93            EaseFunction::QuadInOut => {
94                if t < 0.5 {
95                    2.0 * t * t
96                } else {
97                    -1.0 + (4.0 - 2.0 * t) * t
98                }
99            },
100            EaseFunction::CubicIn => t * t * t,
101            EaseFunction::CubicOut => {
102                let s = t - 1.0;
103                s * s * s + 1.0
104            },
105            EaseFunction::CubicInOut => {
106                if t < 0.5 {
107                    4.0 * t * t * t
108                } else {
109                    (t - 1.0) * (2.0 * t - 2.0) * (2.0 * t - 2.0) + 1.0
110                }
111            },
112            EaseFunction::SineIn => 1.0 - ((t * std::f32::consts::FRAC_PI_2).cos()),
113            EaseFunction::SineOut => (t * std::f32::consts::FRAC_PI_2).sin(),
114            EaseFunction::SineInOut => 0.5 * (1.0 - (std::f32::consts::PI * t).cos()),
115            EaseFunction::ExpoIn => {
116                if t == 0.0 {
117                    0.0
118                } else {
119                    (2.0_f32).powf(10.0 * t - 10.0)
120                }
121            },
122            EaseFunction::ExpoOut => {
123                if t == 1.0 {
124                    1.0
125                } else {
126                    1.0 - (2.0_f32).powf(-10.0 * t)
127                }
128            },
129            EaseFunction::ExpoInOut => {
130                if t == 0.0 {
131                    return 0.0;
132                }
133                if t == 1.0 {
134                    return 1.0;
135                }
136                if t < 0.5 {
137                    (2.0_f32).powf(20.0 * t - 10.0) / 2.0
138                } else {
139                    (2.0 - (2.0_f32).powf(-20.0 * t + 10.0)) / 2.0
140                }
141            },
142            EaseFunction::ElasticIn => {
143                let c = 2.0 * std::f32::consts::PI / 3.0;
144                if t == 0.0 {
145                    0.0
146                } else if t == 1.0 {
147                    1.0
148                } else {
149                    -(2.0_f32).powf(10.0 * t - 10.0) * ((10.0 * t - 10.75) * c).sin()
150                }
151            },
152            EaseFunction::ElasticOut => {
153                let c = 2.0 * std::f32::consts::PI / 3.0;
154                if t == 0.0 {
155                    0.0
156                } else if t == 1.0 {
157                    1.0
158                } else {
159                    (2.0_f32).powf(-10.0 * t) * ((10.0 * t - 0.75) * c).sin() + 1.0
160                }
161            },
162            EaseFunction::BackIn => {
163                let c1 = 1.70158;
164                let c3 = c1 + 1.0;
165                c3 * t * t * t - c1 * t * t
166            },
167            EaseFunction::BackOut => {
168                let c1 = 1.70158;
169                let c3 = c1 + 1.0;
170                1.0 + c3 * (t - 1.0).powi(3) + c1 * (t - 1.0).powi(2)
171            },
172            EaseFunction::BackInOut => {
173                let c2 = 1.70158 * 1.525;
174                if t < 0.5 {
175                    ((2.0 * t).powi(2) * ((c2 + 1.0) * 2.0 * t - c2)) / 2.0
176                } else {
177                    ((2.0 * t - 2.0).powi(2) * ((c2 + 1.0) * (2.0 * t - 2.0) + c2) + 2.0) / 2.0
178                }
179            },
180            EaseFunction::BounceOut => bounce_out(t),
181            EaseFunction::BounceIn => 1.0 - bounce_out(1.0 - t),
182        }
183    }
184}
185
186fn bounce_out(t: f32) -> f32 {
187    let n1 = 7.5625;
188    let d1 = 2.75;
189    if t < 1.0 / d1 {
190        n1 * t * t
191    } else if t < 2.0 / d1 {
192        let t = t - 1.5 / d1;
193        n1 * t * t + 0.75
194    } else if t < 2.5 / d1 {
195        let t = t - 2.25 / d1;
196        n1 * t * t + 0.9375
197    } else {
198        let t = t - 2.625 / d1;
199        n1 * t * t + 0.984375
200    }
201}
202
203// ── Keyframe & Track ──────────────────────────────────────────────────────────
204
205#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
206pub struct Keyframe<T: Lerp> {
207    pub time: f32,
208    pub value: T,
209    pub ease: EaseFunction,
210}
211
212pub struct Track<T: Lerp> {
213    pub keyframes: Vec<Keyframe<T>>,
214}
215
216impl<T: Lerp> Track<T> {
217    pub fn new() -> Self {
218        Self { keyframes: Vec::new() }
219    }
220
221    pub fn add(mut self, time: f32, value: T, ease: EaseFunction) -> Self {
222        self.keyframes.push(Keyframe { time, value, ease });
223        self.keyframes
224            .sort_by(|a, b| a.time.partial_cmp(&b.time).unwrap());
225        self
226    }
227
228    pub fn sample(&self, t: f32) -> Option<T> {
229        if self.keyframes.is_empty() {
230            return None;
231        }
232        if t <= self.keyframes[0].time {
233            return Some(self.keyframes[0].value.clone());
234        }
235        let last = self.keyframes.last().unwrap();
236        if t >= last.time {
237            return Some(last.value.clone());
238        }
239        for i in 0..self.keyframes.len() - 1 {
240            let kf0 = &self.keyframes[i];
241            let kf1 = &self.keyframes[i + 1];
242            if t >= kf0.time && t <= kf1.time {
243                let local = (t - kf0.time) / (kf1.time - kf0.time);
244                let eased = kf0.ease.apply(local);
245                return Some(kf0.value.lerp_by(&kf1.value, eased));
246            }
247        }
248        None
249    }
250
251    pub fn duration(&self) -> f32 {
252        self.keyframes.last().map(|k| k.time).unwrap_or(0.0)
253    }
254}
255
256impl<T: Lerp> Default for Track<T> {
257    fn default() -> Self {
258        Self::new()
259    }
260}
261
262// ── Timeline ──────────────────────────────────────────────────────────────────
263
264/// Named animation clips, played by name.
265#[derive(Debug, Default)]
266pub struct Timeline {
267    pub time: f32,
268    pub playing: bool,
269    pub looping: bool,
270    pub speed: f32,
271    duration: f32,
272}
273
274impl Timeline {
275    pub fn new(duration: f32) -> Self {
276        Self {
277            time: 0.0,
278            playing: false,
279            looping: false,
280            speed: 1.0,
281            duration,
282        }
283    }
284
285    pub fn play(&mut self) {
286        self.playing = true;
287    }
288
289    pub fn pause(&mut self) {
290        self.playing = false;
291    }
292
293    pub fn stop(&mut self) {
294        self.playing = false;
295        self.time = 0.0;
296    }
297
298    pub fn tick(&mut self, dt: f32) {
299        if !self.playing {
300            return;
301        }
302        self.time += dt * self.speed;
303        if self.time >= self.duration {
304            if self.looping {
305                self.time %= self.duration;
306            } else {
307                self.time = self.duration;
308                self.playing = false;
309            }
310        }
311    }
312
313    pub fn normalized_time(&self) -> f32 {
314        if self.duration <= 0.0 {
315            return 0.0;
316        }
317        (self.time / self.duration).clamp(0.0, 1.0)
318    }
319}