use legion::Entity;
use crate::core::components::maths::{coordinates::Coordinates, transform::Transform};
use crate::utils::maths::Vector;
struct RectangleColliderInfo<'a> {
width: &'a usize,
height: &'a usize,
transform: &'a Transform,
offset: &'a Vector,
}
impl<'a> RectangleColliderInfo<'a> {
fn of(
width: &'a usize,
height: &'a usize,
transform: &'a Transform,
offset: &'a Vector,
) -> Self {
Self { width, height, transform, offset }
}
}
#[derive(PartialEq, Clone, Eq, Hash)]
pub enum ColliderMask {
None,
Character,
Bullet,
Death,
Landscape,
Custom(String),
}
#[derive(Clone)]
pub enum ColliderType {
Square(usize),
Rectangle(usize, usize),
}
#[derive(Clone)]
pub struct Collider {
collider_mask: ColliderMask,
collider_type: ColliderType,
collision_filter: Vec<ColliderMask>,
collisions: Vec<Collision>,
offset: Vector,
debug_lines: bool,
}
impl Collider {
pub fn new(
collider_mask: ColliderMask,
collision_filter: Vec<ColliderMask>,
collider_type: ColliderType,
) -> Self {
Collider {
collider_mask,
collider_type,
collision_filter,
collisions: vec![],
offset: Vector::default(),
debug_lines: false,
}
}
pub fn with_debug_lines(mut self) -> Self {
self.debug_lines = true;
self
}
pub fn with_offset(mut self, offset: Vector) -> Self {
self.offset = offset;
self
}
pub fn is_colliding(&self) -> bool {
!self.collisions.is_empty()
}
pub fn collisions(&self) -> &Vec<Collision> {
&self.collisions
}
pub fn mask(&self) -> &ColliderMask {
&self.collider_mask
}
pub fn mask_cloned(&self) -> ColliderMask {
self.collider_mask.clone()
}
pub fn filters(&self) -> &Vec<ColliderMask> {
&self.collision_filter
}
pub fn collider_type(&self) -> &ColliderType {
&self.collider_type
}
pub fn offset(&self) -> &Vector {
&self.offset
}
pub(crate) fn debug_lines(&self) -> bool {
self.debug_lines
}
pub(crate) fn clear_collisions(&mut self) {
self.collisions.clear();
}
pub(crate) fn can_collide_with(&self, other: &Collider) -> bool {
self.collision_filter.is_empty() || self.collision_filter.contains(&other.collider_mask)
}
pub(crate) fn collides_with(
&self,
self_transform: &Transform,
target_collider: &Collider,
target_transform: &Transform,
) -> bool {
self.can_collide_with(target_collider)
&& match (&self.collider_type, &target_collider.collider_type) {
(ColliderType::Square(self_size), ColliderType::Square(target_size)) => {
rectangle_collider_vs_square_collider(
RectangleColliderInfo::of(
self_size,
self_size,
self_transform,
&self.offset,
),
RectangleColliderInfo::of(
target_size,
target_size,
target_transform,
&target_collider.offset,
),
)
}
(
ColliderType::Rectangle(self_width, self_height),
ColliderType::Rectangle(target_width, target_height),
) => rectangle_collider_vs_square_collider(
RectangleColliderInfo::of(
self_width,
self_height,
self_transform,
&self.offset,
),
RectangleColliderInfo::of(
target_width,
target_height,
target_transform,
&target_collider.offset,
),
),
(
ColliderType::Square(self_size),
ColliderType::Rectangle(target_width, target_height),
) => rectangle_collider_vs_square_collider(
RectangleColliderInfo::of(self_size, self_size, self_transform, &self.offset),
RectangleColliderInfo::of(
target_width,
target_height,
target_transform,
&target_collider.offset,
),
),
(
ColliderType::Rectangle(self_width, self_height),
ColliderType::Square(target_size),
) => rectangle_collider_vs_square_collider(
RectangleColliderInfo::of(
self_width,
self_height,
self_transform,
&self.offset,
),
RectangleColliderInfo::of(
target_size,
target_size,
target_transform,
&target_collider.offset,
),
),
}
}
pub(crate) fn add_collisions(&mut self, collisions: &mut Vec<Collision>) {
self.collisions.append(collisions);
}
}
fn rectangle_collider_vs_square_collider(
self_collider: RectangleColliderInfo,
target_collider: RectangleColliderInfo,
) -> bool {
let p1 = self_collider.transform.global_translation;
let p2 = target_collider.transform.global_translation;
let (x_min_p1, x_max_p1, y_min_p1, y_max_p1, x_min_p2, x_max_p2, y_min_p2, y_max_p2) = (
p1.x() + self_collider.offset.x,
p1.x() + self_collider.offset.x + *self_collider.width as f32,
p1.y() + self_collider.offset.y,
p1.y() + self_collider.offset.y + *self_collider.height as f32,
p2.x() + target_collider.offset.x,
p2.x() + target_collider.offset.x + *target_collider.width as f32,
p2.y() + target_collider.offset.y,
p2.y() + target_collider.offset.y + *target_collider.height as f32,
);
x_min_p1 < x_max_p2 && x_max_p1 > x_min_p2 && y_min_p1 < y_max_p2 && y_max_p1 > y_min_p2
}
#[derive(Clone)]
pub struct Collision {
pub(crate) mask: ColliderMask,
pub(crate) entity: Entity,
pub(crate) coordinates: Coordinates,
}
impl Collision {
pub fn entity(&self) -> &Entity {
&self.entity
}
pub fn mask(&self) -> &ColliderMask {
&self.mask
}
pub fn coordinates(&self) -> &Coordinates {
&self.coordinates
}
}
pub(crate) struct ColliderDebug;
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_can_collide_with() {
let bullet = Collider::new(ColliderMask::Bullet, vec![], ColliderType::Square(5));
let ship = Collider::new(
ColliderMask::Character,
vec![ColliderMask::Bullet],
ColliderType::Square(5),
);
let land = Collider::new(ColliderMask::Landscape, vec![], ColliderType::Square(5));
assert_eq!(false, ship.can_collide_with(&land));
assert_eq!(true, ship.can_collide_with(&bullet));
}
#[test]
fn test_collides_with_square() {
let bullet = Collider::new(ColliderMask::Bullet, vec![], ColliderType::Square(5));
let _ship = Collider::new(ColliderMask::Character, vec![], ColliderType::Square(5));
let bullet_transform = Transform::from_xy(4., 4.);
let ship_transform_in = Transform::from_xy(5., 5.);
let ship_transform_out = Transform::from_xy(50., 50.);
assert_eq!(true, bullet.collides_with(&ship_transform_in, &bullet, &bullet_transform));
assert_eq!(false, bullet.collides_with(&ship_transform_out, &bullet, &bullet_transform));
}
#[test]
fn test_does_notcollides_with_square_if_offsets_too_far() {
let mut bullet = Collider::new(
ColliderMask::Bullet,
vec![ColliderMask::Character],
ColliderType::Square(5),
);
bullet = bullet.with_offset(Vector::new(-3., -3.));
let mut ship = Collider::new(
ColliderMask::Character,
vec![ColliderMask::Bullet],
ColliderType::Square(5),
);
ship = ship.with_offset(Vector::new(3., 3.));
let bullet_transform = Transform::from_xy(5., 5.);
let ship_transform = Transform::from_xy(5., 5.);
assert_eq!(false, bullet.collides_with(&bullet_transform, &ship, &ship_transform));
}
#[test]
fn test_does_collides_with_square_if_offsets_close_enough() {
let mut bullet = Collider::new(
ColliderMask::Bullet,
vec![ColliderMask::Character],
ColliderType::Square(5),
);
bullet = bullet.with_offset(Vector::new(-1., -1.));
let mut ship = Collider::new(
ColliderMask::Character,
vec![ColliderMask::Bullet],
ColliderType::Square(5),
);
ship = ship.with_offset(Vector::new(1., 1.));
let bullet_transform = Transform::from_xy(5., 5.);
let ship_transform = Transform::from_xy(5., 5.);
assert_eq!(true, bullet.collides_with(&bullet_transform, &ship, &ship_transform));
}
}