Skip to main content

fastanim_core/
timeline.rs

1//! Scene builder and baked, seekable timeline (SPEC §4.5).
2
3use std::sync::Arc;
4
5use crate::anim::{Animation, RateFn};
6use crate::mobject::{MobjectId, SceneState, VState};
7
8/// A clip and the scene as it was when the clip started (the keyframe).
9struct Clip {
10    start: f32,
11    end: f32,
12    before: SceneState,
13    anim: Option<(Box<dyn Animation>, RateFn)>,
14}
15
16type UpdaterFn = Arc<dyn Fn(&SceneState, f32) -> VState + Send + Sync>;
17
18/// Sets one mobject from the scene and the time since it was registered (SPEC §4.4).
19#[derive(Clone)]
20struct Updater {
21    start: f32,
22    id: MobjectId,
23    f: UpdaterFn,
24}
25
26/// Records a scene imperatively, manim-style: `add`, `play`, `wait`. Nothing renders here.
27#[derive(Default)]
28pub struct Scene {
29    state: SceneState,
30    clips: Vec<Clip>,
31    cursor: f32,
32    next_id: u32,
33    markers: Vec<(String, f32)>,
34    updaters: Vec<Updater>,
35}
36
37impl Scene {
38    /// An empty scene at time 0.
39    pub fn new() -> Self {
40        Self::default()
41    }
42
43    /// Adds a mobject at the current time.
44    pub fn add(&mut self, m: VState) -> MobjectId {
45        let id = MobjectId(self.next_id);
46        self.next_id += 1;
47        self.state.insert(id, m);
48        id
49    }
50
51    /// Removes a mobject at the current time.
52    pub fn remove(&mut self, id: MobjectId) {
53        self.state.remove(&id);
54    }
55
56    /// The scene as of the current time, e.g. for positioning relative to other mobjects.
57    pub fn state(&self) -> &SceneState {
58        &self.state
59    }
60
61    /// Current time in seconds.
62    pub fn time(&self) -> f32 {
63        self.cursor
64    }
65
66    /// Plans `anim` against the current state, records it and advances time past it.
67    pub fn play(&mut self, mut anim: impl Animation + 'static) {
68        anim.plan(&self.state);
69        let rate = anim.rate_fn();
70        let before = self.state.clone();
71        anim.sample(rate.apply(1.0), &mut self.state);
72        let start = self.cursor;
73        self.cursor += anim.duration().max(0.0);
74        self.clips.push(Clip {
75            start,
76            end: self.cursor,
77            before,
78            anim: Some((Box::new(anim), rate)),
79        });
80    }
81
82    /// Holds still for `secs`.
83    pub fn wait(&mut self, secs: f32) {
84        if secs <= 0.0 {
85            return;
86        }
87        // Keyframe now: `add`/`remove` since the last clip must show during the wait.
88        let start = self.cursor;
89        self.cursor += secs;
90        self.clips.push(Clip {
91            start,
92            end: self.cursor,
93            before: self.state.clone(),
94            anim: None,
95        });
96    }
97
98    /// From now on, mobject `id` is `f(scene, secs_since_now)` at every frame, until removed.
99    ///
100    /// `f` sees the scene as animated plus the output of updaters registered before it, so
101    /// register in dependency order. Animations still plan against the un-updated state.
102    pub fn always(
103        &mut self,
104        id: MobjectId,
105        f: impl Fn(&SceneState, f32) -> VState + Send + Sync + 'static,
106    ) {
107        self.updaters.push(Updater {
108            start: self.cursor,
109            id,
110            f: Arc::new(f),
111        });
112    }
113
114    /// Names the current time as a seek point.
115    pub fn marker(&mut self, name: &str) {
116        self.markers.push((name.to_owned(), self.cursor));
117    }
118
119    /// Finishes recording.
120    pub fn bake(mut self) -> BakedTimeline {
121        let tail = Clip {
122            start: self.cursor,
123            end: self.cursor,
124            before: self.state,
125            anim: None,
126        };
127        self.clips.push(tail);
128        BakedTimeline {
129            clips: self.clips,
130            duration: self.cursor,
131            markers: self.markers,
132            updaters: self.updaters,
133        }
134    }
135}
136
137/// The recorded scene: every frame is a pure function of time.
138pub struct BakedTimeline {
139    clips: Vec<Clip>,
140    duration: f32,
141    markers: Vec<(String, f32)>,
142    updaters: Vec<Updater>,
143}
144
145impl BakedTimeline {
146    /// Total length in seconds.
147    pub fn duration(&self) -> f32 {
148        self.duration
149    }
150
151    /// All markers as `(name, time)`, in recording order.
152    pub fn markers(&self) -> &[(String, f32)] {
153        &self.markers
154    }
155
156    /// Time of the first marker called `name`.
157    pub fn marker(&self, name: &str) -> Option<f32> {
158        self.markers
159            .iter()
160            .find(|(n, _)| n == name)
161            .map(|&(_, t)| t)
162    }
163
164    /// The scene at time `t`: the keyframe of the clip containing `t`, plus that clip sampled.
165    /// Costs one clip regardless of position or evaluation order.
166    pub fn eval(&self, t: f32) -> SceneState {
167        // Clips are contiguous in start order and `bake` appends a tail, so this is never empty.
168        let i = self
169            .clips
170            .partition_point(|c| c.start <= t)
171            .saturating_sub(1);
172        let clip = &self.clips[i];
173        let mut out = clip.before.clone();
174        if let Some((anim, rate)) = &clip.anim {
175            let len = clip.end - clip.start;
176            let p = if len > 0.0 {
177                (t - clip.start) / len
178            } else {
179                1.0
180            };
181            anim.sample(rate.apply(p), &mut out);
182        }
183        for u in self.updaters.iter().filter(|u| u.start <= t) {
184            if out.contains_key(&u.id) {
185                let m = (u.f)(&out, t - u.start);
186                out.insert(u.id, m);
187            }
188        }
189        out
190    }
191}