#![allow(clippy::needless_return)]
pub mod polygon;
pub mod circle;
pub mod aabb;
pub mod capsule;
pub mod parallelogram;
pub mod line;
pub trait Shape
{
fn position(&self) -> (f32, f32);
fn set_position(&mut self, position: (f32, f32));
fn num_axes(&self) -> usize;
fn get_axis(&self, index: usize, target: (f32, f32)) -> (f32, f32);
fn project(&self, axis: (f32, f32), normalize: bool) -> (f32, f32);
fn needs_closest(&self, index: usize) -> bool;
fn get_closest(&self, target: (f32, f32)) -> (f32, f32);
fn point(&self, index: usize) -> (f32, f32);
}
pub trait Rotate
{
fn rotate(&mut self, angle: f32);
fn rotate_sincos(&mut self, sin: f32, cos: f32);
}
#[macro_export]
macro_rules! rotate
{
($s: expr, $c: expr, $v: expr) =>
{
($c * $v.0 - $s * $v.1, $s * $v.0 + $c * $v.1 )
};
}
pub fn sat_overlap(left: &(impl Shape + ?Sized), right: &(impl Shape + ?Sized)) -> bool
{
if !shape_overlap(left, right, false).0
{
return false;
}
if !shape_overlap(right, left, false).0
{
return false;
}
return true;
}
pub fn sat_collision(left: &(impl Shape + ?Sized), right: &(impl Shape + ?Sized)) -> (f32, f32)
{
let l_overlap = shape_overlap(left, right, true);
let r_overlap = shape_overlap(right, left, true);
let r_flipped = (true, r_overlap.1, (-r_overlap.2.0, -r_overlap.2.1));
let overlap = if l_overlap.1 < r_flipped.1 { l_overlap } else { r_flipped };
return (overlap.1 * overlap.2.0, overlap.1 * overlap.2.1);
}
pub fn contains_point(shape: &(impl Shape + ?Sized), point: (f32, f32)) -> bool
{
let polygon = polygon::Polygon::from_vertices(point, vec![(0.0, 0.0)]);
return shape_overlap(shape, &polygon, false).0;
}
fn shape_overlap(axes: &(impl Shape + ?Sized), projected: &(impl Shape + ?Sized), normalize: bool) -> (bool, f32, (f32, f32))
{
let mut min_overlap = f32::MAX;
let mut min_axis = (0.0, 0.0);
let num_axes = axes.num_axes();
for i in 0..num_axes
{
let closest = if axes.needs_closest(i) { projected.get_closest(axes.point(i)) } else { (0.0, 0.0) };
let mut axis = axes.get_axis(i, closest);
if normalize
{
let length = f32::sqrt((axis.0 * axis.0) + (axis.1 * axis.1));
if length > f32::EPSILON
{
axis = (axis.0 / length, axis.1 / length);
}
}
let (min_l, max_l) = axes.project(axis, normalize);
let (min_r, max_r) = projected.project(axis, normalize);
if min_l > max_r - f32::EPSILON || min_r > max_l - f32::EPSILON
{
return (false, 0.0, (0.0, 0.0));
}
let overlap = f32::min(max_l - min_r, max_r - min_l);
if overlap < min_overlap
{
min_overlap = overlap;
min_axis = axis;
}
}
let axes_position = axes.position();
let projected_position = projected.position();
let difference = (projected_position.0 - axes_position.0, projected_position.1 - axes_position.1);
if (difference.0 * min_axis.0 + difference.1 * min_axis.1) < -f32::EPSILON
{
min_axis.0 *= -1.0;
min_axis.1 *= -1.0;
}
return (true, min_overlap, min_axis);
}
fn project(position: (f32, f32), axis: (f32, f32), points: &[(f32, f32)]) -> (f32, f32)
{
let mut min = f32::MAX;
let mut max = f32::MIN;
for (x, y) in points
{
let position = (position.0 + *x, position.1 + *y);
let projection = (position.0 * axis.0) + (position.1 * axis.1);
min = f32::min(min, projection);
max = f32::max(max, projection);
}
return (min, max);
}
fn closest(position: (f32, f32), target: (f32, f32), points: &[(f32, f32)]) -> (f32, f32)
{
let mut point = (0.0, 0.0);
let mut min = f32::MAX;
for (x, y) in points
{
let position = (position.0 + *x, position.1 + *y);
let dist_square = (position.0 - target.0) * (position.0 - target.0) + (position.1 - target.1) * (position.1 - target.1);
if dist_square < min
{
point = position;
min = dist_square;
}
}
return point;
}
#[allow(dead_code)]
fn float_equal(left: f32, right: f32) -> bool
{
return (left - right).abs() < 0.00001;
}
#[cfg(test)]
mod sat_tests
{
use super::*;
use super::prelude::*;
#[test]
fn test_sat_overlap()
{
let square = Polygon::from_vertices((0.0, 0.0), vec![(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)]);
let triangle = Polygon::from_vertices((2.0, 2.0), vec![(-1.0, 1.0), (0.0, -1.0), (1.0, 1.0)]);
let pentagon = Polygon::from_vertices((-3.0, 0.0), vec![(2.0, 0.0), (4.0, 1.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]);
let triangle2 = Polygon::from_vertices((4.0, 3.0), vec![(-2.0, 1.0), (-1.0, -2.0), (2.0, 0.0)]);
assert!(sat_overlap(&pentagon, &pentagon));
assert!(sat_overlap(&square, &triangle));
assert!(sat_overlap(&triangle, &square));
assert!(sat_overlap(&square, &pentagon));
assert!(sat_overlap(&pentagon, &square));
assert!(sat_overlap(&square, &triangle2));
assert!(sat_overlap(&triangle2, &square));
assert!(sat_overlap(&triangle, &triangle2));
assert!(sat_overlap(&triangle, &triangle2));
let circle1 = Circle::new((-1.0, -1.0), 2.0);
let circle2 = Circle::new((-2.0, -1.0), 1.1);
assert!(sat_overlap(&circle1, &square));
assert!(sat_overlap(&circle2, &pentagon));
assert!(sat_overlap(&pentagon, &circle1));
assert!(sat_overlap(&circle1, &circle2));
}
#[test]
fn test_sat_no_overlap()
{
let triangle = Polygon::from_vertices((2.0, 2.0), vec![(-1.0, 1.0), (0.0, -1.0), (1.0, 1.0)]);
let pentagon = Polygon::from_vertices((-3.0, 0.0), vec![(2.0, 0.0), (4.0, 1.0), (2.0, 2.0), (0.0, 2.0), (0.0, 0.0)]);
let triangle2 = Polygon::from_vertices((4.0, 3.0), vec![(-2.0, 1.0), (-1.0, -2.0), (2.0, 0.0)]);
assert!(!sat_overlap(&pentagon, &triangle));
assert!(!sat_overlap(&triangle, &pentagon));
assert!(!sat_overlap(&pentagon, &triangle2));
assert!(!sat_overlap(&triangle2, &pentagon));
let circle1 = Circle::new((-1.0, -1.0), 2.0);
let circle2 = Circle::new((-2.0, -1.0), 1.1);
let circle3 = Circle::new((2.0, 5.0), 1.0);
assert!(!sat_overlap(&triangle, &circle1));
assert!(!sat_overlap(&circle2, &triangle2));
assert!(!sat_overlap(&circle1, &circle3));
assert!(!sat_overlap(&circle3, &circle2));
}
#[test]
fn test_sat_collision()
{
let square = Polygon::from_vertices((0.0, 0.0), vec![(0.0, 0.0), (4.0, 0.0), (4.0, 4.0), (0.0, 4.0)]);
let rectangle = Polygon::from_vertices((1.0, -2.0), vec![(0.0, 0.0), (1.0, 0.0), (1.0, 2.1), (0.0, 2.1)]);
let triangle = Polygon::from_vertices((0.0, 0.0), vec![(0.5, 0.6), (0.0, -1.0), (-0.5, 0.6)]);
let triangle2 = Polygon::from_vertices((-0.4, -0.4), vec![(0.0, 0.0), (1.0, 0.0), (0.0, 1.0)]);
let resolution = sat_collision(&square, &rectangle);
assert!(float_equal(resolution.0, 0.0));
assert!(float_equal(resolution.1, -0.1));
let resolution1 = sat_collision(&rectangle, &square);
assert!(float_equal(resolution1.0, 0.0));
assert!(float_equal(resolution1.1, 0.1));
let resolution2 = sat_collision(&square, &triangle);
assert!(float_equal(resolution2.0, -0.5));
assert!(float_equal(resolution2.1, 0.0));
let resolution3 = sat_collision(&square, &triangle2);
assert!(float_equal((resolution3.0 * -1.0) + (resolution3.1), 0.0));
let circle = Circle::new((2.0, -0.5), 1.0);
let circle2 = Circle::new((0.0, -0.5), 1.1);
let resolution4 = sat_collision(&circle, &square);
assert!(float_equal(resolution4.0, 0.0));
assert!(float_equal(resolution4.1, 0.5));
let resolution5 = sat_collision(&circle, &triangle2);
assert!(float_equal(resolution5.0, 0.0));
assert!(float_equal(resolution5.1, 0.0));
let resolution6 = sat_collision(&circle2, &circle);
assert!(float_equal(resolution6.0, 0.1));
assert!(float_equal(resolution6.1, 0.0));
}
#[test]
fn test_capsule_collision()
{
let capsule = Capsule::new((0.0, 0.0), (0.0, 2.0), 2.0);
let triangle = Polygon::from_vertices((0.0, 5.0), vec![(0.0, -2.0), (-1.0, 2.0), (1.0, 2.0)]);
let rectangle = AABB::new((-4.0, 4.0), 2.5, 0.5);
let circle1 = Circle::new((2.0, -2.5), 1.0);
let circle2 = Circle::new((3.0, 0.0), 1.5);
assert!(sat_overlap(&capsule, &circle1));
assert!(!sat_overlap(&rectangle, &capsule));
let resolution1 = sat_collision(&circle2, &capsule);
let resolution2 = sat_collision(&capsule, &triangle);
assert!(float_equal(resolution1.0, -0.5));
assert!(float_equal(resolution1.1, 0.0));
assert!(float_equal(resolution2.0, 0.0));
assert!(float_equal(resolution2.1, 1.0));
}
#[test]
fn test_parallelogram_collision()
{
let gram = Parallelogram::new((0.0, -0.5), (2.0, 1.0), (-1.0, -1.0));
let triangle = Polygon::from_vertices((0.0, 5.0), vec![(0.0, -2.0), (-1.0, 2.0), (1.0, 2.0)]);
let rectangle = AABB::new((-1.0, 0.0), 4.0, 2.0);
let circle = Circle::new((2.0, -2.5), 1.0);
assert!(!sat_overlap(&gram, &triangle));
assert!(!sat_overlap(&gram, &circle));
assert!(sat_overlap(&gram, &rectangle));
let resolution = sat_collision(&rectangle, &gram);
println!("{:?}", resolution);
assert!(float_equal(resolution.0, 0.0));
assert!(float_equal(resolution.1, -0.5));
}
#[test]
fn test_contains_point()
{
let capsule = Capsule::new((0.0, 0.0), (0.0, 2.0), 2.0);
let triangle = Polygon::from_vertices((0.0, 5.0), vec![(0.0, -2.0), (-1.0, 2.0), (1.0, 2.0)]);
let rectangle = AABB::new((-4.0, 4.0), 2.5, 0.5);
let circle = Circle::new((2.0, -2.5), 1.0);
assert!(contains_point(&capsule, (1.0, 1.0)));
assert!(contains_point(&triangle, (0.0, 5.0)));
assert!(contains_point(&rectangle, (-2.0, 4.1)));
assert!(contains_point(&circle, (2.5, -3.0)));
assert!(!contains_point(&capsule, (2.0, 4.0)));
assert!(!contains_point(&triangle, (0.0, 0.0)));
assert!(!contains_point(&rectangle, (-4.0, 3.9)));
assert!(!contains_point(&circle, (1.5, -3.5)));
}
#[test]
fn test_rotate()
{
let mut triangle = Polygon::from_vertices((0.0, 0.0), vec![(0.0, 0.0), (2.0, 0.0), (0.0, 1.0)]);
let mut capsule = Capsule::new((2.0, 1.0), (2.0, 0.0), 4.0);
let mut gram = Parallelogram::new((3.0, 4.0), (2.0, 1.0), (-1.0, 1.0));
capsule.rotate(std::f32::consts::FRAC_PI_4);
assert!(float_equal(capsule.arm().0, 2.0 / f32::sqrt(2.0)));
assert!(float_equal(capsule.arm().1, 2.0 / f32::sqrt(2.0)));
assert!(float_equal(capsule.perp().0, -4.0 / f32::sqrt(2.0)));
assert!(float_equal(capsule.perp().1, 4.0 / f32::sqrt(2.0)));
let sin = f32::sin(std::f32::consts::PI);
let cos = f32::cos(std::f32::consts::PI);
triangle.rotate_sincos(sin, cos);
assert!(float_equal(triangle.vertices[1].0, -2.0));
assert!(float_equal(triangle.vertices[1].1, 0.0));
assert!(float_equal(triangle.vertices[2].0, 0.0));
assert!(float_equal(triangle.vertices[2].1, -1.0));
gram.rotate_sincos(sin, cos);
assert!(float_equal(gram.u.0, -2.0));
assert!(float_equal(gram.u.1, -1.0));
assert!(float_equal(gram.v.0, 1.0));
assert!(float_equal(gram.v.1, -1.0));
}
}
pub mod prelude
{
pub use crate::{sat_overlap, sat_collision, contains_point};
pub use crate::Shape;
pub use crate::Rotate;
pub use crate::polygon::Polygon;
pub use crate::circle::Circle;
pub use crate::aabb::AABB;
pub use crate::capsule::Capsule;
pub use crate::parallelogram::Parallelogram;
pub use crate::line::intersects_line;
pub use crate::line::intersects_ray;
pub use crate::line::intersects_segment;
}