Preview App aabb 示例输出

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

use std::f64::consts::PI;

use ranim::{
    anims::rotating::RotatingAnim,
    color::palettes::manim,
    core::animation::eval::StaticAnim,
    glam::{DVec3, dvec2, dvec3},
    items::{
        debug::VisualizeAabbItem,
        vitem::{
            VItem,
            geometry::{
                Arc, ArcBetweenPoints, Circle, Ellipse, EllipticArc, Polygon, Rectangle,
                RegularPolygon, Square,
            },
        },
    },
    prelude::*,
    utils::rate_functions::smooth,
};

#[scene]
#[wasm_demo_doc]
#[output(dir = "./output/aabb")]
fn aabb(r: &mut RanimScene) {
    let frame_w = 8.0 * 16.0 / 9.0;
    let frame_h = 8.0;
    let cols = 4;
    let rows = 4;
    let cell_w = frame_w / cols as f64;
    let cell_h = frame_h / rows as f64;
    let target = cell_h * 0.6;

    let cell_center = |col: usize, row: usize| -> DVec3 {
        let x = (col as f64 + 0.5) * cell_w - frame_w / 2.0;
        let y = (rows - 1 - row) as f64 * cell_h + cell_h / 2.0 - frame_h / 2.0;
        dvec3(x, y, 0.0)
    };

    // Build all items as VItem, laid out in row-major order (4 cols x 4 rows)
    let star_points: Vec<DVec3> = (0..10)
        .map(|i| {
            let angle = i as f64 / 10.0 * PI * 2.0 + PI / 2.0;
            let rad = if i % 2 == 0 { 1.0 } else { 0.5 };
            dvec3(angle.cos() * rad, angle.sin() * rad, 0.0)
        })
        .chain(std::iter::once(dvec3(0.0, 1.0, 0.0)))
        .collect();

    let items: Vec<VItem> = vec![
        // Row 0: basic shapes
        Square::new(2.0)
            .with(|s| {
                s.set_color(manim::BLUE_C);
            })
            .into(),
        Rectangle::new(3.0, 1.5)
            .with(|s| {
                s.set_color(manim::RED_C);
            })
            .into(),
        Circle::new(1.0)
            .with(|c| {
                c.set_color(manim::GREEN_C);
            })
            .into(),
        Ellipse::new(dvec2(2.0, 1.0))
            .with(|e| {
                e.set_color(manim::TEAL_C);
            })
            .into(),
        // Row 1: arcs & regular polygon
        Arc::new(PI * 1.2, 1.0)
            .with(|a| {
                a.set_stroke_color(manim::YELLOW_D);
            })
            .into(),
        ArcBetweenPoints::new(dvec3(-1.0, -0.5, 0.0), dvec3(1.0, 0.5, 0.0), PI / 3.0)
            .with(|a| {
                a.set_stroke_color(manim::ORANGE);
            })
            .into(),
        EllipticArc::new(0.0, PI * 1.5, dvec2(1.5, 0.8))
            .with(|e| {
                e.set_stroke_color(manim::PINK);
            })
            .into(),
        Polygon::from(RegularPolygon::new(5, 1.0).with(|p| {
            p.set_color(manim::PURPLE_C);
        }))
        .into(),
        // Row 2: polygons & rotated shapes
        Polygon::new(vec![
            dvec3(0.0, 1.0, 0.0),
            dvec3(-1.0, -0.6, 0.0),
            dvec3(1.0, -0.6, 0.0),
            dvec3(0.0, 1.0, 0.0),
        ])
        .with(|p| {
            p.set_color(manim::MAROON_C);
        })
        .into(),
        Polygon::from(RegularPolygon::new(6, 1.0).with(|p| {
            p.set_color(manim::BLUE_D);
        }))
        .into(),
        {
            let mut ellipse: VItem = Ellipse::new(dvec2(2.0, 0.8))
                .with(|e| {
                    e.set_color(manim::RED_D);
                })
                .into();
            ellipse.rotate_on_z(PI / 4.0);
            ellipse
        },
        Polygon::new(star_points)
            .with(|p| {
                p.set_color(manim::YELLOW_B);
            })
            .into(),
        // Row 3: bezier curves — old (control-point min/max) vs new (exact extrema) AABB differ
        //
        // Single segment: handle at (0,3,0), curve only reaches y≈1.5
        VItem::from_vpoints(vec![
            dvec3(-1.0, 0.0, 0.0),
            dvec3(0.0, 3.0, 0.0),
            dvec3(1.0, 0.0, 0.0),
        ])
        .with(|v| {
            v.set_stroke_color(manim::GREEN_C);
        }),
        // S-curve: handles at y=±3, curve stays near y=0
        VItem::from_vpoints(vec![
            dvec3(-1.0, 0.0, 0.0),
            dvec3(-0.3, -3.0, 0.0),
            dvec3(0.0, 0.0, 0.0),
            dvec3(0.3, 3.0, 0.0),
            dvec3(1.0, 0.0, 0.0),
        ])
        .with(|v| {
            v.set_stroke_color(manim::RED_C);
        }),
        // Handle overshoots in x: handle at (3,2,0), endpoint at (1,0,0)
        VItem::from_vpoints(vec![
            dvec3(0.0, 0.0, 0.0),
            dvec3(3.0, 2.0, 0.0),
            dvec3(1.0, 0.0, 0.0),
        ])
        .with(|v| {
            v.set_stroke_color(manim::BLUE_C);
        }),
        // Symmetric loop: handles extend to x=±3
        VItem::from_vpoints(vec![
            dvec3(0.0, -0.5, 0.0),
            dvec3(-3.0, 2.0, 0.0),
            dvec3(0.0, 0.5, 0.0),
            dvec3(3.0, 2.0, 0.0),
            dvec3(0.0, -0.5, 0.0),
        ])
        .with(|v| {
            v.set_stroke_color(manim::YELLOW_D);
        }),
    ];

    let mut content = AnimStack::new();
    for (i, item) in items.into_iter().enumerate() {
        let col = i % cols;
        let row = i / cols;
        let mut wrapped = VisualizeAabbItem(item).with(|v| {
            v.scale_to(ScaleHint::PorportionalY(target));
            v.move_to(cell_center(col, row));
        });
        content.push(seq![
            wrapped.show(),
            wrapped.rotating(PI * 2.0, DVec3::Z).with_rate_func(smooth),
        ]);
    }

    let total_secs = content.duration_secs();
    r.play(CameraFrame::default().show().with_duration(total_secs));
    r.play(content);

    r.insert_time_mark(total_secs, TimeMark::Capture("preview.png".to_string()));
}

/// 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([aabb::scene()]);

        #[cfg(feature = "render")]
        {
            use ranim::{
                items::{RanimItemsPlugin, debug::VisualizeAabbItem, vitem::VItem},
                render::{RenderSetup, extract::ExtractPlugin},
            };

            app.insert_resource(RenderSetup::new(|render_app| {
                render_app.add_plugins((
                    RanimItemsPlugin,
                    ExtractPlugin::<VisualizeAabbItem<VItem>>::default(),
                ));
            }));
        }
    }
}

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())
}