Noon
Animation engine inspired by manim, written in Rust
Install / Use
/learn @yongkyuns/NoonREADME
noon
Experimental animation library with high-level APIs for vector graphics animation.
[!NOTE] This project was developed for experimental purposes and is no longer actively maintained.

Motivation
noon is an animation library that allows you to draw 2D objects and animate them with high-level commands. This project draws inspiration from manim, which is used to create educational videos about math. While Python is a great language for end-users, it would be interesting to explore similar implementation in a compiled language and the possibilities that it would enable, such as real-time interactions and deploying to the web. Along the way, I became interested in Rust and tried to apply it for my learning in this project.
Architecture
Noon is designed as a thin animation-wrapper on top of an awesome existing library called nannou. Nannou and it's dependent crate lyon makes drawing custom shapes rendered in GPU much easier than it would otherwise be using Rust.
To use nannou's per-frame drawing API with noon's animation commands, we cache the user commands in order to interpolate them during animation run-time. Since each animation attributes (e.g. position, size, etc.) are usually independent from one another and we can have many shapes in a scene, noon uses Bevy ECS to keep track of independent animation attributes. This also allows us bypass some tricky ownership-related issues that could arise when dealing with multiple references.
Examples
The following examples demonstrate the current status of this project.

// cargo run --release --example hello_world
fn scene(win_rect: Rect) -> Scene {
// Make a blank scene
let mut scene = Scene::new(win_rect);
// Instantiate a rectangle with builder pattern
let rect = scene
.rectangle()
.with_position(2.0, 0.0)
.with_color(Color::random())
.make();
// Make a circle
let circle = scene
.circle()
.with_position(-2.0, 0.0)
.with_color(Color::random())
.make();
// Wait for a second
scene.wait();
// Show animation for creating the rectangle with builder animation attributes
scene.play(rect.show_creation()).run_time(1.5);
scene.play(circle.show_creation()).run_time(1.5);
// Morph circle into rectangle
scene.play(circle.morph(rect)).run_time(1.5);
scene
}

// cargo run --release --example morph
fn scene(win_rect: Rect) -> Scene {
let mut scene = Scene::new(win_rect);
// Make a text object
let text = scene.text().with_font_size(50).with_text("Hello!").make();
let rectangle = scene.rectangle().with_position(2.0, 0.0).make();
let circle = scene.circle().with_position(-2.0, 0.0).make();
// Make a line object
let line = scene.line().from(-2.0, -2.0).to(2.0, 2.0).make();
// Scene::play can receive multiple animation commands in a Vec
scene
.play(vec![
line.show_creation(),
circle.show_creation(),
rectangle.show_creation(),
text.show_creation(),
])
.lag(1.0); // lag specifies the time delay between each animation
// run_time specifies the duration of each animation
scene
.play(vec![
line.morph(circle),
circle.morph(rectangle),
rectangle.morph(text),
])
.run_time(2.0)
.lag(2.0);
scene
}

// cargo run --release --example morph_text
fn scene(win_rect: Rect) -> Scene {
let mut scene = Scene::new(win_rect);
// Create an empty Vec to contain multiple animations
let mut morph = Vec::new();
let mut show = Vec::new();
// Each animation can be added in a for-loop
for _ in 0..3 {
let text2 = random_text(&mut scene, "This example shows shape transfrom");
show.push(text2.show_creation());
let text = random_text(&mut scene, "Hello World! This is some text");
show.push(text.show_creation());
morph.push(text.morph(text2));
morph.push(text2.fade_out());
}
scene.play(show).run_time(3.0);
scene.wait_for(0.5);
scene.play(morph).run_time(3.0);
scene
}

// cargo run --release --example easing
fn scene(win_rect: Rect) -> Scene {
let mut scene = Scene::new(win_rect);
let mut circles = Vec::new();
let mut show = Vec::new();
let mut to_right = Vec::new();
for i in 0..8 {
let c = scene
.circle()
.with_position(-4.0, 2.0 - i as f32 * 0.5)
.with_radius(0.2)
.with_color(Color::random())
.make();
circles.push(c);
show.push(c.fade_in());
to_right.push(c.move_by(8.0, 0.0));
}
scene.wait();
scene.play(show).lag(0.2);
// These easing functions are available for all animation attributes,
// e.g. position, color, path, angle, scale, etc.
let easing = [
EaseType::Linear,
EaseType::Quad,
EaseType::Quint,
EaseType::Expo,
EaseType::Sine,
EaseType::Back,
EaseType::Bounce,
EaseType::Elastic,
];
for i in 0..8 {
scene
.play(to_right[i].clone())
.lag(0.5)
.rate_func(easing[i]);
}
scene
}

// cargo run --release --example shapes
fn scene(win_rect: Rect) -> Scene {
let mut scene = Scene::new(win_rect);
let mut animations = Vec::new();
let mut show = Vec::new();
let mut move_down = Vec::new();
for _ in 0..1000 {
if noon::rand::random::<bool>() {
let (x, y, w, _h, ang, color) = gen_random_values();
let circle = scene
.circle()
.with_position(x, y)
.with_angle(ang)
.with_color(color)
.with_thin_stroke()
.with_radius(w / 2.0)
.make();
show.push(circle.show_creation());
move_down.push(circle.to_edge(Direction::Down));
let (x, y, w, _h, _ang, color) = gen_random_values();
animations.extend(vec![
circle.set_color(color),
circle.move_to(x, y),
circle.set_radius(w / 2.0),
]);
} else {
let (x, y, w, h, ang, color) = gen_random_values();
let rect = scene
.rectangle()
.with_position(x, y)
.with_angle(ang)
.with_color(color)
.with_thin_stroke()
.with_size(w, h)
.make();
show.push(rect.show_creation());
move_down.push(rect.to_edge(Direction::Down));
let (x, y, w, _h, ang, color) = gen_random_values();
animations.extend(vec![
rect.set_color(color),
rect.move_to(x, y),
rect.set_size(w, h),
rect.rotate(ang),
]);
}
}
scene.wait_for(0.5);
scene.play(show).run_time(1.0).lag(0.001);
scene
.play(animations)
.run_time(3.0)
.lag(0.0001)
.rate_func(EaseType::Quint);
scene
.play(move_down)
.run_time(1.0)
.rate_func(EaseType::BounceOut)
.lag(0.001);
scene
}
Related Skills
node-connect
345.4kDiagnose OpenClaw node connection and pairing failures for Android, iOS, and macOS companion apps
frontend-design
104.6kCreate distinctive, production-grade frontend interfaces with high design quality. Use this skill when the user asks to build web components, pages, or applications. Generates creative, polished code that avoids generic AI aesthetics.
openai-whisper-api
345.4kTranscribe audio via OpenAI Audio Transcriptions API (Whisper).
qqbot-media
345.4kQQBot 富媒体收发能力。使用 <qqmedia> 标签,系统根据文件扩展名自动识别类型(图片/语音/视频/文件)。
