use crate::{CoordinateType, LineString, Point, Rect};
use num_traits::{Float, Signed};
#[derive(PartialEq, Clone, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Polygon<T>
where
T: CoordinateType,
{
exterior: LineString<T>,
interiors: Vec<LineString<T>>,
}
impl<T> Polygon<T>
where
T: CoordinateType,
{
pub fn new(mut exterior: LineString<T>, mut interiors: Vec<LineString<T>>) -> Polygon<T> {
exterior.close();
for interior in &mut interiors {
interior.close();
}
Polygon {
exterior,
interiors,
}
}
pub fn into_inner(self) -> (LineString<T>, Vec<LineString<T>>) {
(self.exterior, self.interiors)
}
pub fn exterior(&self) -> &LineString<T> {
&self.exterior
}
pub fn exterior_mut<F>(&mut self, mut f: F)
where
F: FnMut(&mut LineString<T>),
{
f(&mut self.exterior);
self.exterior.close();
}
pub fn interiors(&self) -> &[LineString<T>] {
&self.interiors
}
pub fn interiors_mut<F>(&mut self, mut f: F)
where
F: FnMut(&mut [LineString<T>]),
{
f(&mut self.interiors);
for interior in &mut self.interiors {
interior.close();
}
}
fn previous_vertex(&self, current_vertex: usize) -> usize
where
T: Float,
{
(current_vertex + (self.exterior.0.len() - 1) - 1) % (self.exterior.0.len() - 1)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum ListSign {
Empty,
Positive,
Negative,
Mixed,
}
impl<T> Polygon<T>
where
T: Float + Signed,
{
pub fn is_convex(&self) -> bool {
let convex = self
.exterior
.0
.iter()
.enumerate()
.map(|(idx, _)| {
let prev_1 = self.previous_vertex(idx);
let prev_2 = self.previous_vertex(prev_1);
Point(self.exterior.0[prev_2])
.cross_prod(Point(self.exterior.0[prev_1]), Point(self.exterior.0[idx]))
})
.fold(ListSign::Empty, |acc, n| match (acc, n.is_positive()) {
(ListSign::Empty, true) | (ListSign::Positive, true) => ListSign::Positive,
(ListSign::Empty, false) | (ListSign::Negative, false) => ListSign::Negative,
_ => ListSign::Mixed,
});
convex != ListSign::Mixed
}
}
impl<T: CoordinateType> From<Rect<T>> for Polygon<T> {
fn from(r: Rect<T>) -> Polygon<T> {
Polygon::new(
vec![
(r.min.x, r.min.y),
(r.max.x, r.min.y),
(r.max.x, r.max.y),
(r.min.x, r.max.y),
(r.min.x, r.min.y),
]
.into(),
Vec::new(),
)
}
}