Skip to main content

motion_canvas_rs/engine/animation/
tween.rs

1use crate::engine::animation::base::Animation;
2use crate::engine::nodes::{PathData, PathNode};
3use glam::Vec2;
4use std::sync::{Arc, Mutex};
5use std::time::Duration;
6use vello::kurbo::Affine;
7use vello::peniko::Color;
8
9const DEFAULT_EASING: fn(f32) -> f32 = crate::engine::easings::cubic_in_out;
10
11fn lerp(a: f32, b: f32, t: f32) -> f32 {
12    a + (b - a) * t
13}
14
15pub trait Tweenable: Clone + Send + Sync + std::fmt::Debug + 'static {
16    fn interpolate(a: &Self, b: &Self, t: f32) -> Self;
17    fn state_hash(&self) -> u64;
18}
19
20impl Tweenable for f32 {
21    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
22        lerp(*a, *b, t)
23    }
24    fn state_hash(&self) -> u64 {
25        crate::engine::util::hash::hash_f32(*self)
26    }
27}
28
29impl Tweenable for Vec2 {
30    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
31        Vec2::new(lerp(a.x, b.x, t), lerp(a.y, b.y, t))
32    }
33    fn state_hash(&self) -> u64 {
34        crate::engine::util::hash::combine_hashes(
35            crate::engine::util::hash::hash_f32(self.x),
36            crate::engine::util::hash::hash_f32(self.y),
37        )
38    }
39}
40
41impl Tweenable for Vec<Vec2> {
42    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
43        if a.len() == b.len() {
44            a.iter()
45                .zip(b.iter())
46                .map(|(v1, v2)| Vec2::interpolate(v1, v2, t))
47                .collect()
48        } else if t >= 1.0 {
49            b.clone()
50        } else {
51            a.clone()
52        }
53    }
54    fn state_hash(&self) -> u64 {
55        let mut h = crate::engine::util::hash::Hasher::new();
56        for v in self {
57            h.update_u64(v.state_hash());
58        }
59        h.finish()
60    }
61}
62
63impl Tweenable for bool {
64    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
65        if t >= 1.0 {
66            *b
67        } else {
68            *a
69        }
70    }
71    fn state_hash(&self) -> u64 {
72        if *self {
73            1
74        } else {
75            0
76        }
77    }
78}
79
80impl Tweenable for String {
81    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
82        if t >= 1.0 {
83            b.clone()
84        } else {
85            a.clone()
86        }
87    }
88    fn state_hash(&self) -> u64 {
89        crate::engine::util::hash::hash_str(self)
90    }
91}
92
93impl Tweenable for Color {
94    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
95        let t = t.clamp(0.0, 1.0);
96        Color::rgba8(
97            lerp(a.r as f32, b.r as f32, t) as u8,
98            lerp(a.g as f32, b.g as f32, t) as u8,
99            lerp(a.b as f32, b.b as f32, t) as u8,
100            lerp(a.a as f32, b.a as f32, t) as u8,
101        )
102    }
103    fn state_hash(&self) -> u64 {
104        let mut h = crate::engine::util::hash::Hasher::new();
105        h.update_bytes(&[self.r, self.g, self.b, self.a]);
106        h.finish()
107    }
108}
109
110impl Tweenable for Affine {
111    fn interpolate(a: &Self, b: &Self, t: f32) -> Self {
112        let t = t as f64;
113        let c1 = a.as_coeffs();
114        let c2 = b.as_coeffs();
115        Affine::new([
116            c1[0] + (c2[0] - c1[0]) * t,
117            c1[1] + (c2[1] - c1[1]) * t,
118            c1[2] + (c2[2] - c1[2]) * t,
119            c1[3] + (c2[3] - c1[3]) * t,
120            c1[4] + (c2[4] - c1[4]) * t,
121            c1[5] + (c2[5] - c1[5]) * t,
122        ])
123    }
124    fn state_hash(&self) -> u64 {
125        let c = self.as_coeffs();
126        let mut h = crate::engine::util::hash::Hasher::new();
127        for val in c {
128            h.update_u64(val.to_bits() as u64);
129        }
130        h.finish()
131    }
132}
133
134/// SignalData now only stores the current value, allowing multiple animations
135/// to control it sequentially or in parallel without state interference.
136pub struct SignalData<T> {
137    pub value: T,
138    pub initial: T,
139}
140
141#[derive(Clone)]
142pub struct Signal<T> {
143    pub data: Arc<Mutex<SignalData<T>>>,
144}
145
146pub trait FromVec2: Send + Sync + 'static {
147    fn from_vec2(v: Vec2) -> Self;
148}
149
150impl FromVec2 for Vec2 {
151    fn from_vec2(v: Vec2) -> Self {
152        v
153    }
154}
155
156impl FromVec2 for Affine {
157    fn from_vec2(v: Vec2) -> Self {
158        Affine::translate((v.x as f64, v.y as f64))
159    }
160}
161
162pub enum Target<T> {
163    Fixed(T),
164    Lazy(Arc<dyn Fn(&T) -> T + Send + Sync>),
165}
166
167impl<T: Clone> Clone for Target<T> {
168    fn clone(&self) -> Self {
169        match self {
170            Self::Fixed(v) => Self::Fixed(v.clone()),
171            Self::Lazy(f) => Self::Lazy(f.clone()),
172        }
173    }
174}
175
176impl<T: Tweenable + PartialEq> Signal<T> {
177    pub fn new(value: T) -> Self {
178        Self {
179            data: Arc::new(Mutex::new(SignalData {
180                value: value.clone(),
181                initial: value,
182            })),
183        }
184    }
185
186    pub fn get(&self) -> T {
187        self.data.lock().unwrap().value.clone()
188    }
189
190    pub fn set(&self, value: T) {
191        let mut data = self.data.lock().unwrap();
192        if data.value != value {
193            data.value = value;
194        }
195    }
196
197    pub fn state_hash(&self) -> u64 {
198        self.data.lock().unwrap().value.state_hash()
199    }
200
201    pub fn reset(&self) {
202        let mut data = self.data.lock().unwrap();
203        data.value = data.initial.clone();
204    }
205
206    pub fn to(&self, target: T, duration: Duration) -> SignalTween<T> {
207        SignalTween {
208            data: self.data.clone(),
209            start_value: None,
210            target: Target::Fixed(target),
211            target_value: None,
212            duration,
213            elapsed: Duration::ZERO,
214            easing: DEFAULT_EASING,
215        }
216    }
217
218    pub fn to_lazy<F>(&self, factory: F, duration: Duration) -> SignalTween<T>
219    where
220        F: Fn(&T) -> T + Send + Sync + 'static,
221    {
222        SignalTween {
223            data: self.data.clone(),
224            start_value: None,
225            target: Target::Lazy(Arc::new(factory)),
226            target_value: None,
227            duration,
228            elapsed: Duration::ZERO,
229            easing: DEFAULT_EASING,
230        }
231    }
232
233    pub fn follow(&self, path: &PathNode, duration: Duration) -> FollowPath<T>
234    where
235        T: FromVec2,
236    {
237        FollowPath {
238            data: self.data.clone(),
239            path_data: path.data.clone(),
240            duration,
241            elapsed: Duration::ZERO,
242            easing: DEFAULT_EASING,
243        }
244    }
245
246    pub fn bind<S: Tweenable + PartialEq, F>(
247        &self,
248        source: Signal<S>,
249        mapper: F,
250    ) -> crate::engine::nodes::video::BindingNode<T, S>
251    where
252        F: Fn(S) -> T + Send + Sync + 'static,
253    {
254        crate::engine::nodes::video::BindingNode::new(source, self.clone(), mapper)
255    }
256}
257
258/// SignalTween now tracks its own elapsed time and start/target values.
259pub struct SignalTween<T> {
260    data: Arc<Mutex<SignalData<T>>>,
261    start_value: Option<T>,
262    target: Target<T>,
263    target_value: Option<T>,
264    duration: Duration,
265    elapsed: Duration,
266    easing: fn(f32) -> f32,
267}
268
269impl<T: Tweenable> SignalTween<T> {
270    pub fn ease(mut self, easing: fn(f32) -> f32) -> Self {
271        self.easing = easing;
272        self
273    }
274}
275
276impl<T: Tweenable> Animation for SignalTween<T> {
277    fn update(&mut self, dt: Duration) -> (bool, Duration) {
278        // Capture values on first update
279        if self.start_value.is_none() {
280            let current = self.data.lock().unwrap().value.clone();
281            self.start_value = Some(current.clone());
282
283            // Evaluate target if needed
284            if self.target_value.is_none() {
285                match &self.target {
286                    Target::Fixed(v) => self.target_value = Some(v.clone()),
287                    Target::Lazy(f) => self.target_value = Some(f(&current)),
288                }
289            }
290        }
291
292        let target = self.target_value.as_ref().unwrap();
293
294        if self.duration == Duration::ZERO {
295            let mut data = self.data.lock().unwrap();
296            data.value = target.clone();
297            return (true, dt);
298        }
299
300        self.elapsed += dt;
301        let finished = self.elapsed >= self.duration;
302        let leftover = if finished {
303            self.elapsed - self.duration
304        } else {
305            Duration::ZERO
306        };
307
308        let t_linear = (self.elapsed.as_secs_f32() / self.duration.as_secs_f32()).min(1.0);
309        let t_eased = (self.easing)(t_linear);
310
311        let start = self.start_value.as_ref().unwrap();
312        let mut data = self.data.lock().unwrap();
313        data.value = T::interpolate(start, target, t_eased);
314
315        (finished, leftover)
316    }
317
318    fn duration(&self) -> Duration {
319        self.duration
320    }
321
322    fn set_easing(&mut self, easing: fn(f32) -> f32) {
323        self.easing = easing;
324    }
325
326    fn reset(&mut self) {
327        self.start_value = None;
328        self.target_value = None;
329        self.elapsed = Duration::ZERO;
330
331        let mut data = self.data.lock().unwrap();
332        data.value = data.initial.clone();
333    }
334}
335
336pub struct FollowPath<T> {
337    data: Arc<Mutex<SignalData<T>>>,
338    path_data: Arc<PathData>,
339    duration: Duration,
340    elapsed: Duration,
341    easing: fn(f32) -> f32,
342}
343
344impl<T: Send + Sync + 'static> FollowPath<T> {
345    pub fn ease(mut self, easing: fn(f32) -> f32) -> Self {
346        self.easing = easing;
347        self
348    }
349}
350
351impl<T: Tweenable + FromVec2> Animation for FollowPath<T> {
352    fn update(&mut self, dt: Duration) -> (bool, Duration) {
353        if self.duration == Duration::ZERO {
354            let mut data = self.data.lock().unwrap();
355            data.value = T::from_vec2(self.path_data.sample(1.0));
356            return (true, dt);
357        }
358
359        self.elapsed += dt;
360        let finished = self.elapsed >= self.duration;
361        let leftover = if finished {
362            self.elapsed - self.duration
363        } else {
364            Duration::ZERO
365        };
366
367        let t_linear = (self.elapsed.as_secs_f32() / self.duration.as_secs_f32()).min(1.0);
368        let t_eased = (self.easing)(t_linear);
369
370        let mut data = self.data.lock().unwrap();
371        data.value = T::from_vec2(self.path_data.sample(t_eased));
372
373        (finished, leftover)
374    }
375
376    fn duration(&self) -> Duration {
377        self.duration
378    }
379
380    fn set_easing(&mut self, easing: fn(f32) -> f32) {
381        self.easing = easing;
382    }
383
384    fn reset(&mut self) {
385        self.elapsed = Duration::ZERO;
386        let mut data = self.data.lock().unwrap();
387        data.value = data.initial.clone();
388    }
389}
390
391impl<T: Tweenable> From<SignalTween<T>> for Box<dyn Animation> {
392    fn from(tween: SignalTween<T>) -> Self {
393        Box::new(tween)
394    }
395}
396
397impl<T: Tweenable + FromVec2> From<FollowPath<T>> for Box<dyn Animation> {
398    fn from(anim: FollowPath<T>) -> Self {
399        Box::new(anim)
400    }
401}