motion-canvas-rs 0.1.7

A high-performance vector animation engine inspired by Motion Canvas, built on Vello and Typst.
Documentation
use crate::engine::animation::Node;
use vello::Scene;

pub trait Scene2D {
    fn render(&self, scene: &mut Scene);
    fn update(&mut self, dt: std::time::Duration);
    fn state_hash(&self) -> u64;
}

pub struct BaseScene {
    pub nodes: Vec<Box<dyn Node>>,
    pub video_timeline: crate::engine::animation::Timeline,
    #[cfg(feature = "audio")]
    pub audio_timeline: crate::engine::animation::Timeline,
}

impl BaseScene {
    pub fn new() -> Self {
        Self {
            nodes: Vec::new(),
            video_timeline: crate::engine::animation::Timeline::new(),
            #[cfg(feature = "audio")]
            audio_timeline: crate::engine::animation::Timeline::new(),
        }
    }

    pub fn add(&mut self, node: Box<dyn Node>) {
        self.nodes.push(node);
    }

    pub fn reset(&mut self) {
        self.video_timeline.reset();
        #[cfg(feature = "audio")]
        self.audio_timeline.reset();
        for node in &mut self.nodes {
            node.reset();
        }
    }

    #[cfg(feature = "audio")]
    pub fn collect_audio_events(
        &mut self,
        current_time: std::time::Duration,
        events: &mut Vec<crate::engine::animation::base::AudioEvent>,
    ) {
        self.video_timeline
            .collect_audio_events(current_time, events);
        self.audio_timeline
            .collect_audio_events(current_time, events);
    }
}

impl Scene2D for BaseScene {
    fn render(&self, scene: &mut Scene) {
        for node in &self.nodes {
            node.render(scene, vello::kurbo::Affine::IDENTITY, 1.0);
        }
    }

    fn update(&mut self, dt: std::time::Duration) {
        self.video_timeline.update(dt);
        #[cfg(feature = "audio")]
        self.audio_timeline.update(dt);
        for node in &mut self.nodes {
            node.update(dt);
        }
    }

    fn state_hash(&self) -> u64 {
        use rayon::prelude::*;
        self.nodes
            .par_iter()
            .enumerate()
            .map(|(i, node)| crate::engine::util::hash::combine_hashes(node.state_hash(), i as u64))
            .reduce(|| 0u64, |a, b| a.wrapping_add(b))
    }
}