motion_canvas_rs/engine/animation/
tween.rs1use 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
134pub 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
258pub 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 if self.start_value.is_none() {
280 let current = self.data.lock().unwrap().value.clone();
281 self.start_value = Some(current.clone());
282
283 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(¤t)),
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}