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::() .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>(&self, label: T) -> bool { let label = label.into(); (self.0 == label) || (self.1 == label) } pub fn either_starts_with>(&self, label: T) -> bool { let label = label.into(); self.0.starts_with(&label) || self.1.starts_with(&label) } pub fn one_starts_with>(&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(&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>, mut collision_events: EventWriter, query: Query<&Actor>, ) { let mut current_collisions = HashSet::::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(¤t_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), } impl Default for Collider { fn default() -> Self { Collider::NoCollider } } impl Collider { pub fn rect>(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 + 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 { 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 { 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 } }