#![no_std]
#![deny(missing_docs)]
#![forbid(unsafe_code)]
use core::ops::{Add, AddAssign, Mul, Sub};
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub struct Vec2 {
pub x: f32,
pub y: f32,
}
impl Vec2 {
pub const ZERO: Self = Self { x: 0.0, y: 0.0 };
pub const fn new(x: f32, y: f32) -> Self {
Self { x, y }
}
pub fn length_squared(&self) -> f32 {
self.x * self.x + self.y * self.y
}
}
impl Add for Vec2 {
type Output = Self;
fn add(self, rhs: Self) -> Self {
Self {
x: self.x + rhs.x,
y: self.y + rhs.y,
}
}
}
impl AddAssign for Vec2 {
fn add_assign(&mut self, rhs: Self) {
self.x += rhs.x;
self.y += rhs.y;
}
}
impl Sub for Vec2 {
type Output = Self;
fn sub(self, rhs: Self) -> Self {
Self {
x: self.x - rhs.x,
y: self.y - rhs.y,
}
}
}
impl Mul<f32> for Vec2 {
type Output = Self;
fn mul(self, rhs: f32) -> Self {
Self {
x: self.x * rhs,
y: self.y * rhs,
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct Body {
pub position: Vec2,
pub velocity: Vec2,
pub acceleration: Vec2,
pub inv_mass: f32,
pub restitution: f32,
pub radius: f32,
}
impl Body {
pub fn new(position: Vec2, mass: f32, radius: f32) -> Self {
let inv_mass = if mass <= 0.0 { 0.0 } else { 1.0 / mass };
Self {
position,
velocity: Vec2::ZERO,
acceleration: Vec2::ZERO,
inv_mass,
restitution: 0.8,
radius,
}
}
pub fn is_static(&self) -> bool {
self.inv_mass == 0.0
}
pub fn apply_force(&mut self, force: Vec2) {
if self.inv_mass > 0.0 {
self.acceleration += force * self.inv_mass;
}
}
}
#[derive(Clone, Copy, Debug)]
pub struct WorldSettings {
pub gravity: Vec2,
pub viscosity: f32,
pub bounds_min: Vec2,
pub bounds_max: Vec2,
}
pub struct World<const N: usize> {
pub bodies: [Option<Body>; N],
pub settings: WorldSettings,
}
impl<const N: usize> World<N> {
pub fn new(settings: WorldSettings) -> Self {
Self {
bodies: [None; N],
settings,
}
}
pub fn bodies_mut(&mut self) -> &mut [Option<Body>; N] {
&mut self.bodies
}
pub fn add_body(&mut self, body: Body) -> Option<usize> {
for (idx, slot) in self.bodies.iter_mut().enumerate() {
if slot.is_none() {
*slot = Some(body);
return Some(idx);
}
}
None
}
pub fn step(&mut self, dt: f32) {
for slot in self.bodies.iter_mut() {
if let Some(body) = slot {
if body.is_static() {
continue; }
body.acceleration += self.settings.gravity;
body.velocity += body.acceleration * dt;
if self.settings.viscosity > 0.0 {
let damping = (1.0 - self.settings.viscosity * dt).max(0.0);
body.velocity = body.velocity * damping;
}
body.position += body.velocity * dt;
body.acceleration = Vec2::ZERO;
Self::resolve_boundaries(body, &self.settings);
}
}
}
fn resolve_boundaries(body: &mut Body, settings: &WorldSettings) {
let min_x = settings.bounds_min.x + body.radius;
let max_x = settings.bounds_max.x - body.radius;
let min_y = settings.bounds_min.y + body.radius;
let max_y = settings.bounds_max.y - body.radius;
if body.position.x < min_x {
body.position.x = min_x;
body.velocity.x = -body.velocity.x * body.restitution;
} else if body.position.x > max_x {
body.position.x = max_x;
body.velocity.x = -body.velocity.x * body.restitution;
}
if body.position.y < min_y {
body.position.y = min_y;
body.velocity.y = -body.velocity.y * body.restitution;
} else if body.position.y > max_y {
body.position.y = max_y;
body.velocity.y = -body.velocity.y * body.restitution;
}
}
}