shadow_engine_2d 2.0.1

A modern, high-performance 2D game engine built in Rust with ECS, physics, particles, audio, and more
Documentation
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) {
        // Apply physics to rigid bodies
        for entity in world.entities_mut() {
            if entity.has::<RigidBody>() {
                // Update rigid body physics
                let velocity = if let Some(body) = entity.get_mut::<RigidBody>() {
                    if !body.is_kinematic {
                        // Apply gravity
                        body.acceleration += self.gravity * body.gravity_scale * dt;

                        // Apply drag
                        body.velocity *= 1.0 - (body.drag * dt).min(1.0);

                        // Update velocity
                        body.velocity += body.acceleration * dt;

                        // Reset acceleration
                        body.acceleration = Vec2::ZERO;

                        Some(body.velocity)
                    } else {
                        None
                    }
                } else {
                    None
                };

                // Update position
                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, // Mixed shapes not implemented yet
        }
    }

    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;
            };

            // Skip triggers
            if is_trigger_a || is_trigger_b {
                continue;
            }

            // Resolve collision
            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 {
                // Both have rigid bodies - split the resolution
                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()
    }
}