veebee 1.1.1

A Simple Rust Game Engine For 2D
Documentation
use crate::actor::Actor;
use bevy::prelude::*;
use std::{collections::HashSet, hash::Hash};

pub struct PhysicsPlugin;

impl Plugin for PhysicsPlugin {
    fn build(&self, app: &mut AppBuilder) {
        app.add_event::<CollisionEvent>()
            .add_system(collision_detection.system());
    }
}

#[derive(Debug, Clone)]
pub struct CollisionEvent {
    pub state: CollisionState,
    pub pair: CollisionPair,
}

#[derive(Debug, Clone, Copy)]
pub enum CollisionState {
    Begin,
    End,
}

impl CollisionState {
    pub fn is_begin(&self) -> bool {
        match self {
            CollisionState::Begin => true,
            CollisionState::End => false,
        }
    }
    pub fn is_end(&self) -> bool {
        match self {
            CollisionState::Begin => false,
            CollisionState::End => true,
        }
    }
}

#[derive(Debug, Default, Eq, Clone)]
pub struct CollisionPair(pub String, pub String);

impl CollisionPair {
    pub fn either_contains<T: Into<String>>(&self, label: T) -> bool {
        let label = label.into();
        (self.0 == label) || (self.1 == label)
    }
    pub fn either_starts_with<T: Into<String>>(&self, label: T) -> bool {
        let label = label.into();
        self.0.starts_with(&label) || self.1.starts_with(&label)
    }
    pub fn one_starts_with<T: Into<String>>(&self, label: T) -> bool {
        let label = label.into();
        let a_matches = self.0.starts_with(&label);
        let b_matches = self.1.starts_with(&label);
        (a_matches && !b_matches) || (!a_matches && b_matches)
    }
}

impl PartialEq for CollisionPair {
    fn eq(&self, other: &Self) -> bool {
        ((self.0 == other.0) && (self.1 == other.1)) || ((self.0 == other.1) && (self.1 == other.0))
    }
}

impl Hash for CollisionPair {
    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {

        if self.0 < self.1 {
            self.0.hash(state);
            self.1.hash(state);
        } else {
            self.1.hash(state);
            self.0.hash(state);
        }
    }
}

fn collision_detection(
    mut existing_collisions: Local<HashSet<CollisionPair>>,
    mut collision_events: EventWriter<CollisionEvent>,
    query: Query<&Actor>,
) {
    let mut current_collisions = HashSet::<CollisionPair>::new();
    'outer: for actor1 in query.iter().filter(|a| a.collision) {
        for actor2 in query.iter().filter(|a| a.collision) {
            if actor1.label == actor2.label {

                continue 'outer;
            }
            if Collider::colliding(actor1, actor2) {
                current_collisions
                    .insert(CollisionPair(actor1.label.clone(), actor2.label.clone()));
            }
        }
    }

    let beginning_collisions: Vec<_> = current_collisions
        .difference(&existing_collisions)
        .cloned()
        .collect();

    collision_events.send_batch(beginning_collisions.iter().map(|p| CollisionEvent {
        state: CollisionState::Begin,
        pair: p.clone(),
    }));

    for beginning_collision in beginning_collisions {
        existing_collisions.insert(beginning_collision);
    }

    let ending_collisions: Vec<_> = existing_collisions
        .difference(&current_collisions)
        .cloned()
        .collect();

    collision_events.send_batch(ending_collisions.iter().map(|p| CollisionEvent {
        state: CollisionState::End,
        pair: p.clone(),
    }));

    for ending_collision in ending_collisions {
        let _ = existing_collisions.remove(&ending_collision);
    }
}

#[derive(Clone, Debug)]
pub enum Collider {
    NoCollider,
    Poly(Vec<Vec2>),
}

impl Default for Collider {
    fn default() -> Self {
        Collider::NoCollider
    }
}

impl Collider {
    pub fn rect<T: Into<Vec2>>(topleft: T, bottomright: T) -> Self {
        let topleft = topleft.into();
        let bottomright = bottomright.into();
        Self::Poly(vec![
            topleft,
            Vec2::new(bottomright.x, topleft.y),
            bottomright,
            Vec2::new(topleft.x, bottomright.y),
        ])
    }
    pub fn poly<T: Into<Vec2> + Copy>(points: &[T]) -> Self {
        Self::Poly(points.iter().map(|&x| x.into()).collect())
    }
    pub fn circle_custom(radius: f32, vertices: usize) -> Self {
        let mut points = vec![];
        for x in 0..=vertices {
            let inner = 2.0 * std::f64::consts::PI / vertices as f64 * x as f64;
            points.push(Vec2::new(
                inner.cos() as f32 * radius,
                inner.sin() as f32 * radius,
            ));
        }
        Self::Poly(points)
    }
    pub fn circle(radius: f32) -> Self {
        Self::circle_custom(radius, 16)
    }
    pub fn is_poly(&self) -> bool {
        matches!(self, Self::Poly(_))
    }
    fn rotated(&self, rotation: f32) -> Vec<Vec2> {
        let mut rotated_points = Vec::new();
        if let Self::Poly(points) = self {
            let sin = rotation.sin();
            let cos = rotation.cos();
            for point in points.iter() {
                rotated_points.push(Vec2::new(
                    point.x * cos - point.y * sin,
                    point.x * sin + point.y * cos,
                ));
            }
        }
        rotated_points
    }
    fn relative_to(&self, actor: &Actor) -> Vec<Vec2> {
        self.rotated(actor.rotation)
            .iter()
            .map(|&v| v * actor.scale + actor.translation) 
            .collect()
    }
    pub fn colliding(actor1: &Actor, actor2: &Actor) -> bool {
        use Collider::*;
        if let NoCollider = actor1.collider {
            return false;
        }
        if let NoCollider = actor2.collider {
            return false;
        }
        if actor1.collider.is_poly() && actor2.collider.is_poly() {
            let poly1 = actor1.collider.relative_to(actor1);
            let poly2 = actor2.collider.relative_to(actor2);

            for poly in vec![poly1.clone(), poly2.clone()] {
                for (idx, &p1) in poly.iter().enumerate() {
                    let p2 = poly[(idx + 1) % poly.len()];
                    let normal = Vec2::new(p2.y - p1.y, p1.x - p2.x);

                    let mut min_a = None;
                    let mut max_a = None;
                    for &p in poly1.iter() {
                        let projected = normal.x * p.x + normal.y * p.y;
                        if min_a.is_none() || projected < min_a.unwrap() {
                            min_a = Some(projected);
                        }
                        if max_a.is_none() || projected > max_a.unwrap() {
                            max_a = Some(projected);
                        }
                    }

                    let mut min_b = None;
                    let mut max_b = None;
                    for &p in poly2.iter() {
                        let projected = normal.x * p.x + normal.y * p.y;
                        if min_b.is_none() || projected < min_b.unwrap() {
                            min_b = Some(projected);
                        }
                        if max_b.is_none() || projected > max_b.unwrap() {
                            max_b = Some(projected);
                        }
                    }

                    if max_a < min_b || max_b < min_a {
                        return false;
                    }
                }
            }
            return true;
        }
        false
    }
}