use geo_clipper::Clipper;
use geo_types::{Coord, LineString};
use crate::core::components::maths::{coordinates::Coordinates, Pivot, transform::Transform};
use crate::utils::maths::{centroid_polygon, rotate_point_around_pivot, Vector};
use hecs::Entity;
#[derive(PartialEq, Clone, Eq, Hash, Debug)]
pub enum ColliderMask {
None,
Character,
Bullet,
Death,
Landscape,
Item,
Custom(String),
}
#[derive(Clone)]
pub enum ColliderType {
Square(usize),
Rectangle(usize, usize),
Polygon(Vec<Coordinates>)
}
#[derive(Clone)]
pub struct Collider {
collider_mask: ColliderMask,
collider_type: ColliderType,
collision_filter: Vec<ColliderMask>,
collisions: Vec<Collision>,
offset: Vector,
debug_lines: bool,
local_pivot: Option<Pivot>,
parent_pivot: Option<Pivot>,
}
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,
local_pivot: None,
parent_pivot: None,
}
}
pub fn with_debug_lines(mut self) -> Self {
self.debug_lines = true;
self
}
pub fn with_custom_pivot(mut self, pivot: Pivot) -> Self {
self.local_pivot = Some(pivot);
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 set_parent_pivot(&mut self, parent_pivot: Pivot) {
self.parent_pivot = Some(parent_pivot);
}
pub(crate) fn get_pivot(&self) -> Pivot {
if self.local_pivot.is_some() {
self.local_pivot.unwrap()
} else if self.parent_pivot.is_some() {
self.parent_pivot.unwrap()
} else {
Pivot::TopLeft
}
}
pub(crate) fn collider_polygon(&self, transform: &Transform) -> geo_types::Polygon::<f32> {
let base_x = transform.global_translation.x + self.offset.x;
let base_y = transform.global_translation.y + self.offset.y;
let vec = self.collider_coordinates(base_x, base_y);
let pivot_point = match self.get_pivot() {
Pivot::TopLeft => { Coordinates::new(base_x, base_y) }
Pivot::Center => { centroid_polygon(&vec) }
Pivot::Custom(x,y) => {Coordinates::new(base_x + x,base_y + y)}
};
let coords: Vec<Coord<f32>> = vec.iter().map(|c| rotate_point_around_pivot(c, &pivot_point, transform.global_angle))
.map(|c| {
Coord { x: c.x, y: c.y }
})
.collect();
geo_types::Polygon::<f32>::new(LineString::<f32>(coords), vec![])
}
pub(crate) fn collider_coordinates(&self, base_x: f32, base_y: f32) -> Vec<Coordinates> {
match self.collider_type() {
ColliderType::Square(size) => {
vec![
Coordinates::new(base_x + 0., base_y + 0.),
Coordinates::new(base_x + *size as f32, base_y + 0.),
Coordinates::new(base_x + *size as f32, base_y + *size as f32),
Coordinates::new(base_x + 0., base_y + *size as f32),
]
}
ColliderType::Rectangle(width, height) => {
vec![
Coordinates::new(base_x + 0., base_y + 0.),
Coordinates::new(base_x + *width as f32, base_y + 0.),
Coordinates::new(base_x + *width as f32, base_y + *height as f32),
Coordinates::new(base_x + 0., base_y + *height as f32),
]
}
ColliderType::Polygon(coordinates) => {
coordinates.iter().map(|c| Coordinates::new(base_x + c.x, base_y + c.y)).collect()
}
}
}
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,
) -> Option<CollisionArea> {
if !self.can_collide_with(target_collider) {
return None;
}
let self_polygon = self.collider_polygon(self_transform);
let target_polygon = target_collider.collider_polygon(target_transform);
let result = self_polygon.intersection(&target_polygon, 1.0);
if !result.0.is_empty() {
let collision = result.0.first().unwrap();
let coordinates: Vec<Coordinates> = collision.exterior().0.iter().map(|c| Coordinates::new(c.x, c.y)).collect();
Some(CollisionArea{
coordinates
})
} else {
None
}
}
pub(crate) fn add_collisions(&mut self, collisions: &mut Vec<Collision>) {
self.collisions.append(collisions);
}
}
#[derive(Clone, Debug)]
pub struct Collision {
pub(crate) mask: ColliderMask,
pub(crate) entity: Entity,
pub(crate) coordinates: Coordinates,
pub(crate) collision_area: CollisionArea,
}
impl Collision {
pub fn entity(&self) -> &Entity {
&self.entity
}
pub fn mask(&self) -> &ColliderMask {
&self.mask
}
pub fn coordinates(&self) -> &Coordinates {
&self.coordinates
}
pub fn area(&self) -> &CollisionArea {
&self.collision_area
}
}
#[derive(Clone, Debug)]
pub struct CollisionArea {
pub(crate) coordinates: Vec<Coordinates>,
}
impl CollisionArea {
pub fn polygon(&self) -> &Vec<Coordinates> {
&self.coordinates
}
pub fn max_x(&self) -> f32 {
self.coordinates.iter().map(|c|c.x).reduce(f32::max).unwrap()
}
pub fn min_x(&self) -> f32 {
self.coordinates.iter().map(|c|c.x).reduce(f32::min).unwrap()
}
pub fn max_y(&self) -> f32 {
self.coordinates.iter().map(|c|c.y).reduce(f32::max).unwrap()
}
pub fn min_y(&self) -> f32 {
self.coordinates.iter().map(|c|c.y).reduce(f32::min).unwrap()
}
}
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!(!ship.can_collide_with(&land));
assert!(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 bullet_transform2 = Transform::from_xy(9., 9.);
let ship_transform_in = Transform::from_xy(5., 5.);
let ship_transform_in2 = Transform::from_xy(8.99999, 8.99999);
let ship_transform_out = Transform::from_xy(50., 50.);
assert!(ship.collides_with(&ship_transform_in, &bullet, &bullet_transform).is_some());
assert!(ship.collides_with(&ship_transform_in2, &bullet, &bullet_transform).is_some());
assert!(ship.collides_with(&ship_transform_in, &bullet, &bullet_transform2).is_some());
assert!(bullet.collides_with(&ship_transform_out, &bullet, &bullet_transform).is_none());
}
#[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!(bullet.collides_with(&bullet_transform, &ship, &ship_transform).is_none());
}
#[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!(bullet.collides_with(&bullet_transform, &ship, &ship_transform).is_some());
}
}