use crate::math::{Transform, Vec2};
use crate::ecs::{Component, World};
#[derive(Clone, Copy, Debug)]
pub struct RigidBody {
pub velocity: Vec2,
pub acceleration: Vec2,
pub mass: f32,
pub drag: f32,
pub gravity_scale: f32,
pub is_kinematic: bool,
}
impl RigidBody {
pub fn new() -> Self {
Self {
velocity: Vec2::ZERO,
acceleration: Vec2::ZERO,
mass: 1.0,
drag: 0.0,
gravity_scale: 1.0,
is_kinematic: false,
}
}
pub fn dynamic() -> Self {
Self::new()
}
pub fn kinematic() -> Self {
Self {
is_kinematic: true,
..Self::new()
}
}
pub fn with_velocity(mut self, velocity: Vec2) -> Self {
self.velocity = velocity;
self
}
pub fn with_mass(mut self, mass: f32) -> Self {
self.mass = mass;
self
}
pub fn with_drag(mut self, drag: f32) -> Self {
self.drag = drag;
self
}
pub fn apply_force(&mut self, force: Vec2) {
if !self.is_kinematic {
self.acceleration += force / self.mass;
}
}
pub fn apply_impulse(&mut self, impulse: Vec2) {
if !self.is_kinematic {
self.velocity += impulse / self.mass;
}
}
}
impl Default for RigidBody {
fn default() -> Self {
Self::new()
}
}
impl Component for RigidBody {}
#[derive(Clone, Copy, Debug)]
pub enum ColliderShape {
Box { width: f32, height: f32 },
Circle { radius: f32 },
}
#[derive(Clone, Copy, Debug)]
pub struct Collider {
pub shape: ColliderShape,
pub is_trigger: bool,
pub offset: Vec2,
}
impl Collider {
pub fn box_collider(width: f32, height: f32) -> Self {
Self {
shape: ColliderShape::Box { width, height },
is_trigger: false,
offset: Vec2::ZERO,
}
}
pub fn circle_collider(radius: f32) -> Self {
Self {
shape: ColliderShape::Circle { radius },
is_trigger: false,
offset: Vec2::ZERO,
}
}
pub fn as_trigger(mut self) -> Self {
self.is_trigger = true;
self
}
pub fn with_offset(mut self, offset: Vec2) -> Self {
self.offset = offset;
self
}
pub fn get_bounds(&self, transform: &Transform) -> Bounds {
let pos = transform.position + self.offset;
match self.shape {
ColliderShape::Box { width, height } => {
let half_w = width * transform.scale.x / 2.0;
let half_h = height * transform.scale.y / 2.0;
Bounds {
min: Vec2::new(pos.x - half_w, pos.y - half_h),
max: Vec2::new(pos.x + half_w, pos.y + half_h),
}
}
ColliderShape::Circle { radius } => {
let r = radius * transform.scale.x.max(transform.scale.y);
Bounds {
min: Vec2::new(pos.x - r, pos.y - r),
max: Vec2::new(pos.x + r, pos.y + r),
}
}
}
}
}
impl Component for Collider {}
#[derive(Clone, Copy, Debug)]
pub struct Bounds {
pub min: Vec2,
pub max: Vec2,
}
impl Bounds {
pub fn intersects(&self, other: &Bounds) -> bool {
self.min.x < other.max.x
&& self.max.x > other.min.x
&& self.min.y < other.max.y
&& self.max.y > other.min.y
}
pub fn center(&self) -> Vec2 {
(self.min + self.max) / 2.0
}
pub fn size(&self) -> Vec2 {
self.max - self.min
}
}
#[derive(Clone, Copy, Debug)]
pub struct Collision {
pub entity_a: u64,
pub entity_b: u64,
pub normal: Vec2,
pub penetration: f32,
}
pub struct PhysicsSystem {
pub gravity: Vec2,
}
impl PhysicsSystem {
pub fn new() -> Self {
Self {
gravity: Vec2::new(0.0, 980.0),
}
}
pub fn update(&self, world: &mut World, dt: f32) {
for entity in world.entities_mut() {
if entity.has::<RigidBody>() {
let velocity = if let Some(body) = entity.get_mut::<RigidBody>() {
if !body.is_kinematic {
body.acceleration += self.gravity * body.gravity_scale * dt;
body.velocity *= 1.0 - (body.drag * dt).min(1.0);
body.velocity += body.acceleration * dt;
body.acceleration = Vec2::ZERO;
Some(body.velocity)
} else {
None
}
} else {
None
};
if let (Some(transform), Some(vel)) = (entity.get_mut::<Transform>(), velocity) {
transform.position += vel * dt;
}
}
}
}
pub fn detect_collisions(&self, world: &World) -> Vec<Collision> {
let mut collisions = Vec::new();
let entities: Vec<_> = world.entities()
.filter_map(|e| {
if let (Some(transform), Some(collider)) =
(e.get::<Transform>(), e.get::<Collider>()) {
Some((e.id(), transform.clone(), collider.clone()))
} else {
None
}
})
.collect();
for i in 0..entities.len() {
for j in (i + 1)..entities.len() {
let (id_a, transform_a, collider_a) = &entities[i];
let (id_b, transform_b, collider_b) = &entities[j];
let bounds_a = collider_a.get_bounds(transform_a);
let bounds_b = collider_b.get_bounds(transform_b);
if bounds_a.intersects(&bounds_b) {
let collision = self.calculate_collision(
*id_a,
*id_b,
transform_a,
collider_a,
transform_b,
collider_b,
);
if let Some(col) = collision {
collisions.push(col);
}
}
}
}
collisions
}
fn calculate_collision(
&self,
id_a: u64,
id_b: u64,
transform_a: &Transform,
collider_a: &Collider,
transform_b: &Transform,
collider_b: &Collider,
) -> Option<Collision> {
match (&collider_a.shape, &collider_b.shape) {
(ColliderShape::Box { width: w1, height: h1 }, ColliderShape::Box { width: w2, height: h2 }) => {
let pos_a = transform_a.position + collider_a.offset;
let pos_b = transform_b.position + collider_b.offset;
let half_w1 = w1 * transform_a.scale.x / 2.0;
let half_h1 = h1 * transform_a.scale.y / 2.0;
let half_w2 = w2 * transform_b.scale.x / 2.0;
let half_h2 = h2 * transform_b.scale.y / 2.0;
let delta = pos_b - pos_a;
let overlap_x = half_w1 + half_w2 - delta.x.abs();
let overlap_y = half_h1 + half_h2 - delta.y.abs();
if overlap_x > 0.0 && overlap_y > 0.0 {
let (normal, penetration) = if overlap_x < overlap_y {
(Vec2::new(delta.x.signum(), 0.0), overlap_x)
} else {
(Vec2::new(0.0, delta.y.signum()), overlap_y)
};
Some(Collision {
entity_a: id_a,
entity_b: id_b,
normal,
penetration,
})
} else {
None
}
}
(ColliderShape::Circle { radius: r1 }, ColliderShape::Circle { radius: r2 }) => {
let pos_a = transform_a.position + collider_a.offset;
let pos_b = transform_b.position + collider_b.offset;
let radius_a = r1 * transform_a.scale.x.max(transform_a.scale.y);
let radius_b = r2 * transform_b.scale.x.max(transform_b.scale.y);
let delta = pos_b - pos_a;
let distance = delta.length();
let min_distance = radius_a + radius_b;
if distance < min_distance {
let normal = if distance > 0.0 {
delta / distance
} else {
Vec2::new(1.0, 0.0)
};
Some(Collision {
entity_a: id_a,
entity_b: id_b,
normal,
penetration: min_distance - distance,
})
} else {
None
}
}
_ => None, }
}
pub fn resolve_collisions(&self, world: &mut World, collisions: &[Collision]) {
for collision in collisions {
let entity_a = world.entity(collision.entity_a);
let entity_b = world.entity(collision.entity_b);
let (has_body_a, is_trigger_a) = if let Some(e) = entity_a {
(e.has::<RigidBody>(), e.get::<Collider>().map_or(false, |c| c.is_trigger))
} else {
continue;
};
let (has_body_b, is_trigger_b) = if let Some(e) = entity_b {
(e.has::<RigidBody>(), e.get::<Collider>().map_or(false, |c| c.is_trigger))
} else {
continue;
};
if is_trigger_a || is_trigger_b {
continue;
}
if has_body_a && !has_body_b {
if let Some(entity) = world.entity_mut(collision.entity_a) {
if let Some(transform) = entity.get_mut::<Transform>() {
transform.position -= collision.normal * collision.penetration;
}
if let Some(body) = entity.get_mut::<RigidBody>() {
if !body.is_kinematic {
let vel_along_normal = body.velocity.dot(collision.normal);
if vel_along_normal < 0.0 {
body.velocity -= collision.normal * vel_along_normal;
}
}
}
}
} else if !has_body_a && has_body_b {
if let Some(entity) = world.entity_mut(collision.entity_b) {
if let Some(transform) = entity.get_mut::<Transform>() {
transform.position += collision.normal * collision.penetration;
}
if let Some(body) = entity.get_mut::<RigidBody>() {
if !body.is_kinematic {
let vel_along_normal = body.velocity.dot(-collision.normal);
if vel_along_normal < 0.0 {
body.velocity += collision.normal * vel_along_normal;
}
}
}
}
} else if has_body_a && has_body_b {
let half_pen = collision.penetration / 2.0;
if let Some(entity) = world.entity_mut(collision.entity_a) {
if let Some(transform) = entity.get_mut::<Transform>() {
transform.position -= collision.normal * half_pen;
}
}
if let Some(entity) = world.entity_mut(collision.entity_b) {
if let Some(transform) = entity.get_mut::<Transform>() {
transform.position += collision.normal * half_pen;
}
}
}
}
}
}
impl Default for PhysicsSystem {
fn default() -> Self {
Self::new()
}
}