Preview App iterative_spring 示例输出

scenes.rs(共享场景与项目装配)

//! Iterative animation example: a damped spring (state stepped by a closure,
//! no ECS required).
//!
//! The spring's state (`x`, `v`) is owned by `Iterative` and advanced by a
//! closure via semi-implicit Euler; the `Extract` impl projects the state into
//! a rectangle once per frame, with the displacement carried by a
//! `Translation` wrapper. Reset is structural — the adapter restores the
//! stored initial state, so `SceneEvaluator::sample_at` matches forward
//! advancement for free.
//!
//! The spring's logical duration is the local `sim_secs` value captured by the
//! closure; the same value is passed to `with_duration`, so physical time and
//! timeline time stay in sync.

use ranim::{
    color::palettes::manim, core::animation::eval::iterative::Iterative, glam::dvec3,
    items::vitem::geometry::Rectangle, prelude::*,
};

/// The spring's state: displacement and velocity.
#[derive(Clone)]
struct SpringState {
    x: f64,
    v: f64,
}

#[cfg(feature = "render")]
impl ranim::render::extract::Extract for SpringState {
    fn extract(&self, ctx: &mut ranim::render::extract::ExtractContext<'_>) {
        let mut ball = Rectangle::new(0.6, 0.6).transformed(Translation(dvec3(self.x, 0.0, 0.0)));
        ball.set_fill_color(manim::BLUE_C);
        ball.set_stroke_opacity(0.0);
        ball.extract(ctx);
    }
}

const K: f64 = 25.0;
const C: f64 = 1.0;

#[scene]
#[wasm_demo_doc]
#[output(dir = "./output/iterative_spring")]
fn iterative_spring(r: &mut RanimScene) {
    let sim_secs = 4.0;
    r.play(CameraFrame::default().show().with_duration(sim_secs));
    r.play(
        Iterative::from_fn(
            SpringState { x: 1.0, v: 0.0 },
            move |state: &mut SpringState, _alpha: f64, delta_alpha: f64| {
                let dt = sim_secs * delta_alpha;
                let acc = -K * state.x - C * state.v;
                state.v += acc * dt;
                state.x += state.v * dt;
            },
        )
        .with_duration(sim_secs),
    );
    r.insert_time_mark(sim_secs / 2.0, TimeMark::Capture("preview.png".to_string()));
}

#[cfg(feature = "render")]
pub(crate) fn configure_render(app: &mut bevy_app::App) {
    use ranim::render::extract::ExtractPlugin;

    app.add_plugins(ExtractPlugin::<SpringState>::default());
}

/// Register the scenes provided by this example.
pub struct ScenesPlugin;

impl ranim::Plugin for ScenesPlugin {
    fn build(&self, app: &mut bevy_app::App) {
        app.add_scenes([iterative_spring::scene()]);

        #[cfg(feature = "render")]
        {
            use ranim::{items::RanimItemsPlugin, render::RenderSetup};

            app.insert_resource(RenderSetup::new(|render_app| {
                render_app.add_plugins(RanimItemsPlugin);
                configure_render(render_app);
            }));
        }
    }
}

main.rs(平台入口)

mod scenes;

#[cfg(not(target_arch = "wasm32"))]
fn main() {
    #[cfg(feature = "cmd-render")]
    {
        use ranim::prelude::*;
        let mut app = App::new();
        app.add_plugins(scenes::ScenesPlugin);
        run_cli(app.build());
    }
    #[cfg(not(feature = "cmd-render"))]
    compile_error!("native examples require the `cmd-render` feature (enabled by default)");
}

// The WASM module is driven by JavaScript through app().
#[cfg(target_arch = "wasm32")]
fn main() {}

#[cfg(target_arch = "wasm32")]
#[wasm_bindgen::prelude::wasm_bindgen]
pub fn app() -> ranim::wasm::WasmApp {
    use ranim::prelude::*;

    let mut app = App::new();
    app.add_plugins(scenes::ScenesPlugin);
    ranim::wasm::WasmApp::new(app.build())
}