use float_cmp::approx_eq;
use crate::{boundingbox::BoundingBox, geom, point::Point};
use std::{
fmt::{self, Display},
iter::zip,
};
#[allow(clippy::len_without_is_empty)] #[derive(Debug, Clone)]
pub struct Polygon {
pub points: Vec<Point>,
pub bounds: BoundingBox,
}
impl Polygon {
pub fn new(points: Vec<Point>) -> Self {
if points.len() < 3 {
panic!(
"Trying to create a polygon with {} points. You need at least 3",
points.len()
)
}
let bounds = BoundingBox::from_points(&points);
Polygon { points, bounds }
}
pub fn len(&self) -> usize {
self.points.len()
}
pub fn get_side(&self, i: usize) -> (Point, Point) {
let p1 = self.points[i];
let p2: Point = if i + 1 >= self.points.len() {
self.points[0]
} else {
self.points[i + 1]
};
(p1, p2)
}
pub fn sides(&self) -> Vec<(Point, Point)> {
let mut result = Vec::new();
for i in 0..self.len() {
result.push(self.get_side(i));
}
result
}
pub fn is_self_intersecting(&self) -> bool {
for i in 0..self.points.len() {
let (p1, p2) = self.get_side(i);
for j in i + 1..self.points.len() {
let (p3, p4) = self.get_side(j);
if p1 == p3 || p1 == p4 || p2 == p3 || p2 == p4 {
continue;
}
if geom::lines_intersect(p1, p2, p3, p4) {
return true;
}
}
}
false
}
pub fn area(&self) -> f64 {
if self.is_self_intersecting() {
panic!("Can not calculate the area of a self intersecting polygon")
}
let sides = self.sides();
let triangle_sum = sides
.iter()
.map(|s| geom::area_of_triangle(Point::zero(), s.0, s.1))
.sum();
triangle_sum
}
pub fn center(&self) -> Point {
let mut x = 0.0;
let mut y = 0.0;
for p in self.points.iter() {
x += p.x;
y += p.y;
}
let len = self.len() as f64;
Point::new(x / len, y / len)
}
pub fn contains(&self, p: Point) -> bool {
if !self.bounds.contains(p) {
return false;
}
let mut total = 0.0;
for i in 0..self.points.len() {
let (p1, p2) = self.get_side(i);
let angle_a = p.angle_to(&p2);
let angle_b = p.angle_to(&p1);
let result = if angle_a > angle_b {
-((360.0_f64.to_radians() - angle_a) + angle_b)
} else {
angle_a - angle_b
};
total += result;
}
approx_eq!(f64, total.abs(), 360.0_f64.to_radians(), ulps = 2)
}
pub fn intersects(&self, other: &Polygon) -> bool {
if !self.bounds.intersects(&other.bounds) {
return false;
}
let self_sides = self.sides();
let other_sides = other.sides();
for self_side in self_sides.iter() {
for other_side in other_sides.iter() {
if geom::lines_intersect(self_side.0, self_side.1, other_side.0, other_side.1) {
return true;
}
}
}
if self.contains(other.points[0]) {
return true;
}
if other.contains(self.points[0]) {
return true;
}
false
}
pub fn translate(&self, p: Point) -> Polygon {
let points = self.points.iter().map(|point| point.translate(p)).collect();
Polygon::new(points)
}
pub fn rotate_around_center(&self, angle: f64) -> Polygon {
let center = self.center();
let center_inv = center.invert();
let new_points = self
.points
.iter()
.map(|p| p.translate(center_inv).rotate(angle).translate(center))
.collect();
Polygon::new(new_points)
}
pub fn rotate_around_origin(&self, angle: f64) -> Polygon {
let new_points = self.points.iter().map(|p| p.rotate(angle)).collect();
Polygon::new(new_points)
}
}
impl PartialEq for Polygon {
fn eq(&self, other: &Self) -> bool {
if other.len() != self.len() {
return false;
}
for (a, b) in zip(self.points.iter(), other.points.iter()) {
if a != b {
return false;
}
}
true
}
}
impl Display for Polygon {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(formatter, "Poly(")?;
let mut first = true;
for p in self.points.iter() {
if !first {
write!(formatter, ", ")?;
} else {
first = false;
}
p.fmt(formatter)?;
}
write!(formatter, ")")
}
}
#[cfg(test)]
mod tests {
use float_cmp::approx_eq;
use crate::{point::Point, tests::assert_f64};
use super::Polygon;
macro_rules! contains_tests {
($($name:ident: $poly_points:expr, $test_point:expr, $expected:expr,)*) => {
$(
#[test]
fn $name() {
let poly = Polygon::new($poly_points);
assert_eq!(poly.contains($test_point), $expected);
}
)*
};
}
contains_tests!(
not_in:
vec![
Point::zero(),
Point::new(0.0, 2.0),
Point::new(2.0, 2.0),
Point::new(2.0, 0.0)
],
Point::new(5.0, 1.0),
false,
inside:
vec![
Point::zero(),
Point::new(0.0, 2.0),
Point::new(2.0, 2.0),
Point::new(2.0, 0.0)
],
Point::new(1.0, 1.0),
true,
);
#[test]
fn is_self_intersecting() {
let poly = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(0.0, 1.0),
Point::new(1.0, 0.0),
Point::new(1.0, 1.0),
]);
assert!(poly.is_self_intersecting())
}
#[test]
fn is_not_self_intersecting() {
let poly = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
Point::new(1.0, 0.0),
]);
assert!(!poly.is_self_intersecting())
}
#[test]
fn sides_square() {
let poly = Polygon::new(vec![
Point::new(1.0, 0.0),
Point::new(0.0, 0.0),
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
]);
let result = poly.sides();
let expected = vec![
(Point::new(1.0, 0.0), Point::new(0.0, 0.0)),
(Point::new(0.0, 0.0), Point::new(0.0, 1.0)),
(Point::new(0.0, 1.0), Point::new(1.0, 1.0)),
(Point::new(1.0, 1.0), Point::new(1.0, 0.0)),
];
assert_eq!(result, expected);
}
#[test]
fn check_area() {
let poly = Polygon::new(vec![
Point::new(1.0, 0.0),
Point::new(0.0, 0.0),
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
]);
let result = poly.area();
assert_f64!(result, 1.0);
}
#[test]
fn rotate_square() {
let poly = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
Point::new(1.0, 0.0),
]);
let result = poly.rotate_around_center(90.0_f64.to_radians());
println!("result: {}", result);
assert_f64!(result.area(), poly.area());
let expected = Polygon::new(vec![
Point::new(1.0, 0.0),
Point::new(0.0, 0.0),
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
]);
assert_eq!(result, expected);
}
#[test]
fn rotate_square_around_origin() {
let poly = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
Point::new(1.0, 0.0),
]);
let result = poly.rotate_around_origin(90.0_f64.to_radians());
let expected = Polygon::new(vec![
Point::new(0.0, 0.0),
Point::new(-1.0, 0.0),
Point::new(-1.0, 1.0),
Point::new(0.0, 1.0),
]);
assert_eq!(result, expected);
assert_f64!(result.area(), poly.area());
}
macro_rules! intersection_tests {
($($name:ident: $points_a:expr, $points_b:expr, $expected:expr,)*) => {
$(
#[test]
fn $name() {
let a = Polygon::new($points_a);
let b = Polygon::new($points_b);
assert_eq!(a.intersects(&b), $expected);
}
)*
};
}
intersection_tests!(
non_intersecting:
vec![
Point::new(0.0, 0.0),
Point::new(1.0, 1.0),
Point::new(1.0, 0.0)
],
vec![
Point::new(2.0, 2.0),
Point::new(3.0, 3.0),
Point::new(3.0, 2.0)
],
false,
corner_intersecting:
vec![
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
Point::new(1.0, 0.0)
],
vec![
Point::new(1.0, 1.0),
Point::new(1.0, 2.0),
Point::new(2.0, 1.0)
],
true,
overlapping:
vec![
Point::new(0.0, 1.0),
Point::new(1.0, 1.0),
Point::new(1.0, 0.0),
Point::new(0.0, 0.0)
],
vec![
Point::new(0.5, 1.5),
Point::new(1.5, 1.5),
Point::new(1.5, 0.5),
Point::new(0.5, 0.5)
],
true,
containing:
vec![
Point::new(0.0, 2.0),
Point::new(2.0, 2.0),
Point::new(2.0, 0.0),
Point::new(0.0, 0.0)
],
vec![
Point::new(0.5, 1.5),
Point::new(1.5, 1.5),
Point::new(1.5, 0.5),
Point::new(0.5, 0.5)
],
true,
contained:
vec![
Point::new(0.5, 1.5),
Point::new(1.5, 1.5),
Point::new(1.5, 0.5),
Point::new(0.5, 0.5)
],
vec![
Point::new(0.0, 2.0),
Point::new(2.0, 2.0),
Point::new(2.0, 0.0),
Point::new(0.0, 0.0)
],
true,
);
}