scenes.rs(共享场景与项目装配)
use ranim::glam;
use ranim_items::vitem::DEFAULT_STROKE_WIDTH;
use std::f64::consts::PI;
use ranim::{
anims::{creation::WritingAnim, fading::FadingAnim, morph::MorphAnim},
color,
color::palettes::manim,
core::components::width::Width,
items::vitem::{
VItem,
geometry::{Circle, Square},
svg::SvgItem,
typst::typst_svg,
},
prelude::*,
utils::rate_functions::smooth,
};
use glam::DVec3;
// MARK: ranim_text
#[scene]
#[wasm_demo_doc]
#[output(dir = "./output/extract_vitem_visualize")]
fn ranim_text(r: &mut RanimScene) {
let mut cam = CameraFrame::default();
let text = Vec::<VItem>::from(SvgItem::new(typst_svg("Ranim")).with(|item| {
item.set_fill_color(manim::WHITE)
.set_fill_opacity(0.5)
.scale_to_with_stroke(ScaleHint::PorportionalY(3.6))
.move_to(DVec3::ZERO);
}));
let visual_text = text.into_iter().map(VisualVItem).collect::<Vec<_>>();
let mut camera = AnimSequence::new();
camera
.push(cam.show())
.hold(1.0)
.push(
cam.morph(|cam| {
cam.scale = 0.3;
cam.up = DVec3::NEG_X;
cam.pos.shift(DVec3::NEG_X * 6.0);
})
.with_rate_func(smooth),
)
.hold(1.0)
.push(
cam.morph(|cam| {
cam.pos.shift(DVec3::X * 12.0);
})
.with_duration(7.0)
.with_rate_func(smooth),
)
.hold(1.0)
.push(cam.morph_to(CameraFrame::default()).with_rate_func(smooth));
let content = visual_text.show().with_duration(camera.cursor_sec());
r.play(camera);
r.play(content);
r.insert_time_mark(5.0, TimeMark::Capture("preview-ranim_text.png".to_string()));
}
#[scene(name = "extract_vitem_visualize")]
#[wasm_demo_doc]
#[output(dir = "./output/extract_vitem_visualize")]
pub fn hello_ranim(r: &mut RanimScene) {
let square = VisualVItem(VItem::from(Square::new(2.0).with(|square| {
square.set_color(manim::BLUE_C);
})));
let mut circle_item = VItem::from(Circle::new(2.0).with(|circle| {
circle.set_color(manim::GREEN_C);
}));
circle_item.with_origin(AabbPoint::CENTER, |x| {
x.rotate_on_z(-PI / 4.0 + PI);
});
let mut circle = VisualVItem(circle_item);
let mut content = seq![
square
.clone()
.morph_to(circle.clone())
.with_rate_func(smooth)
];
content
.hold(1.0)
.push(
circle
.clone()
.unwrite()
.with_duration(2.0)
.with_rate_func(smooth),
)
.push(circle.write().with_duration(2.0).with_rate_func(smooth))
.push(circle.fade_out().with_rate_func(smooth));
r.play(
CameraFrame::default()
.show()
.with_duration(content.cursor_sec()),
);
r.play(content);
r.insert_time_mark(
3.2,
TimeMark::Capture("preview-hello_ranim.png".to_string()),
);
}
#[derive(Clone)]
pub struct VisualVItem(VItem);
impl Interpolatable for VisualVItem {
fn lerp(&self, target: &Self, t: f64) -> Self {
Self(self.0.lerp(&target.0, t))
}
}
impl Alignable for VisualVItem {
fn is_aligned(&self, other: &Self) -> bool {
self.0.is_aligned(&other.0)
}
fn align_with(&mut self, other: &mut Self) {
self.0.align_with(&mut other.0);
}
}
impl Partial for VisualVItem {
fn get_partial(&self, range: std::ops::Range<f64>) -> Self {
Self(self.0.get_partial(range))
}
fn get_partial_closed(&self, range: std::ops::Range<f64>) -> Self {
Self(self.0.get_partial_closed(range))
}
}
impl Opacity for VisualVItem {
fn set_opacity(&mut self, opacity: f32) -> &mut Self {
self.0.set_opacity(opacity);
self
}
}
impl StrokeColor for VisualVItem {
fn set_stroke_color(&mut self, color: color::AlphaColor<color::Srgb>) -> &mut Self {
self.0.set_stroke_color(color);
self
}
fn set_stroke_opacity(&mut self, opacity: f32) -> &mut Self {
self.0.set_stroke_opacity(opacity);
self
}
fn stroke_color(&self) -> color::AlphaColor<color::Srgb> {
self.0.stroke_color()
}
}
impl FillColor for VisualVItem {
fn set_fill_color(&mut self, color: color::AlphaColor<color::Srgb>) -> &mut Self {
self.0.set_fill_color(color);
self
}
fn set_fill_opacity(&mut self, opacity: f32) -> &mut Self {
self.0.set_fill_opacity(opacity);
self
}
fn fill_color(&self) -> color::AlphaColor<color::Srgb> {
self.0.fill_color()
}
}
impl StrokeWidth for VisualVItem {
fn stroke_width(&self) -> f32 {
self.0.stroke_width()
}
fn apply_stroke_func(&mut self, f: impl for<'a> Fn(&'a mut [Width])) -> &mut Self {
self.0.apply_stroke_func(f);
self
}
fn set_stroke_width(&mut self, width: f32) -> &mut Self {
self.0.set_stroke_width(width);
self
}
}
#[cfg(feature = "render")]
impl ranim::render::extract::Extract for VisualVItem {
fn extract(&self, ctx: &mut ranim::render::extract::ExtractContext<'_>) {
// The VItem itself
self.0.extract(ctx);
// VItem's vpoints as circles
self.0.vpoints.get_subpaths().iter().for_each(|subpath| {
let subpath_len = subpath.len();
subpath.iter().enumerate().for_each(|(idx, p)| {
if idx == subpath_len - 1 && !idx.is_multiple_of(2) {
return;
}
let point = if idx.is_multiple_of(2) {
Circle::new(0.06).with(|circle| {
circle
.set_color(if idx == 0 {
manim::GREEN_C
} else if idx / 2 == (subpath_len - 1) / 2 {
manim::RED_C
} else {
manim::BLUE_C
})
.set_fill_opacity(0.6);
})
} else {
Circle::new(0.04).with(|circle| {
circle
.set_color(manim::WHITE)
.set_stroke_opacity(0.8)
.set_fill_opacity(0.4);
})
}
.transformed(Translation(*p));
point.extract(ctx);
});
});
// lines between VItem's vpoints
self.0
.vpoints
.iter()
.step_by(2)
.zip(self.0.vpoints.iter().skip(1).step_by(2))
.zip(self.0.vpoints.iter().skip(2).step_by(2))
.for_each(|((p0, p1), p2)| {
if p0 != p1 {
VItem::from_vpoints(vec![*p0, (p0 + p1) / 2.0, *p1])
.with(|x| {
x.set_stroke_width(DEFAULT_STROKE_WIDTH * 0.75);
})
.extract(ctx);
VItem::from_vpoints(vec![*p1, (p1 + p2) / 2.0, *p2])
.with(|x| {
x.set_stroke_width(DEFAULT_STROKE_WIDTH * 0.75);
})
.extract(ctx);
}
});
}
}
impl Empty for VisualVItem {
fn empty() -> Self {
Self(VItem::empty())
}
}
#[cfg(feature = "render")]
pub(crate) fn configure_render(app: &mut bevy_app::App) {
use ranim::render::extract::ExtractPlugin;
app.add_plugins((
ExtractPlugin::<VisualVItem>::default(),
ExtractPlugin::<Vec<VisualVItem>>::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([ranim_text::scene(), hello_ranim::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())
}