1use glam::{Vec2, Vec3, Vec4};
2use lottie_data::model::{BezierPath, Property, TextDocument, Value};
3#[cfg(feature = "expressions")]
4use crate::expressions::ExpressionEvaluator;
5#[cfg(feature = "expressions")]
6use boa_engine::{JsValue, js_string};
7
8pub trait Interpolatable: Sized + Clone {
9 fn lerp(&self, other: &Self, t: f32) -> Self;
10
11 fn lerp_spatial(
12 &self,
13 other: &Self,
14 t: f32,
15 _tan_in: Option<&Vec<f32>>,
16 _tan_out: Option<&Vec<f32>>,
17 ) -> Self {
18 self.lerp(other, t)
19 }
20}
21
22impl Interpolatable for TextDocument {
23 fn lerp(&self, other: &Self, t: f32) -> Self {
24 if t < 1.0 {
25 self.clone()
26 } else {
27 other.clone()
28 }
29 }
30}
31
32impl Interpolatable for BezierPath {
33 fn lerp(&self, other: &Self, t: f32) -> Self {
34 if t < 1.0 {
35 self.clone()
36 } else {
37 other.clone()
38 }
39 }
40}
41
42impl Interpolatable for f32 {
43 fn lerp(&self, other: &Self, t: f32) -> Self {
44 self + (other - self) * t
45 }
46}
47
48impl Interpolatable for Vec2 {
49 fn lerp(&self, other: &Self, t: f32) -> Self {
50 Vec2::lerp(*self, *other, t)
51 }
52
53 fn lerp_spatial(
54 &self,
55 other: &Self,
56 t: f32,
57 tan_in: Option<&Vec<f32>>,
58 tan_out: Option<&Vec<f32>>,
59 ) -> Self {
60 let p0 = *self;
61 let p3 = *other;
62
63 let t_out = if let Some(to) = tan_out {
64 if to.len() >= 2 {
65 Vec2::new(to[0], to[1])
66 } else {
67 Vec2::ZERO
68 }
69 } else {
70 Vec2::ZERO
71 };
72
73 let t_in = if let Some(ti) = tan_in {
74 if ti.len() >= 2 {
75 Vec2::new(ti[0], ti[1])
76 } else {
77 Vec2::ZERO
78 }
79 } else {
80 Vec2::ZERO
81 };
82
83 let p1 = p0 + t_out;
84 let p2 = p3 + t_in;
85
86 let one_minus_t = 1.0 - t;
87 let one_minus_t_sq = one_minus_t * one_minus_t;
88 let one_minus_t_cub = one_minus_t_sq * one_minus_t;
89
90 let t_sq = t * t;
91 let t_cub = t_sq * t;
92
93 p0 * one_minus_t_cub
94 + p1 * 3.0 * one_minus_t_sq * t
95 + p2 * 3.0 * one_minus_t * t_sq
96 + p3 * t_cub
97 }
98}
99
100impl Interpolatable for Vec3 {
101 fn lerp(&self, other: &Self, t: f32) -> Self {
102 Vec3::lerp(*self, *other, t)
103 }
104
105 fn lerp_spatial(
106 &self,
107 other: &Self,
108 t: f32,
109 tan_in: Option<&Vec<f32>>,
110 tan_out: Option<&Vec<f32>>,
111 ) -> Self {
112 let p0 = *self;
113 let p3 = *other;
114
115 let t_out = if let Some(to) = tan_out {
116 if to.len() >= 3 {
117 Vec3::new(to[0], to[1], to[2])
118 } else if to.len() >= 2 {
119 Vec3::new(to[0], to[1], 0.0)
120 } else {
121 Vec3::ZERO
122 }
123 } else {
124 Vec3::ZERO
125 };
126
127 let t_in = if let Some(ti) = tan_in {
128 if ti.len() >= 3 {
129 Vec3::new(ti[0], ti[1], ti[2])
130 } else if ti.len() >= 2 {
131 Vec3::new(ti[0], ti[1], 0.0)
132 } else {
133 Vec3::ZERO
134 }
135 } else {
136 Vec3::ZERO
137 };
138
139 let p1 = p0 + t_out;
140 let p2 = p3 + t_in;
141
142 let one_minus_t = 1.0 - t;
143 let one_minus_t_sq = one_minus_t * one_minus_t;
144 let one_minus_t_cub = one_minus_t_sq * one_minus_t;
145
146 let t_sq = t * t;
147 let t_cub = t_sq * t;
148
149 p0 * one_minus_t_cub
150 + p1 * 3.0 * one_minus_t_sq * t
151 + p2 * 3.0 * one_minus_t * t_sq
152 + p3 * t_cub
153 }
154}
155
156impl Interpolatable for Vec4 {
157 fn lerp(&self, other: &Self, t: f32) -> Self {
158 Vec4::lerp(*self, *other, t)
159 }
160}
161
162impl Interpolatable for Vec<f32> {
164 fn lerp(&self, other: &Self, t: f32) -> Self {
165 self.iter()
166 .zip(other.iter())
167 .map(|(a, b)| a + (b - a) * t)
168 .collect()
169 }
170}
171
172#[cfg(feature = "expressions")]
174pub trait ToJsValue {
175 fn to_js_value(&self, context: &mut boa_engine::Context) -> JsValue;
176 fn from_js_value(v: &JsValue, context: &mut boa_engine::Context) -> Option<Self> where Self: Sized;
177}
178
179#[cfg(not(feature = "expressions"))]
180pub trait ToJsValue {}
181
182#[cfg(not(feature = "expressions"))]
183impl<T> ToJsValue for T {}
184
185#[cfg(feature = "expressions")]
186impl ToJsValue for f32 {
187 fn to_js_value(&self, _context: &mut boa_engine::Context) -> JsValue {
188 JsValue::new(*self)
189 }
190 fn from_js_value(v: &JsValue, context: &mut boa_engine::Context) -> Option<Self> {
191 v.to_number(context).ok().map(|n| n as f32)
192 }
193}
194
195#[cfg(feature = "expressions")]
196impl ToJsValue for Vec<f32> {
197 fn to_js_value(&self, context: &mut boa_engine::Context) -> JsValue {
198 let vals: Vec<JsValue> = self.iter().map(|f| JsValue::new(*f)).collect();
199 boa_engine::object::builtins::JsArray::from_iter(vals, context).into()
200 }
201 fn from_js_value(v: &JsValue, context: &mut boa_engine::Context) -> Option<Self> {
202 if let Some(obj) = v.as_object() {
203 if obj.is_array() {
204 if let Ok(len_val) = obj.get(js_string!("length"), context) {
205 if let Ok(len) = len_val.to_number(context) {
206 let len_u64 = len as u64;
207 let mut vec = Vec::with_capacity(len_u64 as usize);
208 for i in 0..len_u64 {
209 if let Ok(val) = obj.get(i, context) {
210 if let Ok(n) = val.to_number(context) {
211 vec.push(n as f32);
212 }
213 }
214 }
215 return Some(vec);
216 }
217 }
218 }
219 }
220 None
221 }
222}
223
224#[cfg(feature = "expressions")]
225impl ToJsValue for Vec2 {
226 fn to_js_value(&self, context: &mut boa_engine::Context) -> JsValue {
227 let vals = vec![JsValue::new(self.x), JsValue::new(self.y)];
228 boa_engine::object::builtins::JsArray::from_iter(vals, context).into()
229 }
230 fn from_js_value(v: &JsValue, context: &mut boa_engine::Context) -> Option<Self> {
231 if let Some(obj) = v.as_object() {
232 if obj.is_array() {
233 let x = obj.get(0, context).ok()?.to_number(context).ok()? as f32;
234 let y = obj.get(1, context).ok()?.to_number(context).ok()? as f32;
235 return Some(Vec2::new(x, y));
236 }
237 }
238 None
239 }
240}
241
242#[cfg(feature = "expressions")]
243impl ToJsValue for Vec3 {
244 fn to_js_value(&self, context: &mut boa_engine::Context) -> JsValue {
245 let vals = vec![JsValue::new(self.x), JsValue::new(self.y), JsValue::new(self.z)];
246 boa_engine::object::builtins::JsArray::from_iter(vals, context).into()
247 }
248 fn from_js_value(v: &JsValue, context: &mut boa_engine::Context) -> Option<Self> {
249 if let Some(obj) = v.as_object() {
250 if obj.is_array() {
251 let x = obj.get(0, context).ok()?.to_number(context).ok()? as f32;
252 let y = obj.get(1, context).ok()?.to_number(context).ok()? as f32;
253 let z = obj.get(2, context).ok()?.to_number(context).ok()? as f32;
254 return Some(Vec3::new(x, y, z));
255 }
256 }
257 None
258 }
259}
260
261#[cfg(feature = "expressions")]
262impl ToJsValue for Vec4 {
263 fn to_js_value(&self, context: &mut boa_engine::Context) -> JsValue {
264 let vals = vec![JsValue::new(self.x), JsValue::new(self.y), JsValue::new(self.z), JsValue::new(self.w)];
265 boa_engine::object::builtins::JsArray::from_iter(vals, context).into()
266 }
267 fn from_js_value(v: &JsValue, context: &mut boa_engine::Context) -> Option<Self> {
268 if let Some(obj) = v.as_object() {
269 if obj.is_array() {
270 let x = obj.get(0, context).ok()?.to_number(context).ok()? as f32;
271 let y = obj.get(1, context).ok()?.to_number(context).ok()? as f32;
272 let z = obj.get(2, context).ok()?.to_number(context).ok()? as f32;
273 let w = obj.get(3, context).ok()?.to_number(context).ok()? as f32;
274 return Some(Vec4::new(x, y, z, w));
275 }
276 }
277 None
278 }
279}
280
281#[cfg(feature = "expressions")]
282impl ToJsValue for BezierPath {
283 fn to_js_value(&self, _context: &mut boa_engine::Context) -> JsValue {
284 JsValue::Undefined
285 }
286 fn from_js_value(_v: &JsValue, _context: &mut boa_engine::Context) -> Option<Self> {
287 None
288 }
289}
290
291#[cfg(feature = "expressions")]
292impl ToJsValue for TextDocument {
293 fn to_js_value(&self, _context: &mut boa_engine::Context) -> JsValue {
294 JsValue::Undefined
295 }
296 fn from_js_value(_v: &JsValue, _context: &mut boa_engine::Context) -> Option<Self> {
297 None
298 }
299}
300
301pub fn solve_cubic_bezier(p1: Vec2, p2: Vec2, x: f32) -> f32 {
303 if x <= 0.0 {
304 return 0.0;
305 }
306 if x >= 1.0 {
307 return 1.0;
308 }
309
310 let mut t = x;
312 for _ in 0..8 {
313 let one_minus_t = 1.0 - t;
314 let x_est = 3.0 * one_minus_t * one_minus_t * t * p1.x
315 + 3.0 * one_minus_t * t * t * p2.x
316 + t * t * t;
317
318 let err = x_est - x;
319 if err.abs() < 1e-4 {
320 break;
321 }
322
323 let dx_dt = 3.0 * one_minus_t * one_minus_t * p1.x
324 + 6.0 * one_minus_t * t * (p2.x - p1.x)
325 + 3.0 * t * t * (1.0 - p2.x);
326
327 if dx_dt.abs() < 1e-6 {
328 break;
329 }
330 t -= err / dx_dt;
331 }
332
333 let one_minus_t = 1.0 - t;
334 3.0 * one_minus_t * one_minus_t * t * p1.y + 3.0 * one_minus_t * t * t * p2.y + t * t * t
335}
336
337pub struct Animator;
338
339impl Animator {
340 pub fn resolve<T, U>(
341 prop: &Property<T>,
342 frame: f32,
343 converter: impl Fn(&T) -> U,
344 default: U,
345 #[cfg(feature = "expressions")] evaluator: Option<&mut ExpressionEvaluator>,
346 #[cfg(not(feature = "expressions"))] _evaluator: Option<&mut ()>, frame_rate: f32,
348 ) -> U
349 where
350 U: Interpolatable + 'static + ToJsValue,
351 {
352 let base_value = Self::resolve_keyframes(prop, frame, &converter, default.clone());
354
355 #[cfg(feature = "expressions")]
357 if let Some(expr) = &prop.x {
358 if let Some(eval) = evaluator {
359 let time = frame / frame_rate; let loop_value = if let Value::Animated(keyframes) = &prop.k {
363 if !keyframes.is_empty() {
364 let first_t = keyframes[0].t;
365 let last_t = keyframes[keyframes.len() - 1].t;
366 let duration = last_t - first_t;
367
368 if duration > 0.0 && frame > last_t {
369 let t_since_end = frame - last_t;
370 let cycle_offset = t_since_end % duration;
371 let cycle_frame = first_t + cycle_offset;
372 Self::resolve_keyframes(prop, cycle_frame, &converter, default.clone())
373 } else {
374 base_value.clone()
375 }
376 } else {
377 base_value.clone()
378 }
379 } else {
380 base_value.clone()
381 };
382
383 let (js_val, js_loop_val) = {
384 let ctx = eval.context();
385 (base_value.to_js_value(ctx), loop_value.to_js_value(ctx))
386 };
387
388 match eval.evaluate(expr, &js_val, &js_loop_val, time, frame_rate) {
389 Ok(res) => {
390 let context = eval.context();
391 if let Some(val) = U::from_js_value(&res, context) {
392 return val;
393 }
394 },
395 Err(_e) => {
396 }
398 }
399 }
400 }
401
402 base_value
403 }
404
405 fn resolve_keyframes<T, U>(
406 prop: &Property<T>,
407 frame: f32,
408 converter: &impl Fn(&T) -> U,
409 default: U,
410 ) -> U
411 where
412 U: Interpolatable,
413 {
414 match &prop.k {
415 Value::Default => default,
416 Value::Static(v) => converter(v),
417 Value::Animated(keyframes) => {
418 if keyframes.is_empty() {
419 return default;
420 }
421
422 let idx = keyframes.partition_point(|kf| kf.t <= frame);
427
428 if idx == 0 {
430 if let Some(s) = &keyframes[0].s {
431 return converter(s);
432 }
433 return default;
434 }
435
436 let len = keyframes.len();
437 if idx >= len {
439 let last = &keyframes[len - 1];
440 if let Some(e) = &last.e {
442 return converter(e);
443 }
444 if let Some(s) = &last.s {
445 return converter(s);
446 }
447 return default;
448 }
449
450 let kf_start = &keyframes[idx - 1];
452 let kf_end = &keyframes[idx];
453
454 let start_val = kf_start
455 .s
456 .as_ref()
457 .map(|v| converter(v))
458 .unwrap_or(default.clone());
459
460 let end_val = kf_start
462 .e
463 .as_ref()
464 .map(|v| converter(v))
465 .or_else(|| kf_end.s.as_ref().map(|v| converter(v)))
466 .unwrap_or(start_val.clone());
467
468 let duration = kf_end.t - kf_start.t;
469 if duration <= 0.0 {
470 return start_val;
471 }
472
473 let mut local_t = (frame - kf_start.t) / duration;
474
475 let p1 = if let Some(o) = kf_start.o {
477 Vec2::new(o[0], o[1])
478 } else {
479 Vec2::new(0.0, 0.0)
480 };
481 let p2 = if let Some(i) = kf_end.i {
482 Vec2::new(i[0], i[1])
483 } else {
484 Vec2::new(1.0, 1.0)
485 };
486
487 if let Some(h) = kf_start.h {
489 if h == 1 {
490 return start_val;
491 }
492 }
493
494 local_t = solve_cubic_bezier(p1, p2, local_t);
495
496 start_val.lerp_spatial(
497 &end_val,
498 local_t,
499 kf_end.ti.as_ref(),
500 kf_start.to.as_ref(),
501 )
502 }
503 }
504 }
505}
506
507#[cfg(test)]
508mod tests {
509 use super::*;
510 use lottie_data::model::{Keyframe, Value};
511
512 #[test]
513 fn test_animator_resolve_binary_search() {
514 let keyframes = vec![
516 Keyframe {
517 t: 0.0,
518 s: Some(0.0),
519 e: Some(10.0),
520 i: None, o: None, to: None, ti: None, h: None,
521 },
522 Keyframe {
523 t: 10.0,
524 s: Some(10.0),
525 e: Some(20.0),
526 i: None, o: None, to: None, ti: None, h: None,
527 },
528 Keyframe {
529 t: 20.0,
530 s: Some(20.0),
531 e: Some(30.0),
532 i: None, o: None, to: None, ti: None, h: None,
533 }
534 ];
535
536 let prop = Property {
537 a: 1,
538 k: Value::Animated(keyframes),
539 ix: None,
540 x: None,
541 };
542
543 let conv = |v: &f32| *v;
544
545 assert_eq!(Animator::resolve(&prop, 0.0, conv, -1.0, None, 60.0), 0.0);
547
548 assert_eq!(Animator::resolve(&prop, 10.0, conv, -1.0, None, 60.0), 10.0);
550
551 assert_eq!(Animator::resolve(&prop, 20.0, conv, -1.0, None, 60.0), 30.0);
553
554 assert_eq!(Animator::resolve(&prop, -5.0, conv, -1.0, None, 60.0), 0.0);
556
557 assert_eq!(Animator::resolve(&prop, 25.0, conv, -1.0, None, 60.0), 30.0);
559
560 assert_eq!(Animator::resolve(&prop, 5.0, conv, -1.0, None, 60.0), 5.0);
562
563 assert_eq!(Animator::resolve(&prop, 15.0, conv, -1.0, None, 60.0), 15.0);
565 }
566}