use alloc::vec::Vec;
use core::fmt;
use crate::predicates::{is_ccw, orient2d, ring_area2, segments_properly_cross, Orientation};
use crate::Point;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct VertexId(pub u16);
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct EdgeId(pub u16);
impl EdgeId {
#[inline]
pub const fn start_vertex(self) -> VertexId {
VertexId(self.0)
}
}
impl VertexId {
#[inline]
pub const fn outgoing_edge(self) -> EdgeId {
EdgeId(self.0)
}
}
impl fmt::Display for VertexId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "v{}", self.0)
}
}
impl fmt::Display for EdgeId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "e{}", self.0)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct RingId(pub u16);
#[derive(Clone, Debug, PartialEq, Eq)]
#[non_exhaustive]
pub enum PolygonError {
NoOuterRing,
TooFewVertices {
ring: RingId,
count: usize,
},
RepeatedVertex {
ring: RingId,
index: usize,
point: Point,
},
DegenerateRing {
ring: RingId,
},
Spike {
ring: RingId,
vertex: VertexId,
},
SelfIntersection {
a: EdgeId,
b: EdgeId,
},
HoleOutsideOuter {
ring: RingId,
},
TooManyVertices {
count: usize,
max: usize,
},
CoordinateOutOfRange {
ring: RingId,
point: Point,
},
}
impl fmt::Display for PolygonError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
PolygonError::NoOuterRing => write!(f, "polygon has no outer ring"),
PolygonError::TooFewVertices { ring, count } => write!(
f,
"ring {} has {count} vertices; at least 3 are required",
ring.0
),
PolygonError::RepeatedVertex { ring, index, point } => write!(
f,
"ring {} repeats vertex ({}, {}) at index {index}, giving a zero-length edge",
ring.0, point.x, point.y
),
PolygonError::DegenerateRing { ring } => {
write!(f, "ring {} encloses no area", ring.0)
}
PolygonError::Spike { ring, vertex } => write!(
f,
"ring {} doubles back through 180° at vertex {}, forming a zero-width spike",
ring.0, vertex.0
),
PolygonError::SelfIntersection { a, b } => {
write!(
f,
"edges {} and {} cross; the polygon must be simple",
a.0, b.0
)
}
PolygonError::HoleOutsideOuter { ring } => {
write!(f, "hole {} is not contained in the outer ring", ring.0)
}
PolygonError::TooManyVertices { count, max } => {
write!(f, "polygon has {count} vertices; the maximum is {max}")
}
PolygonError::CoordinateOutOfRange { ring, point } => write!(
f,
"ring {} has vertex ({}, {}), outside the supported range {}..={}",
ring.0,
point.x,
point.y,
Point::MIN_COORD,
Point::MAX_COORD
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for PolygonError {}
#[derive(Clone, Debug, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Polygon {
verts: Vec<Point>,
ring_starts: Vec<u16>,
}
impl Polygon {
pub const MAX_VERTICES: usize = u16::MAX as usize - 1;
pub fn new(outer: &[Point], holes: &[Vec<Point>]) -> Result<Self, PolygonError> {
if outer.is_empty() {
return Err(PolygonError::NoOuterRing);
}
let total: usize = outer.len() + holes.iter().map(|h| h.len()).sum::<usize>();
if total > Self::MAX_VERTICES {
return Err(PolygonError::TooManyVertices {
count: total,
max: Self::MAX_VERTICES,
});
}
let mut verts: Vec<Point> = Vec::with_capacity(total);
let mut ring_starts: Vec<u16> = Vec::with_capacity(holes.len() + 2);
ring_starts.push(0);
push_ring(&mut verts, &mut ring_starts, outer, RingId(0), true)?;
for (i, hole) in holes.iter().enumerate() {
let id = RingId((i + 1) as u16);
push_ring(&mut verts, &mut ring_starts, hole, id, false)?;
}
let poly = Polygon { verts, ring_starts };
poly.check_no_spikes()?;
poly.check_simple()?;
poly.check_holes_inside()?;
Ok(poly)
}
pub fn from_outer(outer: &[Point]) -> Result<Self, PolygonError> {
Polygon::new(outer, &[])
}
#[inline]
pub fn vertex_count(&self) -> usize {
self.verts.len()
}
#[inline]
pub fn ring_count(&self) -> usize {
self.ring_starts.len() - 1
}
#[inline]
pub fn hole_count(&self) -> usize {
self.ring_count() - 1
}
#[inline]
pub fn vertices(&self) -> &[Point] {
&self.verts
}
#[inline]
pub fn vertex(&self, v: VertexId) -> Point {
self.verts[v.0 as usize]
}
#[inline]
pub fn ring(&self, ring: RingId) -> &[Point] {
let lo = self.ring_starts[ring.0 as usize] as usize;
let hi = self.ring_starts[ring.0 as usize + 1] as usize;
&self.verts[lo..hi]
}
pub fn ring_of(&self, v: VertexId) -> RingId {
let i = v.0;
let idx = self
.ring_starts
.partition_point(|&start| start <= i)
.saturating_sub(1);
debug_assert!(idx < self.ring_count());
RingId(idx as u16)
}
pub fn rings(&self) -> impl Iterator<Item = &[Point]> + '_ {
(0..self.ring_count()).map(move |i| self.ring(RingId(i as u16)))
}
pub fn next_vertex(&self, v: VertexId) -> VertexId {
let ring = self.ring_of(v);
let lo = self.ring_starts[ring.0 as usize];
let hi = self.ring_starts[ring.0 as usize + 1];
if v.0 + 1 == hi {
VertexId(lo)
} else {
VertexId(v.0 + 1)
}
}
pub fn prev_vertex(&self, v: VertexId) -> VertexId {
let ring = self.ring_of(v);
let lo = self.ring_starts[ring.0 as usize];
let hi = self.ring_starts[ring.0 as usize + 1];
if v.0 == lo {
VertexId(hi - 1)
} else {
VertexId(v.0 - 1)
}
}
#[inline]
pub fn edge(&self, e: EdgeId) -> (Point, Point) {
let start = e.start_vertex();
(self.vertex(start), self.vertex(self.next_vertex(start)))
}
#[inline]
pub fn edge_count(&self) -> usize {
self.verts.len()
}
pub fn edge_ids(&self) -> impl Iterator<Item = EdgeId> + '_ {
(0..self.edge_count() as u16).map(EdgeId)
}
pub fn vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
(0..self.vertex_count() as u16).map(VertexId)
}
pub fn is_reflex(&self, v: VertexId) -> bool {
let prev = self.vertex(self.prev_vertex(v));
let cur = self.vertex(v);
let next = self.vertex(self.next_vertex(v));
orient2d(prev, cur, next) == Orientation::Clockwise
}
pub fn signed_area2(&self) -> i64 {
self.rings().map(ring_area2).sum()
}
fn check_no_spikes(&self) -> Result<(), PolygonError> {
for v in self.vertex_ids() {
let prev = self.vertex(self.prev_vertex(v));
let cur = self.vertex(v);
let next = self.vertex(self.next_vertex(v));
if orient2d(prev, cur, next) != Orientation::Collinear {
continue;
}
let inc = (cur.x as i64 - prev.x as i64, cur.y as i64 - prev.y as i64);
let out = (next.x as i64 - cur.x as i64, next.y as i64 - cur.y as i64);
if inc.0 * out.0 + inc.1 * out.1 < 0 {
return Err(PolygonError::Spike {
ring: self.ring_of(v),
vertex: v,
});
}
}
Ok(())
}
fn check_simple(&self) -> Result<(), PolygonError> {
let n = self.edge_count();
if n < 2 {
return Ok(());
}
let boxes: Vec<[i16; 4]> = (0..n)
.map(|i| {
let (p, q) = self.edge(EdgeId(i as u16));
[p.x.min(q.x), p.x.max(q.x), p.y.min(q.y), p.y.max(q.y)]
})
.collect();
let mut order: Vec<u16> = (0..n as u16).collect();
order.sort_unstable_by_key(|&e| boxes[e as usize][0]);
let mut active: Vec<u16> = Vec::new();
for &i in &order {
let bi = boxes[i as usize];
active.retain(|&j| boxes[j as usize][1] >= bi[0]);
for &j in &active {
let bj = boxes[j as usize];
if bj[3] < bi[2] || bi[3] < bj[2] {
continue;
}
let (a, b) = if i < j {
(EdgeId(i), EdgeId(j))
} else {
(EdgeId(j), EdgeId(i))
};
let (a1, a2) = self.edge(a);
let (b1, b2) = self.edge(b);
if segments_properly_cross(a1, a2, b1, b2) {
return Err(PolygonError::SelfIntersection { a, b });
}
if !self.edges_are_adjacent(a, b) && self.edges_touch(a, b) {
return Err(PolygonError::SelfIntersection { a, b });
}
}
active.push(i);
}
Ok(())
}
fn edges_are_adjacent(&self, a: EdgeId, b: EdgeId) -> bool {
let a_start = a.start_vertex();
let b_start = b.start_vertex();
self.next_vertex(a_start) == b_start || self.next_vertex(b_start) == a_start
}
fn edges_touch(&self, a: EdgeId, b: EdgeId) -> bool {
use crate::predicates::point_on_segment;
let (a1, a2) = self.edge(a);
let (b1, b2) = self.edge(b);
point_on_segment(a1, b1, b2)
|| point_on_segment(a2, b1, b2)
|| point_on_segment(b1, a1, a2)
|| point_on_segment(b2, a1, a2)
}
fn check_holes_inside(&self) -> Result<(), PolygonError> {
let outer = self.ring(RingId(0));
for h in 1..self.ring_count() {
let ring = RingId(h as u16);
let probe = self.ring(ring)[0];
if !point_in_ring(probe, outer) {
return Err(PolygonError::HoleOutsideOuter { ring });
}
}
Ok(())
}
}
fn push_ring(
verts: &mut Vec<Point>,
ring_starts: &mut Vec<u16>,
ring: &[Point],
id: RingId,
want_ccw: bool,
) -> Result<(), PolygonError> {
let ring = match ring {
[first, mid @ .., last] if first == last && !mid.is_empty() => &ring[..ring.len() - 1],
_ => ring,
};
if ring.len() < 3 {
return Err(PolygonError::TooFewVertices {
ring: id,
count: ring.len(),
});
}
for &p in ring {
if !p.in_range() {
return Err(PolygonError::CoordinateOutOfRange { ring: id, point: p });
}
}
for i in 0..ring.len() {
let next = (i + 1) % ring.len();
if ring[i] == ring[next] {
return Err(PolygonError::RepeatedVertex {
ring: id,
index: next,
point: ring[i],
});
}
}
let area2 = ring_area2(ring);
if area2 == 0 {
return Err(PolygonError::DegenerateRing { ring: id });
}
let start = verts.len();
verts.extend_from_slice(ring);
if is_ccw(ring) != want_ccw {
verts[start..].reverse();
}
ring_starts.push(verts.len() as u16);
Ok(())
}
fn point_in_ring(p: Point, ring: &[Point]) -> bool {
let n = ring.len();
let mut inside = false;
for i in 0..n {
let a = ring[i];
let b = ring[(i + 1) % n];
if crate::predicates::point_on_segment(p, a, b) {
return true;
}
let crosses = (a.y > p.y) != (b.y > p.y);
if crosses {
let side = orient2d(a, b, p);
let upward = b.y > a.y;
let right_of_p = if upward {
side == Orientation::Clockwise
} else {
side == Orientation::CounterClockwise
};
if right_of_p {
inside = !inside;
}
}
}
inside
}
#[cfg(test)]
mod tests {
use super::*;
use alloc::vec;
fn square(size: i16) -> Vec<Point> {
vec![
Point::new(0, 0),
Point::new(size, 0),
Point::new(size, size),
Point::new(0, size),
]
}
#[test]
fn builds_a_square() {
let p = Polygon::from_outer(&square(10)).unwrap();
assert_eq!(p.vertex_count(), 4);
assert_eq!(p.edge_count(), 4);
assert_eq!(p.ring_count(), 1);
assert_eq!(p.hole_count(), 0);
assert_eq!(p.signed_area2(), 200);
}
#[test]
fn normalises_outer_ring_to_ccw() {
let mut cw = square(10);
cw.reverse();
let p = Polygon::from_outer(&cw).unwrap();
assert!(is_ccw(p.ring(RingId(0))), "outer ring must end up CCW");
assert!(p.signed_area2() > 0);
}
#[test]
fn normalises_holes_to_cw() {
let hole_ccw = vec![
Point::new(10, 10),
Point::new(20, 10),
Point::new(20, 20),
Point::new(10, 20),
];
let p = Polygon::new(&square(30), &[hole_ccw]).unwrap();
assert!(!is_ccw(p.ring(RingId(1))), "hole must end up CW");
assert_eq!(p.signed_area2(), 1600);
}
#[test]
fn accepts_explicitly_closed_rings() {
let mut closed = square(10);
closed.push(closed[0]);
let p = Polygon::from_outer(&closed).unwrap();
assert_eq!(p.vertex_count(), 4, "the repeated closing point is dropped");
}
#[test]
fn edge_and_vertex_ids_correspond() {
let p = Polygon::from_outer(&square(10)).unwrap();
for v in p.vertex_ids() {
assert_eq!(v.outgoing_edge().start_vertex(), v);
let (start, _) = p.edge(v.outgoing_edge());
assert_eq!(start, p.vertex(v));
}
}
#[test]
fn edges_wrap_within_their_ring() {
let p = Polygon::new(
&square(30),
&[vec![
Point::new(10, 10),
Point::new(10, 20),
Point::new(20, 20),
Point::new(20, 10),
]],
)
.unwrap();
assert_eq!(p.next_vertex(VertexId(3)), VertexId(0));
assert_eq!(p.next_vertex(VertexId(7)), VertexId(4));
assert_eq!(p.prev_vertex(VertexId(4)), VertexId(7));
}
#[test]
fn ring_of_maps_vertices_correctly() {
let p = Polygon::new(
&square(30),
&[vec![
Point::new(10, 10),
Point::new(10, 20),
Point::new(20, 20),
Point::new(20, 10),
]],
)
.unwrap();
for v in 0..4 {
assert_eq!(p.ring_of(VertexId(v)), RingId(0));
}
for v in 4..8 {
assert_eq!(p.ring_of(VertexId(v)), RingId(1));
}
}
#[test]
fn rejects_too_few_vertices() {
let e = Polygon::from_outer(&[Point::new(0, 0), Point::new(1, 1)]).unwrap_err();
assert!(matches!(e, PolygonError::TooFewVertices { count: 2, .. }));
}
#[test]
fn rejects_coordinates_outside_the_cap() {
let e = Polygon::from_outer(&[Point::new(0, 0), Point::new(16384, 0), Point::new(0, 100)])
.unwrap_err();
assert!(
matches!(e, PolygonError::CoordinateOutOfRange { point, .. } if point.x == 16384),
"got {e:?}"
);
assert!(matches!(
Polygon::from_outer(&[Point::new(0, 0), Point::new(100, 0), Point::new(0, -16385)])
.unwrap_err(),
PolygonError::CoordinateOutOfRange { .. }
));
assert!(Polygon::from_outer(&[
Point::new(Point::MIN_COORD, Point::MIN_COORD),
Point::new(Point::MAX_COORD, Point::MIN_COORD),
Point::new(Point::MAX_COORD, Point::MAX_COORD),
])
.is_ok());
}
#[test]
fn rejects_empty_outer_ring() {
assert_eq!(
Polygon::from_outer(&[]).unwrap_err(),
PolygonError::NoOuterRing
);
}
#[test]
fn rejects_repeated_vertices() {
let e = Polygon::from_outer(&[
Point::new(0, 0),
Point::new(10, 0),
Point::new(10, 0),
Point::new(10, 10),
])
.unwrap_err();
assert!(matches!(e, PolygonError::RepeatedVertex { .. }));
}
#[test]
fn rejects_collinear_ring() {
let e = Polygon::from_outer(&[Point::new(0, 0), Point::new(5, 0), Point::new(9, 0)])
.unwrap_err();
assert!(matches!(e, PolygonError::DegenerateRing { .. }));
}
fn check_simple_all_pairs(p: &Polygon) -> Option<(EdgeId, EdgeId)> {
let n = p.edge_count();
for i in 0..n {
let a = EdgeId(i as u16);
let (a1, a2) = p.edge(a);
for j in (i + 1)..n {
let b = EdgeId(j as u16);
let (b1, b2) = p.edge(b);
if segments_properly_cross(a1, a2, b1, b2) {
return Some((a, b));
}
if !p.edges_are_adjacent(a, b) && p.edges_touch(a, b) {
return Some((a, b));
}
}
}
None
}
#[test]
fn sweep_agrees_with_all_pairs_on_random_rings() {
let mut rng = 0x9E37_79B9_7F4A_7C15u64;
let mut next = || {
rng ^= rng << 13;
rng ^= rng >> 7;
rng ^= rng << 17;
rng
};
let mut crossing = 0;
let mut simple = 0;
for _ in 0..4000 {
let n = 3 + (next() % 8) as usize;
let verts: Vec<Point> = (0..n)
.map(|_| Point::new((next() % 7) as i16, (next() % 7) as i16))
.collect();
let poly = Polygon {
ring_starts: vec![0, verts.len() as u16],
verts,
};
let want = check_simple_all_pairs(&poly);
let got = poly.check_simple();
assert_eq!(
want.is_some(),
got.is_err(),
"sweep and all-pairs disagree on {:?}: all-pairs {want:?}, sweep {got:?}",
poly.verts
);
if want.is_some() {
crossing += 1;
} else {
simple += 1;
}
}
assert!(crossing > 100, "only {crossing} crossing cases generated");
assert!(simple > 100, "only {simple} simple cases generated");
}
#[test]
fn rejects_self_intersecting_ring() {
let e = Polygon::from_outer(&[
Point::new(0, 0),
Point::new(10, 10),
Point::new(10, 0),
Point::new(0, 4),
])
.unwrap_err();
assert!(
matches!(e, PolygonError::SelfIntersection { .. }),
"got {e:?}"
);
}
#[test]
fn rejects_symmetric_bowtie() {
let e = Polygon::from_outer(&[
Point::new(0, 0),
Point::new(10, 10),
Point::new(10, 0),
Point::new(0, 10),
])
.unwrap_err();
assert_eq!(e, PolygonError::DegenerateRing { ring: RingId(0) });
}
#[test]
fn rejects_zero_width_spike() {
let e = Polygon::from_outer(&[
Point::new(0, 0),
Point::new(10, 0),
Point::new(10, 5),
Point::new(20, 5),
Point::new(10, 5),
Point::new(0, 10),
])
.unwrap_err();
assert!(
matches!(
e,
PolygonError::Spike { .. } | PolygonError::SelfIntersection { .. }
),
"got {e:?}"
);
}
#[test]
fn accepts_straight_through_vertices() {
let p = Polygon::from_outer(&[
Point::new(0, 0),
Point::new(5, 0), Point::new(10, 0),
Point::new(10, 10),
Point::new(0, 10),
])
.unwrap();
assert_eq!(p.vertex_count(), 5);
}
#[test]
fn rejects_hole_outside_outer() {
let far_hole = vec![
Point::new(100, 100),
Point::new(110, 100),
Point::new(110, 110),
Point::new(100, 110),
];
let e = Polygon::new(&square(30), &[far_hole]).unwrap_err();
assert!(matches!(e, PolygonError::HoleOutsideOuter { .. }));
}
#[test]
fn rejects_overlapping_holes() {
let a = vec![
Point::new(5, 5),
Point::new(15, 5),
Point::new(15, 15),
Point::new(5, 15),
];
let b = vec![
Point::new(10, 10),
Point::new(20, 10),
Point::new(20, 20),
Point::new(10, 20),
];
assert!(Polygon::new(&square(30), &[a, b]).is_err());
}
#[test]
fn rejects_hole_touching_outer_ring() {
let touching = vec![
Point::new(0, 10),
Point::new(10, 10),
Point::new(10, 20),
Point::new(0, 20),
];
assert!(Polygon::new(&square(30), &[touching]).is_err());
}
#[test]
fn detects_reflex_vertices() {
let l = Polygon::from_outer(&[
Point::new(0, 0),
Point::new(20, 0),
Point::new(20, 10),
Point::new(10, 10), Point::new(10, 20),
Point::new(0, 20),
])
.unwrap();
let reflex: Vec<_> = l.vertex_ids().filter(|&v| l.is_reflex(v)).collect();
assert_eq!(reflex, vec![VertexId(3)]);
}
#[test]
fn convex_polygons_have_no_reflex_vertices() {
let p = Polygon::from_outer(&square(10)).unwrap();
assert!(p.vertex_ids().all(|v| !p.is_reflex(v)));
}
#[test]
fn hole_vertices_are_reflex_from_the_interiors_view() {
let p = Polygon::new(
&square(30),
&[vec![
Point::new(10, 10),
Point::new(20, 10),
Point::new(20, 20),
Point::new(10, 20),
]],
)
.unwrap();
for v in 4..8 {
assert!(p.is_reflex(VertexId(v)), "hole vertex {v} should be reflex");
}
}
#[test]
fn point_in_ring_basics() {
let sq = square(10);
assert!(point_in_ring(Point::new(5, 5), &sq));
assert!(!point_in_ring(Point::new(15, 5), &sq));
assert!(!point_in_ring(Point::new(-1, 5), &sq));
assert!(point_in_ring(Point::new(0, 5), &sq));
assert!(point_in_ring(Point::new(0, 0), &sq));
}
#[test]
fn point_in_ring_handles_rays_through_vertices() {
let diamond = vec![
Point::new(10, 0),
Point::new(20, 10),
Point::new(10, 20),
Point::new(0, 10),
];
assert!(!point_in_ring(Point::new(-5, 0), &diamond));
assert!(point_in_ring(Point::new(10, 10), &diamond));
assert!(!point_in_ring(Point::new(10, 25), &diamond));
}
#[test]
fn display_for_errors_names_the_ring() {
let e = PolygonError::HoleOutsideOuter { ring: RingId(2) };
assert!(alloc::format!("{e}").contains('2'));
}
}