use std::sync::Arc;
use crate::anim::{Animation, RateFn};
use crate::mobject::{MobjectId, SceneState, VState};
struct Clip {
start: f32,
end: f32,
before: SceneState,
anim: Option<(Box<dyn Animation>, RateFn)>,
}
type UpdaterFn = Arc<dyn Fn(&SceneState, f32) -> VState + Send + Sync>;
#[derive(Clone)]
struct Updater {
start: f32,
id: MobjectId,
f: UpdaterFn,
}
#[derive(Default)]
pub struct Scene {
state: SceneState,
clips: Vec<Clip>,
cursor: f32,
next_id: u32,
markers: Vec<(String, f32)>,
updaters: Vec<Updater>,
}
impl Scene {
pub fn new() -> Self {
Self::default()
}
pub fn add(&mut self, m: VState) -> MobjectId {
let id = MobjectId(self.next_id);
self.next_id += 1;
self.state.insert(id, m);
id
}
pub fn remove(&mut self, id: MobjectId) {
self.state.remove(&id);
}
pub fn state(&self) -> &SceneState {
&self.state
}
pub fn time(&self) -> f32 {
self.cursor
}
pub fn play(&mut self, mut anim: impl Animation + 'static) {
anim.plan(&self.state);
let rate = anim.rate_fn();
let before = self.state.clone();
anim.sample(rate.apply(1.0), &mut self.state);
let start = self.cursor;
self.cursor += anim.duration().max(0.0);
self.clips.push(Clip {
start,
end: self.cursor,
before,
anim: Some((Box::new(anim), rate)),
});
}
pub fn wait(&mut self, secs: f32) {
if secs <= 0.0 {
return;
}
let start = self.cursor;
self.cursor += secs;
self.clips.push(Clip {
start,
end: self.cursor,
before: self.state.clone(),
anim: None,
});
}
pub fn always(
&mut self,
id: MobjectId,
f: impl Fn(&SceneState, f32) -> VState + Send + Sync + 'static,
) {
self.updaters.push(Updater {
start: self.cursor,
id,
f: Arc::new(f),
});
}
pub fn marker(&mut self, name: &str) {
self.markers.push((name.to_owned(), self.cursor));
}
pub fn bake(mut self) -> BakedTimeline {
let tail = Clip {
start: self.cursor,
end: self.cursor,
before: self.state,
anim: None,
};
self.clips.push(tail);
BakedTimeline {
clips: self.clips,
duration: self.cursor,
markers: self.markers,
updaters: self.updaters,
}
}
}
pub struct BakedTimeline {
clips: Vec<Clip>,
duration: f32,
markers: Vec<(String, f32)>,
updaters: Vec<Updater>,
}
impl BakedTimeline {
pub fn duration(&self) -> f32 {
self.duration
}
pub fn markers(&self) -> &[(String, f32)] {
&self.markers
}
pub fn marker(&self, name: &str) -> Option<f32> {
self.markers
.iter()
.find(|(n, _)| n == name)
.map(|&(_, t)| t)
}
pub fn eval(&self, t: f32) -> SceneState {
let i = self
.clips
.partition_point(|c| c.start <= t)
.saturating_sub(1);
let clip = &self.clips[i];
let mut out = clip.before.clone();
if let Some((anim, rate)) = &clip.anim {
let len = clip.end - clip.start;
let p = if len > 0.0 {
(t - clip.start) / len
} else {
1.0
};
anim.sample(rate.apply(p), &mut out);
}
for u in self.updaters.iter().filter(|u| u.start <= t) {
if out.contains_key(&u.id) {
let m = (u.f)(&out, t - u.start);
out.insert(u.id, m);
}
}
out
}
}