
๐ Rust Code
use nannou::prelude::*;
const NUM_BALLS: usize = 10_000;
const WINDOW_SIZE: f32 = 800.0;
const BALL_RADIUS: f32 = 1.0;
const GRAVITY: f32 = 0.005;
const LARGE_CIRCLE_RADIUS: f32 = 400.0;
const RESTITUTION: f32 = 0.95;
const MAX_BOUNCING_COUNT: u32 = 10;
const STOP_THRESHOLD: f32 = 0.1;
struct Ball {
position: Vec2,
velocity: Vec2,
center: Vec2,
gravity_direction: f32,
bouncing_count: u32,
stopped: bool,
}
struct Model {
balls: Vec<Ball>,
}
fn main() {
nannou::app(model).update(update).run();
}
fn model(app: &App) -> Model {
app.new_window()
.size(WINDOW_SIZE as u32, WINDOW_SIZE as u32)
.view(view)
.build()
.unwrap();
let circle_center_top = vec2(0.0, 400.0);
let circle_center_bottom = vec2(0.0, -400.0);
let mut balls = Vec::new();
for _ in 0..(NUM_BALLS / 2) {
let angle = random_range(0.0, PI);
let speed = random_range(0.5, 1.0);
balls.push(Ball {
position: circle_center_top,
velocity: vec2(angle.cos() * speed, -angle.sin() * speed),
center: circle_center_top,
gravity_direction: -1.0,
bouncing_count: 0,
stopped: false,
});
}
for _ in 0..(NUM_BALLS / 2) {
let angle = random_range(0.0, PI);
let speed = random_range(0.5, 1.0);
balls.push(Ball {
position: circle_center_bottom,
velocity: vec2(angle.cos() * speed, angle.sin() * speed),
center: circle_center_bottom,
gravity_direction: 1.0,
bouncing_count: 0,
stopped: false,
});
}
Model { balls }
}
fn update(_app: &App, model: &mut Model, _update: Update) {
for ball in &mut model.balls {
if ball.stopped {
continue;
}
ball.velocity.y += GRAVITY * ball.gravity_direction;
ball.position += ball.velocity;
let distance_from_center = ball.position.distance(ball.center);
let distance_diff = (distance_from_center - LARGE_CIRCLE_RADIUS).abs();
if distance_from_center > LARGE_CIRCLE_RADIUS - BALL_RADIUS {
ball.bouncing_count += 1;
let direction = (ball.position - ball.center).normalize();
ball.position = ball.center + direction * (LARGE_CIRCLE_RADIUS - BALL_RADIUS);
let normal = direction;
let velocity_normal = ball.velocity.dot(normal);
let velocity_tangent = ball.velocity - normal * velocity_normal;
ball.velocity = velocity_tangent + normal * (-velocity_normal * RESTITUTION);
if ball.bouncing_count >= MAX_BOUNCING_COUNT || distance_diff < STOP_THRESHOLD {
ball.stopped = true;
ball.velocity = vec2(0.0, 0.0);
}
}
}
}
fn view(app: &App, model: &Model, frame: Frame) {
let draw = app.draw();
let frame_no = frame.nth();
if frame_no == 0 {
draw.background().color(hsla(0.0, 0.0, 0.01, 1.0));
} else {
draw.rect()
.w_h(WINDOW_SIZE, WINDOW_SIZE)
.color(hsla(0.0, 0.0, 0.01, 0.1));
}
for ball in &model.balls {
if !ball.stopped {
draw.ellipse()
.xy(ball.position)
.radius(BALL_RADIUS)
.color(rgba(1.0, 1.0, 1.0, 0.8));
}
}
draw.to_frame(app, &frame).unwrap();
}