1use std::f64::consts::{FRAC_PI_2, TAU};
4
5use kurbo::{Affine, CubicBez, Point, Vec2};
6
7use crate::Interpolate;
8use crate::color::{Color, YELLOW};
9use crate::geom::{VPath, align};
10use crate::mobject::{MobjectId, SceneState, Stroke, VState};
11use crate::timeline::Scene;
12use crate::transform_diff::center;
13
14#[derive(Debug, Clone, Copy)]
16pub enum RateFn {
17 Linear,
19 Smooth,
21 ThereAndBack,
23 EaseIn(Ease),
25 EaseOut(Ease),
27 EaseInOut(Ease),
29 Spring {
32 stiffness: f32,
34 damping: f32,
36 },
37 Custom(fn(f32) -> f32),
39}
40
41#[derive(Debug, Clone, Copy, PartialEq, Eq)]
43pub enum Ease {
44 Quad,
46 Cubic,
48 Expo,
50 Back,
52}
53
54impl Ease {
55 fn ease_in(self, t: f32) -> f32 {
57 if t <= 0.0 {
58 return 0.0;
59 }
60 if t >= 1.0 {
61 return 1.0;
62 }
63 match self {
64 Ease::Quad => t * t,
65 Ease::Cubic => t * t * t,
66 Ease::Expo => 2f32.powf(10.0 * t - 10.0),
67 Ease::Back => {
68 const C1: f32 = 1.70158;
69 (C1 + 1.0) * t * t * t - C1 * t * t
70 }
71 }
72 }
73}
74
75impl RateFn {
76 pub fn apply(self, t: f32) -> f32 {
78 let t = t.clamp(0.0, 1.0);
79 let smooth = |t: f32| t * t * t * (t * (6.0 * t - 15.0) + 10.0);
80 match self {
81 RateFn::Linear => t,
82 RateFn::Smooth => smooth(t),
83 RateFn::ThereAndBack => smooth(if t < 0.5 { 2.0 * t } else { 2.0 - 2.0 * t }),
84 RateFn::EaseIn(e) => e.ease_in(t),
85 RateFn::EaseOut(e) => 1.0 - e.ease_in(1.0 - t),
86 RateFn::EaseInOut(e) if t < 0.5 => e.ease_in(2.0 * t) / 2.0,
87 RateFn::EaseInOut(e) => 1.0 - e.ease_in(2.0 - 2.0 * t) / 2.0,
88 RateFn::Spring { .. } if t >= 1.0 => 1.0,
89 RateFn::Spring { stiffness, damping } => {
90 let f = |t: f32| 1.0 - (-damping * t).exp() * (stiffness * t).cos();
91 f(t) / f(1.0)
93 }
94 RateFn::Custom(f) => f(t),
95 }
96 }
97}
98
99pub trait Animation: Send + Sync {
101 fn plan(&mut self, state: &SceneState);
104
105 fn sample(&self, alpha: f32, state: &mut SceneState);
108
109 fn duration(&self) -> f32 {
111 1.0
112 }
113
114 fn rate_fn(&self) -> RateFn {
116 RateFn::Smooth
117 }
118}
119
120impl Animation for Box<dyn Animation> {
121 fn plan(&mut self, state: &SceneState) {
122 (**self).plan(state);
123 }
124 fn sample(&self, alpha: f32, state: &mut SceneState) {
125 (**self).sample(alpha, state);
126 }
127 fn duration(&self) -> f32 {
128 (**self).duration()
129 }
130 fn rate_fn(&self) -> RateFn {
131 (**self).rate_fn()
132 }
133}
134
135pub trait AnimationExt: Animation + Sized {
137 fn run_time(self, secs: f32) -> Timed<Self> {
139 let rate = self.rate_fn();
140 Timed {
141 anim: self,
142 secs,
143 rate,
144 }
145 }
146
147 fn rate(self, rate: RateFn) -> Timed<Self> {
149 let secs = self.duration();
150 Timed {
151 anim: self,
152 secs,
153 rate,
154 }
155 }
156}
157
158impl<A: Animation> AnimationExt for A {}
159
160pub struct Timed<A> {
162 anim: A,
163 secs: f32,
164 rate: RateFn,
165}
166
167impl<A: Animation> Timed<A> {
168 pub fn run_time(self, secs: f32) -> Self {
170 Self { secs, ..self }
171 }
172
173 pub fn rate(self, rate: RateFn) -> Self {
175 Self { rate, ..self }
176 }
177}
178
179impl<A: Animation> Animation for Timed<A> {
180 fn plan(&mut self, state: &SceneState) {
181 self.anim.plan(state);
182 }
183 fn sample(&self, alpha: f32, state: &mut SceneState) {
184 self.anim.sample(alpha, state);
185 }
186 fn duration(&self) -> f32 {
187 self.secs
188 }
189 fn rate_fn(&self) -> RateFn {
190 self.rate
191 }
192}
193
194fn get(state: &SceneState, id: MobjectId) -> &VState {
195 state
196 .get(&id)
197 .unwrap_or_else(|| panic!("{id:?} is not in the scene; `add` it first"))
198}
199
200type UpdateFn = Box<dyn Fn(&VState, f32) -> VState + Send + Sync>;
201
202pub struct Update {
205 id: MobjectId,
206 start: Option<VState>,
207 f: UpdateFn,
208}
209
210impl Update {
211 pub fn new(id: MobjectId, f: impl Fn(&VState, f32) -> VState + Send + Sync + 'static) -> Self {
213 Self {
214 id,
215 start: None,
216 f: Box::new(f),
217 }
218 }
219}
220
221impl Animation for Update {
222 fn plan(&mut self, state: &SceneState) {
223 self.start = Some(get(state, self.id).clone());
224 }
225 fn sample(&self, alpha: f32, state: &mut SceneState) {
226 let start = self.start.as_ref().expect("sample before plan");
227 state.insert(self.id, (self.f)(start, alpha));
228 }
229}
230
231pub fn create(id: MobjectId) -> Update {
233 Update::new(id, |s, a| VState {
234 draw_range: 0.0..a,
235 ..s.clone()
236 })
237}
238
239pub fn fade_in(id: MobjectId) -> Update {
241 Update::new(id, |s, a| VState {
242 opacity: s.opacity * a,
243 ..s.clone()
244 })
245}
246
247pub fn fade_out(id: MobjectId) -> Update {
249 Update::new(id, |s, a| VState {
250 opacity: s.opacity * (1.0 - a),
251 ..s.clone()
252 })
253}
254
255pub fn shift(id: MobjectId, v: Vec2) -> Update {
257 Update::new(id, move |s, a| {
258 s.clone().transform(Affine::translate(v * f64::from(a)))
259 })
260}
261
262pub fn rotate(id: MobjectId, angle: f64) -> Update {
264 Update::new(id, move |s, a| {
265 let c = s.path.center();
266 s.clone()
267 .transform(Affine::rotate_about(angle * f64::from(a), c))
268 })
269}
270
271pub fn scale(id: MobjectId, factor: f64) -> Update {
273 Update::new(id, move |s, a| {
274 let c = s.path.center();
275 s.clone()
276 .transform(Affine::scale_about(1.0 + (factor - 1.0) * f64::from(a), c))
277 })
278}
279
280pub fn grow_from_center(id: MobjectId) -> Update {
282 Update::new(id, |s, a| s.clone().scale(f64::from(a)))
283}
284
285pub fn spin_in(id: MobjectId) -> Update {
287 Update::new(id, |s, a| {
288 let c = s.path.center();
289 let a = f64::from(a);
290 s.clone()
291 .transform(Affine::rotate_about(FRAC_PI_2 * (a - 1.0), c) * Affine::scale_about(a, c))
292 })
293}
294
295pub fn shrink_to_center(id: MobjectId) -> Update {
297 Update::new(id, |s, a| s.clone().scale(f64::from(1.0 - a)))
298}
299
300pub fn uncreate(id: MobjectId) -> Update {
303 Update::new(id, |s, a| VState {
304 draw_range: 0.0..1.0 - a,
305 ..s.clone()
306 })
307}
308
309pub fn move_to(id: MobjectId, p: Point) -> Update {
311 Update::new(id, move |s, a| {
312 s.clone().shift((p - s.path.center()) * f64::from(a))
313 })
314}
315
316pub fn apply_function(id: MobjectId, f: impl Fn(Point) -> Point + Send + Sync + 'static) -> Update {
318 Update::new(id, move |s, a| {
319 let t = f64::from(a);
320 let mut m = s.clone();
321 for seg in m.path.subpaths.iter_mut().flat_map(|sp| &mut sp.segments) {
322 let g = |q: Point| q.lerp(f(q), t);
323 *seg = CubicBez::new(g(seg.p0), g(seg.p1), g(seg.p2), g(seg.p3));
324 }
325 m
326 })
327}
328
329type GroupFn = Box<dyn Fn(&VState, Point, f32) -> VState + Send + Sync>;
330
331pub struct UpdateGroup {
334 ids: Vec<MobjectId>,
335 starts: Vec<VState>,
336 center: Point,
337 f: GroupFn,
338}
339
340impl UpdateGroup {
341 pub fn new(
343 ids: &[MobjectId],
344 f: impl Fn(&VState, Point, f32) -> VState + Send + Sync + 'static,
345 ) -> Self {
346 Self {
347 ids: ids.to_vec(),
348 starts: Vec::new(),
349 center: Point::ORIGIN,
350 f: Box::new(f),
351 }
352 }
353}
354
355impl Animation for UpdateGroup {
356 fn plan(&mut self, state: &SceneState) {
357 self.starts = self.ids.iter().map(|&id| get(state, id).clone()).collect();
358 self.center = center(self.starts.iter());
359 }
360 fn sample(&self, alpha: f32, state: &mut SceneState) {
361 for (&id, s) in self.ids.iter().zip(&self.starts) {
362 state.insert(id, (self.f)(s, self.center, alpha));
363 }
364 }
365}
366
367pub fn indicate(ids: &[MobjectId]) -> Timed<UpdateGroup> {
369 UpdateGroup::new(ids, |s, c, a| {
370 if a <= 0.0 {
371 return s.clone();
372 }
373 let tint = |col: Color| Color::lerp(&col, &YELLOW.with_alpha(col.a), a);
374 VState {
375 fill: tint(s.fill),
376 stroke: Stroke {
377 color: tint(s.stroke.color),
378 ..s.stroke
379 },
380 ..s.clone()
381 }
382 .transform(Affine::scale_about(1.0 + 0.2 * f64::from(a), c))
383 })
384 .rate(RateFn::ThereAndBack)
385}
386
387pub fn wiggle(ids: &[MobjectId]) -> Timed<UpdateGroup> {
389 UpdateGroup::new(ids, |s, c, a| {
390 if a <= 0.0 || a >= 1.0 {
391 return s.clone();
392 }
393 let a = f64::from(a);
394 let swell = RateFn::ThereAndBack.apply(a as f32) as f64;
395 let angle = 0.1 * swell * (3.0 * TAU * a).sin();
396 s.clone()
397 .transform(Affine::rotate_about(angle, c) * Affine::scale_about(1.0 + 0.1 * swell, c))
398 })
399 .run_time(2.0)
400 .rate(RateFn::Linear)
401}
402
403pub struct Overlay {
406 ids: Vec<MobjectId>,
407 starts: Vec<VState>,
408 f: UpdateFn,
409}
410
411impl Overlay {
412 pub fn new(
415 s: &mut Scene,
416 shapes: Vec<VState>,
417 f: impl Fn(&VState, f32) -> VState + Send + Sync + 'static,
418 ) -> Self {
419 let ids = (shapes.iter())
420 .map(|m| {
421 s.add(VState {
422 draw_range: 0.0..0.0,
423 ..m.clone()
424 })
425 })
426 .collect();
427 Self {
428 ids,
429 starts: shapes,
430 f: Box::new(f),
431 }
432 }
433}
434
435impl Animation for Overlay {
436 fn plan(&mut self, _: &SceneState) {}
437 fn sample(&self, alpha: f32, state: &mut SceneState) {
438 for (&id, m) in self.ids.iter().zip(&self.starts) {
439 if alpha >= 1.0 {
440 state.remove(&id);
441 } else {
442 state.insert(id, (self.f)(m, alpha));
443 }
444 }
445 }
446}
447
448pub fn circumscribe(s: &mut Scene, ids: &[MobjectId]) -> Overlay {
450 const BUFF: f64 = 0.2;
451 let st = s.state();
452 let bbox = (ids.iter())
453 .filter_map(|id| get(st, *id).path.bbox())
454 .reduce(|a, b| a.union(b))
455 .unwrap_or_default()
456 .inflate(BUFF, BUFF);
457 let rect = VState::rectangle(bbox.width(), bbox.height())
458 .move_to(bbox.center())
459 .stroke(YELLOW, 0.06);
460 Overlay::new(s, vec![rect], |m, a| VState {
461 draw_range: (2.0 * a - 1.0).max(0.0)..(2.0 * a).min(1.0),
463 ..m.clone()
464 })
465}
466
467pub fn flash(s: &mut Scene, p: Point) -> Overlay {
469 let rays = (0..12)
470 .map(|i| {
471 let d = Vec2::from_angle(TAU * f64::from(i) / 12.0);
472 VState::line(p + d * 0.3, p + d * 0.8).stroke(YELLOW, 0.04)
473 })
474 .collect();
475 Overlay::new(s, rays, |m, a| VState {
476 draw_range: (2.0 * a - 1.0).max(0.0)..(2.0 * a).min(1.0),
477 ..m.clone()
478 })
479}
480
481fn border_then_fill(s: &VState, p: f32) -> VState {
484 if p >= 1.0 {
485 return s.clone();
486 }
487 let outline = if s.stroke.width > 0.0 && s.stroke.color.a > 0.0 {
488 s.stroke
489 } else {
490 Stroke {
491 color: s.fill.with_alpha(1.0),
492 width: 0.02,
493 }
494 };
495 if p < 0.5 {
496 VState {
497 stroke: outline,
498 fill: s.fill.with_alpha(0.0),
499 draw_range: 0.0..p * 2.0,
500 ..s.clone()
501 }
502 } else {
503 let q = p * 2.0 - 1.0;
504 VState {
505 stroke: Stroke {
506 color: Color::lerp(&outline.color, &s.stroke.color, q),
507 width: outline.width + (s.stroke.width - outline.width) * f64::from(q),
508 },
509 fill: s.fill.with_alpha(s.fill.a * q),
510 ..s.clone()
511 }
512 }
513}
514
515pub fn draw_border_then_fill(id: MobjectId) -> Timed<Update> {
518 Update::new(id, border_then_fill).run_time(2.0)
519}
520
521pub struct Write {
524 ids: Vec<MobjectId>,
525 starts: Vec<VState>,
526}
527
528pub fn write(ids: &[MobjectId]) -> Write {
530 Write {
531 ids: ids.to_vec(),
532 starts: Vec::new(),
533 }
534}
535
536impl Animation for Write {
537 fn plan(&mut self, state: &SceneState) {
538 self.starts = self.ids.iter().map(|&id| get(state, id).clone()).collect();
539 }
540 fn sample(&self, alpha: f32, state: &mut SceneState) {
541 let n = self.ids.len() as f32;
542 let lag = (4.0 / n.max(1.0)).min(0.2);
544 let w = 1.0 / (1.0 + (n - 1.0).max(0.0) * lag);
545 for (i, (&id, s)) in self.ids.iter().zip(&self.starts).enumerate() {
546 let p = if alpha >= 1.0 {
548 1.0
549 } else {
550 ((alpha - i as f32 * lag * w) / w).clamp(0.0, 1.0)
551 };
552 state.insert(id, border_then_fill(s, p));
553 }
554 }
555 fn duration(&self) -> f32 {
556 (self.ids.len() as f32 / 15.0).clamp(1.0, 2.0)
558 }
559}
560
561pub struct Unwrite(Write);
564
565pub fn unwrite(ids: &[MobjectId]) -> Unwrite {
567 Unwrite(write(ids))
568}
569
570impl Animation for Unwrite {
571 fn plan(&mut self, state: &SceneState) {
572 self.0.plan(state);
573 }
574 fn sample(&self, alpha: f32, state: &mut SceneState) {
575 self.0.sample(1.0 - alpha, state);
576 }
577 fn duration(&self) -> f32 {
578 self.0.duration()
579 }
580}
581
582pub struct Transform {
584 id: MobjectId,
585 target: VState,
586 ends: Option<(VState, VState)>,
587}
588
589pub fn transform(id: MobjectId, target: VState) -> Transform {
591 Transform {
592 id,
593 target,
594 ends: None,
595 }
596}
597
598impl Animation for Transform {
599 fn plan(&mut self, state: &SceneState) {
600 let from = get(state, self.id);
601 let (a, b) = align(&from.path, &self.target.path);
602 self.ends = Some((
603 VState {
604 path: a,
605 ..from.clone()
606 },
607 VState {
608 path: b,
609 ..self.target.clone()
610 },
611 ));
612 }
613 fn sample(&self, alpha: f32, state: &mut SceneState) {
614 let (a, b) = self.ends.as_ref().expect("sample before plan");
615 if alpha >= 1.0 {
617 state.insert(self.id, self.target.clone());
618 } else if alpha > 0.0 {
619 state.insert(self.id, VState::lerp(a, b, alpha));
620 }
621 }
622}
623
624pub struct ReplacementTransform {
628 id: MobjectId,
629 target: MobjectId,
630 morph: Transform,
631}
632
633pub fn replacement_transform(id: MobjectId, target: MobjectId) -> ReplacementTransform {
635 ReplacementTransform {
636 id,
637 target,
638 morph: transform(id, VState::new(VPath::default())),
639 }
640}
641
642impl Animation for ReplacementTransform {
643 fn plan(&mut self, state: &SceneState) {
644 self.morph.target = get(state, self.target).clone();
645 self.morph.plan(state);
646 }
647 fn sample(&self, alpha: f32, state: &mut SceneState) {
648 if alpha >= 1.0 {
649 state.remove(&self.id);
651 } else {
652 state.remove(&self.target);
653 self.morph.sample(alpha, state);
654 }
655 }
656}
657
658pub struct Parallel(pub Vec<Box<dyn Animation>>);
661
662impl Animation for Parallel {
663 fn plan(&mut self, state: &SceneState) {
664 self.0.iter_mut().for_each(|a| a.plan(state));
665 }
666 fn sample(&self, alpha: f32, state: &mut SceneState) {
667 let t = alpha * self.duration();
668 for a in &self.0 {
669 let p = if a.duration() > 0.0 {
670 t / a.duration()
671 } else {
672 1.0
673 };
674 a.sample(a.rate_fn().apply(p), state);
675 }
676 }
677 fn duration(&self) -> f32 {
678 self.0.iter().map(|a| a.duration()).fold(0.0, f32::max)
679 }
680 fn rate_fn(&self) -> RateFn {
681 RateFn::Linear
682 }
683}
684
685pub struct Sequence(pub Vec<Box<dyn Animation>>);
688
689impl Animation for Sequence {
690 fn plan(&mut self, state: &SceneState) {
691 let mut st = state.clone();
692 for a in &mut self.0 {
693 a.plan(&st);
694 a.sample(a.rate_fn().apply(1.0), &mut st);
695 }
696 }
697 fn sample(&self, alpha: f32, state: &mut SceneState) {
698 let mut t = alpha * self.duration();
699 for a in &self.0 {
700 if t < 0.0 {
701 break;
702 }
703 let d = a.duration();
704 let p = if alpha >= 1.0 || d <= 0.0 { 1.0 } else { t / d };
706 a.sample(a.rate_fn().apply(p), state);
707 t -= d;
708 }
709 }
710 fn duration(&self) -> f32 {
711 self.0.iter().map(|a| a.duration()).sum()
712 }
713 fn rate_fn(&self) -> RateFn {
714 RateFn::Linear
715 }
716}
717
718pub struct LaggedStart {
721 anims: Vec<Box<dyn Animation>>,
722 starts: Vec<f32>,
723}
724
725pub fn lagged_start(lag_ratio: f32, anims: Vec<Box<dyn Animation>>) -> LaggedStart {
727 let starts = (anims.iter())
728 .scan(0.0, |t, a| {
729 let s = *t;
730 *t += a.duration() * lag_ratio;
731 Some(s)
732 })
733 .collect();
734 LaggedStart { anims, starts }
735}
736
737impl Animation for LaggedStart {
738 fn plan(&mut self, state: &SceneState) {
739 self.anims.iter_mut().for_each(|a| a.plan(state));
740 }
741 fn sample(&self, alpha: f32, state: &mut SceneState) {
742 let t = alpha * self.duration();
743 for (a, s) in self.anims.iter().zip(&self.starts) {
744 let d = a.duration();
745 let p = if alpha >= 1.0 || d <= 0.0 {
746 1.0
747 } else {
748 (t - s) / d
749 };
750 a.sample(a.rate_fn().apply(p), state);
751 }
752 }
753 fn duration(&self) -> f32 {
754 (self.anims.iter().zip(&self.starts))
755 .map(|(a, s)| s + a.duration())
756 .fold(0.0, f32::max)
757 }
758 fn rate_fn(&self) -> RateFn {
759 RateFn::Linear
760 }
761}