straight_skeleton/polygon.rs
1//! Input polygons, their identifiers, and validation.
2
3use alloc::vec::Vec;
4use core::fmt;
5
6use crate::predicates::{is_ccw, orient2d, ring_area2, segments_properly_cross, Orientation};
7use crate::Point;
8
9/// Identifies an input vertex of a [`Polygon`].
10///
11/// Vertices are numbered across the whole polygon, outer ring first, then each
12/// hole in order. See [`Polygon`] for the numbering guarantee.
13#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct VertexId(pub u16);
16
17/// Identifies an input edge of a [`Polygon`].
18///
19/// # The `EdgeId` / `VertexId` correspondence
20///
21/// Edge `i` is the edge that **starts** at vertex `i` and ends at the next
22/// vertex of the same ring (wrapping at the ring's end). So `EdgeId(i)` and
23/// `VertexId(i)` always share a number, and converting between an edge and its
24/// start vertex is free. This is the whole reason the crate stores rings
25/// flattened.
26#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct EdgeId(pub u16);
29
30impl EdgeId {
31 /// The vertex this edge starts at.
32 #[inline]
33 pub const fn start_vertex(self) -> VertexId {
34 VertexId(self.0)
35 }
36}
37
38impl VertexId {
39 /// The edge that starts at this vertex.
40 #[inline]
41 pub const fn outgoing_edge(self) -> EdgeId {
42 EdgeId(self.0)
43 }
44}
45
46impl fmt::Display for VertexId {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 write!(f, "v{}", self.0)
49 }
50}
51
52impl fmt::Display for EdgeId {
53 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
54 write!(f, "e{}", self.0)
55 }
56}
57
58/// Identifies a ring of a [`Polygon`]. Ring 0 is always the outer boundary.
59#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
60#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
61pub struct RingId(pub u16);
62
63/// Why a [`Polygon`] could not be built.
64///
65/// Every variant names the ring (and where meaningful the vertex or edge)
66/// responsible, so the caller can point at the offending input rather than
67/// guessing.
68#[derive(Clone, Debug, PartialEq, Eq)]
69#[non_exhaustive]
70pub enum PolygonError {
71 /// A polygon needs an outer ring; none was supplied.
72 NoOuterRing,
73 /// A ring had fewer than three distinct vertices.
74 TooFewVertices {
75 /// The offending ring.
76 ring: RingId,
77 /// How many vertices it had.
78 count: usize,
79 },
80 /// A ring repeated a vertex back to back, giving a zero-length edge.
81 RepeatedVertex {
82 /// The offending ring.
83 ring: RingId,
84 /// Index of the repeat within the ring.
85 index: usize,
86 /// The duplicated point.
87 point: Point,
88 },
89 /// A ring encloses no area (every vertex is collinear).
90 DegenerateRing {
91 /// The offending ring.
92 ring: RingId,
93 },
94 /// A ring doubles back on itself through 180°, forming a zero-width spike.
95 ///
96 /// The wavefront vertex at such a corner would have to move infinitely
97 /// fast, so the skeleton is undefined there. Nudge the spike tip sideways
98 /// by one unit, or drop it.
99 Spike {
100 /// The offending ring.
101 ring: RingId,
102 /// The spike tip.
103 vertex: VertexId,
104 },
105 /// Two edges of the polygon cross. Simple polygons only.
106 SelfIntersection {
107 /// One of the crossing edges.
108 a: EdgeId,
109 /// The other crossing edge.
110 b: EdgeId,
111 },
112 /// A hole is not contained in the outer ring.
113 HoleOutsideOuter {
114 /// The offending hole.
115 ring: RingId,
116 },
117 /// The polygon has more vertices than [`EdgeId`] can number.
118 TooManyVertices {
119 /// How many vertices were supplied.
120 count: usize,
121 /// The most that can be numbered.
122 max: usize,
123 },
124 /// A vertex lies outside the crate's coordinate range.
125 ///
126 /// Coordinates are capped at [`Point::MIN_COORD`]`..=`[`Point::MAX_COORD`],
127 /// one bit narrower than `i16`. That bit is what makes every predicate
128 /// exact in `i32` and lets the whole algorithm run without `f64` — see
129 /// [`Point`] and [`crate::predicates`]. Scale the input down to fit.
130 CoordinateOutOfRange {
131 /// The ring it is in.
132 ring: RingId,
133 /// The offending point.
134 point: Point,
135 },
136}
137
138impl fmt::Display for PolygonError {
139 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
140 match self {
141 PolygonError::NoOuterRing => write!(f, "polygon has no outer ring"),
142 PolygonError::TooFewVertices { ring, count } => write!(
143 f,
144 "ring {} has {count} vertices; at least 3 are required",
145 ring.0
146 ),
147 PolygonError::RepeatedVertex { ring, index, point } => write!(
148 f,
149 "ring {} repeats vertex ({}, {}) at index {index}, giving a zero-length edge",
150 ring.0, point.x, point.y
151 ),
152 PolygonError::DegenerateRing { ring } => {
153 write!(f, "ring {} encloses no area", ring.0)
154 }
155 PolygonError::Spike { ring, vertex } => write!(
156 f,
157 "ring {} doubles back through 180° at vertex {}, forming a zero-width spike",
158 ring.0, vertex.0
159 ),
160 PolygonError::SelfIntersection { a, b } => {
161 write!(
162 f,
163 "edges {} and {} cross; the polygon must be simple",
164 a.0, b.0
165 )
166 }
167 PolygonError::HoleOutsideOuter { ring } => {
168 write!(f, "hole {} is not contained in the outer ring", ring.0)
169 }
170 PolygonError::TooManyVertices { count, max } => {
171 write!(f, "polygon has {count} vertices; the maximum is {max}")
172 }
173 PolygonError::CoordinateOutOfRange { ring, point } => write!(
174 f,
175 "ring {} has vertex ({}, {}), outside the supported range {}..={}",
176 ring.0,
177 point.x,
178 point.y,
179 Point::MIN_COORD,
180 Point::MAX_COORD
181 ),
182 }
183 }
184}
185
186#[cfg(feature = "std")]
187impl std::error::Error for PolygonError {}
188
189/// A simple polygon, optionally with holes, on the `i16` lattice.
190///
191/// # Invariants
192///
193/// A `Polygon` can only be constructed through [`Polygon::new`] or
194/// [`Polygon::from_outer`], which enforce that:
195///
196/// - Ring 0 is the outer boundary; rings `1..` are holes.
197/// - The outer ring winds **counter-clockwise** and holes wind **clockwise**,
198/// so the polygon's interior is always on the *left* of every directed edge.
199/// Rings supplied the other way round are reversed automatically.
200/// - Every coordinate is within [`Point::MIN_COORD`]`..=`[`Point::MAX_COORD`].
201/// - Every ring has at least 3 vertices, no repeated consecutive vertices, and
202/// encloses a non-zero area.
203/// - No two edges cross, and no vertex is a zero-width spike.
204/// - Every hole lies inside the outer ring.
205///
206/// The uniform "interior on the left" rule is what lets the wavefront treat
207/// outer boundary and holes identically — see `docs/ALGORITHM.md`.
208///
209/// # Vertex and edge numbering
210///
211/// Vertices are numbered `0..n` across all rings, outer ring first. Edge `i`
212/// starts at vertex `i`; see [`EdgeId`].
213///
214/// # Examples
215///
216/// ```
217/// use straight_skeleton::{Point, Polygon};
218///
219/// // A square. Winding is fixed up for you.
220/// let square = Polygon::from_outer(&[
221/// Point::new(0, 0),
222/// Point::new(10, 0),
223/// Point::new(10, 10),
224/// Point::new(0, 10),
225/// ])?;
226/// assert_eq!(square.vertex_count(), 4);
227/// assert_eq!(square.ring_count(), 1);
228///
229/// // A square with a square hole.
230/// let with_hole = Polygon::new(
231/// &[Point::new(0, 0), Point::new(30, 0), Point::new(30, 30), Point::new(0, 30)],
232/// &[vec![
233/// Point::new(10, 10),
234/// Point::new(20, 10),
235/// Point::new(20, 20),
236/// Point::new(10, 20),
237/// ]],
238/// )?;
239/// assert_eq!(with_hole.ring_count(), 2);
240/// assert_eq!(with_hole.vertex_count(), 8);
241/// # Ok::<(), straight_skeleton::PolygonError>(())
242/// ```
243#[derive(Clone, Debug, PartialEq, Eq)]
244#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
245pub struct Polygon {
246 /// All vertices, outer ring first, then holes in order.
247 verts: Vec<Point>,
248 /// `ring_starts[i]..ring_starts[i + 1]` is ring `i`'s slice of `verts`.
249 /// Has `ring_count + 1` entries; the last is `verts.len()`.
250 ring_starts: Vec<u16>,
251}
252
253impl Polygon {
254 /// The largest number of vertices a polygon may have.
255 ///
256 /// Bounded by [`VertexId`]'s `u16`, minus one so that "one past the end"
257 /// indices cannot overflow.
258 pub const MAX_VERTICES: usize = u16::MAX as usize - 1;
259
260 /// Builds a polygon from an outer ring and a list of holes.
261 ///
262 /// Ring winding is normalised for you: the outer ring is made
263 /// counter-clockwise and holes clockwise, reversing any ring given the
264 /// other way round.
265 ///
266 /// # Errors
267 ///
268 /// Returns a [`PolygonError`] naming the offending ring if the input is not
269 /// a simple polygon with holes. See [`Polygon`]'s invariants for the full
270 /// list of checks.
271 ///
272 /// # Examples
273 ///
274 /// ```
275 /// use straight_skeleton::{Point, Polygon, PolygonError};
276 ///
277 /// // A ring that crosses itself is rejected, not silently accepted.
278 /// let crossed = Polygon::from_outer(&[
279 /// Point::new(0, 0),
280 /// Point::new(10, 10),
281 /// Point::new(10, 0),
282 /// Point::new(0, 4),
283 /// ]);
284 /// assert!(matches!(crossed, Err(PolygonError::SelfIntersection { .. })));
285 /// ```
286 pub fn new(outer: &[Point], holes: &[Vec<Point>]) -> Result<Self, PolygonError> {
287 if outer.is_empty() {
288 return Err(PolygonError::NoOuterRing);
289 }
290
291 let total: usize = outer.len() + holes.iter().map(|h| h.len()).sum::<usize>();
292 if total > Self::MAX_VERTICES {
293 return Err(PolygonError::TooManyVertices {
294 count: total,
295 max: Self::MAX_VERTICES,
296 });
297 }
298
299 let mut verts: Vec<Point> = Vec::with_capacity(total);
300 let mut ring_starts: Vec<u16> = Vec::with_capacity(holes.len() + 2);
301 ring_starts.push(0);
302
303 // The outer ring must be CCW and holes CW, so that the interior lies to
304 // the left of every directed edge without exception.
305 push_ring(&mut verts, &mut ring_starts, outer, RingId(0), true)?;
306 for (i, hole) in holes.iter().enumerate() {
307 let id = RingId((i + 1) as u16);
308 push_ring(&mut verts, &mut ring_starts, hole, id, false)?;
309 }
310
311 let poly = Polygon { verts, ring_starts };
312 poly.check_no_spikes()?;
313 poly.check_simple()?;
314 poly.check_holes_inside()?;
315 Ok(poly)
316 }
317
318 /// Builds a polygon with no holes.
319 ///
320 /// # Errors
321 ///
322 /// As [`Polygon::new`].
323 pub fn from_outer(outer: &[Point]) -> Result<Self, PolygonError> {
324 Polygon::new(outer, &[])
325 }
326
327 /// Total number of vertices across all rings.
328 #[inline]
329 pub fn vertex_count(&self) -> usize {
330 self.verts.len()
331 }
332
333 /// Number of rings: 1 (outer) plus one per hole.
334 #[inline]
335 pub fn ring_count(&self) -> usize {
336 self.ring_starts.len() - 1
337 }
338
339 /// Number of holes.
340 #[inline]
341 pub fn hole_count(&self) -> usize {
342 self.ring_count() - 1
343 }
344
345 /// All vertices, outer ring first, then holes in order.
346 #[inline]
347 pub fn vertices(&self) -> &[Point] {
348 &self.verts
349 }
350
351 /// The position of a vertex.
352 ///
353 /// # Panics
354 ///
355 /// Panics if `v` does not belong to this polygon.
356 #[inline]
357 pub fn vertex(&self, v: VertexId) -> Point {
358 self.verts[v.0 as usize]
359 }
360
361 /// The vertices of one ring, in order.
362 ///
363 /// # Panics
364 ///
365 /// Panics if `ring` does not belong to this polygon.
366 #[inline]
367 pub fn ring(&self, ring: RingId) -> &[Point] {
368 let lo = self.ring_starts[ring.0 as usize] as usize;
369 let hi = self.ring_starts[ring.0 as usize + 1] as usize;
370 &self.verts[lo..hi]
371 }
372
373 /// Which ring a vertex belongs to.
374 ///
375 /// # Panics
376 ///
377 /// Panics if `v` does not belong to this polygon.
378 pub fn ring_of(&self, v: VertexId) -> RingId {
379 let i = v.0;
380 // Rings are contiguous and ordered, so the ring is the last start <= i.
381 let idx = self
382 .ring_starts
383 .partition_point(|&start| start <= i)
384 .saturating_sub(1);
385 debug_assert!(idx < self.ring_count());
386 RingId(idx as u16)
387 }
388
389 /// Iterates every ring's vertices in order, outer ring first.
390 pub fn rings(&self) -> impl Iterator<Item = &[Point]> + '_ {
391 (0..self.ring_count()).map(move |i| self.ring(RingId(i as u16)))
392 }
393
394 /// The vertex following `v` within its ring, wrapping at the ring's end.
395 pub fn next_vertex(&self, v: VertexId) -> VertexId {
396 let ring = self.ring_of(v);
397 let lo = self.ring_starts[ring.0 as usize];
398 let hi = self.ring_starts[ring.0 as usize + 1];
399 if v.0 + 1 == hi {
400 VertexId(lo)
401 } else {
402 VertexId(v.0 + 1)
403 }
404 }
405
406 /// The vertex preceding `v` within its ring, wrapping at the ring's start.
407 pub fn prev_vertex(&self, v: VertexId) -> VertexId {
408 let ring = self.ring_of(v);
409 let lo = self.ring_starts[ring.0 as usize];
410 let hi = self.ring_starts[ring.0 as usize + 1];
411 if v.0 == lo {
412 VertexId(hi - 1)
413 } else {
414 VertexId(v.0 - 1)
415 }
416 }
417
418 /// The endpoints of an edge, in direction order.
419 ///
420 /// The polygon's interior lies to the **left** of `start -> end`.
421 ///
422 /// # Panics
423 ///
424 /// Panics if `e` does not belong to this polygon.
425 #[inline]
426 pub fn edge(&self, e: EdgeId) -> (Point, Point) {
427 let start = e.start_vertex();
428 (self.vertex(start), self.vertex(self.next_vertex(start)))
429 }
430
431 /// Total number of edges, which equals the number of vertices.
432 #[inline]
433 pub fn edge_count(&self) -> usize {
434 self.verts.len()
435 }
436
437 /// Iterates every edge id.
438 pub fn edge_ids(&self) -> impl Iterator<Item = EdgeId> + '_ {
439 (0..self.edge_count() as u16).map(EdgeId)
440 }
441
442 /// Iterates every vertex id.
443 pub fn vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
444 (0..self.vertex_count() as u16).map(VertexId)
445 }
446
447 /// Whether the interior angle at `v` exceeds 180°, i.e. `v` is a reflex
448 /// ("notch") corner.
449 ///
450 /// Reflex vertices are the only ones that can trigger split events, so this
451 /// drives the algorithm's main branch.
452 pub fn is_reflex(&self, v: VertexId) -> bool {
453 let prev = self.vertex(self.prev_vertex(v));
454 let cur = self.vertex(v);
455 let next = self.vertex(self.next_vertex(v));
456 // Interior is on the left, so a left turn (CCW) is convex.
457 orient2d(prev, cur, next) == Orientation::Clockwise
458 }
459
460 /// Twice the signed area of the polygon: the outer ring's area minus every
461 /// hole's. Always positive for a valid polygon.
462 pub fn signed_area2(&self) -> i64 {
463 self.rings().map(ring_area2).sum()
464 }
465
466 /// Rejects vertices where the ring reverses through exactly 180°.
467 ///
468 /// Such a corner is a zero-width spike: its wavefront vertex would need
469 /// infinite speed, so no finite skeleton exists.
470 fn check_no_spikes(&self) -> Result<(), PolygonError> {
471 for v in self.vertex_ids() {
472 let prev = self.vertex(self.prev_vertex(v));
473 let cur = self.vertex(v);
474 let next = self.vertex(self.next_vertex(v));
475
476 if orient2d(prev, cur, next) != Orientation::Collinear {
477 continue;
478 }
479 // Collinear at `cur`: either a straight-through vertex (fine, the
480 // wavefront just translates) or a 180° reversal (a spike). They are
481 // told apart by the sign of the dot product of the two edges.
482 let inc = (cur.x as i64 - prev.x as i64, cur.y as i64 - prev.y as i64);
483 let out = (next.x as i64 - cur.x as i64, next.y as i64 - cur.y as i64);
484 if inc.0 * out.0 + inc.1 * out.1 < 0 {
485 return Err(PolygonError::Spike {
486 ring: self.ring_of(v),
487 vertex: v,
488 });
489 }
490 }
491 Ok(())
492 }
493
494 /// Rejects polygons whose edges cross.
495 ///
496 /// # Why this is not the all-pairs loop
497 ///
498 /// Because all-pairs costs **five times more than the skeleton it feeds**:
499 /// 73ms against 13ms on a 3200-vertex comb. Validation being cheap enough
500 /// not to matter is an easy assumption to make and a wrong one — this runs
501 /// in a function every caller must go through to get a `Polygon` at all, so
502 /// it is on the critical path of everything the crate does.
503 ///
504 /// # What it does instead
505 ///
506 /// A sweep along x. Edges are visited left to right and only those whose
507 /// x-range still overlaps the sweep line are held open; the rest are dropped
508 /// and never looked at again. Two segments with disjoint x-ranges cannot
509 /// cross or touch, so the pairs skipped are exactly the pairs that could not
510 /// have failed. The verdict is the one all-pairs gives on every input, which
511 /// is asserted against the reference over several thousand random rings
512 /// rather than argued (`sweep_agrees_with_all_pairs_on_random_rings`).
513 ///
514 /// A y-overlap test then drops most of the surviving pairs before the exact
515 /// predicates run, which are much the more expensive part.
516 ///
517 /// # What it does not fix
518 ///
519 /// The worst case is still `O(n^2)`, unavoidably: a polygon whose edges all
520 /// span the full width has `n^2` pairs that genuinely need testing, and
521 /// pruning cannot skip a pair that might really cross.
522 ///
523 /// That case is not hypothetical. The comb above falls to 0.13ms — its teeth
524 /// each occupy their own narrow column — but a star of long spokes radiating
525 /// from a centre has every edge overlapping every other in x, and only
526 /// improves from 67ms to 17ms. Beating *that* needs a real sweep-line
527 /// intersection algorithm (Bentley–Ottmann), whose event ordering around
528 /// vertical segments, shared endpoints and collinear overlaps is a
529 /// well-known source of subtle wrongness. Given the crate's
530 /// correct > fast > understandable ordering, an exact prune that is
531 /// occasionally no help beats an asymptotically better algorithm that is
532 /// occasionally incorrect.
533 fn check_simple(&self) -> Result<(), PolygonError> {
534 let n = self.edge_count();
535 if n < 2 {
536 return Ok(());
537 }
538
539 // Edge bounding boxes, computed once. The sweep touches these far more
540 // often than it touches the edges themselves.
541 let boxes: Vec<[i16; 4]> = (0..n)
542 .map(|i| {
543 let (p, q) = self.edge(EdgeId(i as u16));
544 [p.x.min(q.x), p.x.max(q.x), p.y.min(q.y), p.y.max(q.y)]
545 })
546 .collect();
547
548 let mut order: Vec<u16> = (0..n as u16).collect();
549 // By left edge, so that once a box falls behind the sweep line it is
550 // behind it for every edge still to come.
551 order.sort_unstable_by_key(|&e| boxes[e as usize][0]);
552
553 let mut active: Vec<u16> = Vec::new();
554 for &i in &order {
555 let bi = boxes[i as usize];
556 active.retain(|&j| boxes[j as usize][1] >= bi[0]);
557
558 for &j in &active {
559 let bj = boxes[j as usize];
560 // Disjoint in y: no need to ask the predicates.
561 if bj[3] < bi[2] || bi[3] < bj[2] {
562 continue;
563 }
564
565 // Reported low id first, so the error names the same pair
566 // whatever order the sweep happened to reach them in.
567 let (a, b) = if i < j {
568 (EdgeId(i), EdgeId(j))
569 } else {
570 (EdgeId(j), EdgeId(i))
571 };
572 let (a1, a2) = self.edge(a);
573 let (b1, b2) = self.edge(b);
574
575 if segments_properly_cross(a1, a2, b1, b2) {
576 return Err(PolygonError::SelfIntersection { a, b });
577 }
578
579 // A proper crossing test deliberately ignores touching, since
580 // consecutive edges must share a vertex. But two *non*-adjacent
581 // edges touching is still an invalid pinch, so check for it.
582 if !self.edges_are_adjacent(a, b) && self.edges_touch(a, b) {
583 return Err(PolygonError::SelfIntersection { a, b });
584 }
585 }
586 active.push(i);
587 }
588 Ok(())
589 }
590
591 /// Whether two edges share a vertex by construction (consecutive in a ring).
592 fn edges_are_adjacent(&self, a: EdgeId, b: EdgeId) -> bool {
593 let a_start = a.start_vertex();
594 let b_start = b.start_vertex();
595 self.next_vertex(a_start) == b_start || self.next_vertex(b_start) == a_start
596 }
597
598 /// Whether two edges share any point at all.
599 fn edges_touch(&self, a: EdgeId, b: EdgeId) -> bool {
600 use crate::predicates::point_on_segment;
601 let (a1, a2) = self.edge(a);
602 let (b1, b2) = self.edge(b);
603 point_on_segment(a1, b1, b2)
604 || point_on_segment(a2, b1, b2)
605 || point_on_segment(b1, a1, a2)
606 || point_on_segment(b2, a1, a2)
607 }
608
609 /// Rejects holes that escape the outer ring.
610 ///
611 /// Holes overlapping each other, or poking out of the outer ring, would
612 /// already have been caught by [`Polygon::check_simple`] as a crossing.
613 /// What remains is a hole entirely *outside* the outer ring, which crosses
614 /// nothing — so one containment test per hole closes the gap.
615 fn check_holes_inside(&self) -> Result<(), PolygonError> {
616 let outer = self.ring(RingId(0));
617 for h in 1..self.ring_count() {
618 let ring = RingId(h as u16);
619 let probe = self.ring(ring)[0];
620 if !point_in_ring(probe, outer) {
621 return Err(PolygonError::HoleOutsideOuter { ring });
622 }
623 }
624 Ok(())
625 }
626}
627
628/// Normalises and appends one ring, enforcing the per-ring invariants.
629fn push_ring(
630 verts: &mut Vec<Point>,
631 ring_starts: &mut Vec<u16>,
632 ring: &[Point],
633 id: RingId,
634 want_ccw: bool,
635) -> Result<(), PolygonError> {
636 // Tolerate the common convention of repeating the first point to close the
637 // ring; the crate's own representation leaves rings implicitly closed.
638 let ring = match ring {
639 [first, mid @ .., last] if first == last && !mid.is_empty() => &ring[..ring.len() - 1],
640 _ => ring,
641 };
642
643 if ring.len() < 3 {
644 return Err(PolygonError::TooFewVertices {
645 ring: id,
646 count: ring.len(),
647 });
648 }
649
650 // Checked before anything else: every predicate below is only exact inside
651 // the cap, and `signed_area2` debug-asserts it.
652 for &p in ring {
653 if !p.in_range() {
654 return Err(PolygonError::CoordinateOutOfRange { ring: id, point: p });
655 }
656 }
657
658 for i in 0..ring.len() {
659 let next = (i + 1) % ring.len();
660 if ring[i] == ring[next] {
661 return Err(PolygonError::RepeatedVertex {
662 ring: id,
663 index: next,
664 point: ring[i],
665 });
666 }
667 }
668
669 let area2 = ring_area2(ring);
670 if area2 == 0 {
671 return Err(PolygonError::DegenerateRing { ring: id });
672 }
673
674 let start = verts.len();
675 verts.extend_from_slice(ring);
676 if is_ccw(ring) != want_ccw {
677 verts[start..].reverse();
678 }
679 ring_starts.push(verts.len() as u16);
680 Ok(())
681}
682
683/// Exact point-in-ring test by crossing number.
684///
685/// Uses only `i64` predicates, so it is exact for every `i16` input. Points
686/// exactly on the boundary are reported as inside.
687fn point_in_ring(p: Point, ring: &[Point]) -> bool {
688 let n = ring.len();
689 let mut inside = false;
690 for i in 0..n {
691 let a = ring[i];
692 let b = ring[(i + 1) % n];
693
694 if crate::predicates::point_on_segment(p, a, b) {
695 return true;
696 }
697
698 // Cast a ray in +x and count crossings. The half-open rule
699 // (a.y <= p.y < b.y) counts each crossing exactly once, so vertices
700 // touched by the ray don't get double-counted.
701 let crosses = (a.y > p.y) != (b.y > p.y);
702 if crosses {
703 // Is the crossing strictly right of p? Compare exactly, without
704 // dividing: the sign of the orientation, flipped when the edge
705 // points downward.
706 let side = orient2d(a, b, p);
707 let upward = b.y > a.y;
708 let right_of_p = if upward {
709 side == Orientation::Clockwise
710 } else {
711 side == Orientation::CounterClockwise
712 };
713 if right_of_p {
714 inside = !inside;
715 }
716 }
717 }
718 inside
719}
720
721#[cfg(test)]
722mod tests {
723 use super::*;
724 use alloc::vec;
725
726 fn square(size: i16) -> Vec<Point> {
727 vec![
728 Point::new(0, 0),
729 Point::new(size, 0),
730 Point::new(size, size),
731 Point::new(0, size),
732 ]
733 }
734
735 #[test]
736 fn builds_a_square() {
737 let p = Polygon::from_outer(&square(10)).unwrap();
738 assert_eq!(p.vertex_count(), 4);
739 assert_eq!(p.edge_count(), 4);
740 assert_eq!(p.ring_count(), 1);
741 assert_eq!(p.hole_count(), 0);
742 assert_eq!(p.signed_area2(), 200);
743 }
744
745 #[test]
746 fn normalises_outer_ring_to_ccw() {
747 let mut cw = square(10);
748 cw.reverse();
749 let p = Polygon::from_outer(&cw).unwrap();
750 assert!(is_ccw(p.ring(RingId(0))), "outer ring must end up CCW");
751 assert!(p.signed_area2() > 0);
752 }
753
754 #[test]
755 fn normalises_holes_to_cw() {
756 let hole_ccw = vec![
757 Point::new(10, 10),
758 Point::new(20, 10),
759 Point::new(20, 20),
760 Point::new(10, 20),
761 ];
762 let p = Polygon::new(&square(30), &[hole_ccw]).unwrap();
763 assert!(!is_ccw(p.ring(RingId(1))), "hole must end up CW");
764 // Outer 30x30 = 900, hole 10x10 = 100. Twice the difference is 1600.
765 assert_eq!(p.signed_area2(), 1600);
766 }
767
768 #[test]
769 fn accepts_explicitly_closed_rings() {
770 let mut closed = square(10);
771 closed.push(closed[0]);
772 let p = Polygon::from_outer(&closed).unwrap();
773 assert_eq!(p.vertex_count(), 4, "the repeated closing point is dropped");
774 }
775
776 #[test]
777 fn edge_and_vertex_ids_correspond() {
778 let p = Polygon::from_outer(&square(10)).unwrap();
779 for v in p.vertex_ids() {
780 assert_eq!(v.outgoing_edge().start_vertex(), v);
781 let (start, _) = p.edge(v.outgoing_edge());
782 assert_eq!(start, p.vertex(v));
783 }
784 }
785
786 #[test]
787 fn edges_wrap_within_their_ring() {
788 let p = Polygon::new(
789 &square(30),
790 &[vec![
791 Point::new(10, 10),
792 Point::new(10, 20),
793 Point::new(20, 20),
794 Point::new(20, 10),
795 ]],
796 )
797 .unwrap();
798
799 // The outer ring's last edge closes back to the outer ring's first
800 // vertex, not into the hole.
801 assert_eq!(p.next_vertex(VertexId(3)), VertexId(0));
802 // The hole's last edge closes back to the hole's first vertex.
803 assert_eq!(p.next_vertex(VertexId(7)), VertexId(4));
804 assert_eq!(p.prev_vertex(VertexId(4)), VertexId(7));
805 }
806
807 #[test]
808 fn ring_of_maps_vertices_correctly() {
809 let p = Polygon::new(
810 &square(30),
811 &[vec![
812 Point::new(10, 10),
813 Point::new(10, 20),
814 Point::new(20, 20),
815 Point::new(20, 10),
816 ]],
817 )
818 .unwrap();
819 for v in 0..4 {
820 assert_eq!(p.ring_of(VertexId(v)), RingId(0));
821 }
822 for v in 4..8 {
823 assert_eq!(p.ring_of(VertexId(v)), RingId(1));
824 }
825 }
826
827 #[test]
828 fn rejects_too_few_vertices() {
829 let e = Polygon::from_outer(&[Point::new(0, 0), Point::new(1, 1)]).unwrap_err();
830 assert!(matches!(e, PolygonError::TooFewVertices { count: 2, .. }));
831 }
832
833 #[test]
834 fn rejects_coordinates_outside_the_cap() {
835 // One past the cap on a single coordinate is enough.
836 let e = Polygon::from_outer(&[Point::new(0, 0), Point::new(16384, 0), Point::new(0, 100)])
837 .unwrap_err();
838 assert!(
839 matches!(e, PolygonError::CoordinateOutOfRange { point, .. } if point.x == 16384),
840 "got {e:?}"
841 );
842
843 // And the negative side.
844 assert!(matches!(
845 Polygon::from_outer(&[Point::new(0, 0), Point::new(100, 0), Point::new(0, -16385)])
846 .unwrap_err(),
847 PolygonError::CoordinateOutOfRange { .. }
848 ));
849
850 // Right at the cap is fine.
851 assert!(Polygon::from_outer(&[
852 Point::new(Point::MIN_COORD, Point::MIN_COORD),
853 Point::new(Point::MAX_COORD, Point::MIN_COORD),
854 Point::new(Point::MAX_COORD, Point::MAX_COORD),
855 ])
856 .is_ok());
857 }
858
859 #[test]
860 fn rejects_empty_outer_ring() {
861 assert_eq!(
862 Polygon::from_outer(&[]).unwrap_err(),
863 PolygonError::NoOuterRing
864 );
865 }
866
867 #[test]
868 fn rejects_repeated_vertices() {
869 let e = Polygon::from_outer(&[
870 Point::new(0, 0),
871 Point::new(10, 0),
872 Point::new(10, 0),
873 Point::new(10, 10),
874 ])
875 .unwrap_err();
876 assert!(matches!(e, PolygonError::RepeatedVertex { .. }));
877 }
878
879 #[test]
880 fn rejects_collinear_ring() {
881 let e = Polygon::from_outer(&[Point::new(0, 0), Point::new(5, 0), Point::new(9, 0)])
882 .unwrap_err();
883 assert!(matches!(e, PolygonError::DegenerateRing { .. }));
884 }
885
886 /// The reference `check_simple` replaced: every pair, no pruning.
887 fn check_simple_all_pairs(p: &Polygon) -> Option<(EdgeId, EdgeId)> {
888 let n = p.edge_count();
889 for i in 0..n {
890 let a = EdgeId(i as u16);
891 let (a1, a2) = p.edge(a);
892 for j in (i + 1)..n {
893 let b = EdgeId(j as u16);
894 let (b1, b2) = p.edge(b);
895 if segments_properly_cross(a1, a2, b1, b2) {
896 return Some((a, b));
897 }
898 if !p.edges_are_adjacent(a, b) && p.edges_touch(a, b) {
899 return Some((a, b));
900 }
901 }
902 }
903 None
904 }
905
906 /// The sweep must accept and reject exactly what all-pairs did.
907 ///
908 /// That is a claim about *every* input, not about the shapes someone thought
909 /// to write a test for, so it is checked against the reference over a few
910 /// thousand random rings. Coordinates are drawn from a deliberately tiny
911 /// grid: it makes crossings, collinear overlaps and shared endpoints common
912 /// rather than vanishingly rare, which is where a pruning bug would hide.
913 ///
914 /// The rings go straight into a `Polygon` rather than through
915 /// `Polygon::new`, because the whole point is to reach `check_simple` with
916 /// the malformed input that `new` exists to reject.
917 #[test]
918 fn sweep_agrees_with_all_pairs_on_random_rings() {
919 let mut rng = 0x9E37_79B9_7F4A_7C15u64;
920 let mut next = || {
921 rng ^= rng << 13;
922 rng ^= rng >> 7;
923 rng ^= rng << 17;
924 rng
925 };
926
927 let mut crossing = 0;
928 let mut simple = 0;
929 for _ in 0..4000 {
930 let n = 3 + (next() % 8) as usize;
931 let verts: Vec<Point> = (0..n)
932 .map(|_| Point::new((next() % 7) as i16, (next() % 7) as i16))
933 .collect();
934 let poly = Polygon {
935 ring_starts: vec![0, verts.len() as u16],
936 verts,
937 };
938
939 let want = check_simple_all_pairs(&poly);
940 let got = poly.check_simple();
941 assert_eq!(
942 want.is_some(),
943 got.is_err(),
944 "sweep and all-pairs disagree on {:?}: all-pairs {want:?}, sweep {got:?}",
945 poly.verts
946 );
947 if want.is_some() {
948 crossing += 1;
949 } else {
950 simple += 1;
951 }
952 }
953
954 // A test that only ever saw one answer would pass while checking nothing.
955 assert!(crossing > 100, "only {crossing} crossing cases generated");
956 assert!(simple > 100, "only {simple} simple cases generated");
957 }
958
959 #[test]
960 fn rejects_self_intersecting_ring() {
961 // Asymmetric, so it has non-zero area and must be caught by the
962 // crossing test rather than incidentally by the area test.
963 let e = Polygon::from_outer(&[
964 Point::new(0, 0),
965 Point::new(10, 10),
966 Point::new(10, 0),
967 Point::new(0, 4),
968 ])
969 .unwrap_err();
970 assert!(
971 matches!(e, PolygonError::SelfIntersection { .. }),
972 "got {e:?}"
973 );
974 }
975
976 #[test]
977 fn rejects_symmetric_bowtie() {
978 // A symmetric bowtie's two lobes cancel exactly, so it trips the
979 // zero-area check before the crossing check ever runs. Either
980 // rejection is correct; what matters is that it does not build.
981 let e = Polygon::from_outer(&[
982 Point::new(0, 0),
983 Point::new(10, 10),
984 Point::new(10, 0),
985 Point::new(0, 10),
986 ])
987 .unwrap_err();
988 assert_eq!(e, PolygonError::DegenerateRing { ring: RingId(0) });
989 }
990
991 #[test]
992 fn rejects_zero_width_spike() {
993 // Out to (20, 5) and straight back along the same line.
994 let e = Polygon::from_outer(&[
995 Point::new(0, 0),
996 Point::new(10, 0),
997 Point::new(10, 5),
998 Point::new(20, 5),
999 Point::new(10, 5),
1000 Point::new(0, 10),
1001 ])
1002 .unwrap_err();
1003 assert!(
1004 matches!(
1005 e,
1006 PolygonError::Spike { .. } | PolygonError::SelfIntersection { .. }
1007 ),
1008 "got {e:?}"
1009 );
1010 }
1011
1012 #[test]
1013 fn accepts_straight_through_vertices() {
1014 // A collinear vertex mid-edge is not a spike; the wavefront handles it.
1015 let p = Polygon::from_outer(&[
1016 Point::new(0, 0),
1017 Point::new(5, 0), // straight-through
1018 Point::new(10, 0),
1019 Point::new(10, 10),
1020 Point::new(0, 10),
1021 ])
1022 .unwrap();
1023 assert_eq!(p.vertex_count(), 5);
1024 }
1025
1026 #[test]
1027 fn rejects_hole_outside_outer() {
1028 let far_hole = vec![
1029 Point::new(100, 100),
1030 Point::new(110, 100),
1031 Point::new(110, 110),
1032 Point::new(100, 110),
1033 ];
1034 let e = Polygon::new(&square(30), &[far_hole]).unwrap_err();
1035 assert!(matches!(e, PolygonError::HoleOutsideOuter { .. }));
1036 }
1037
1038 #[test]
1039 fn rejects_overlapping_holes() {
1040 let a = vec![
1041 Point::new(5, 5),
1042 Point::new(15, 5),
1043 Point::new(15, 15),
1044 Point::new(5, 15),
1045 ];
1046 let b = vec![
1047 Point::new(10, 10),
1048 Point::new(20, 10),
1049 Point::new(20, 20),
1050 Point::new(10, 20),
1051 ];
1052 assert!(Polygon::new(&square(30), &[a, b]).is_err());
1053 }
1054
1055 #[test]
1056 fn rejects_hole_touching_outer_ring() {
1057 // A hole whose vertex lands on the outer boundary pinches the interior.
1058 let touching = vec![
1059 Point::new(0, 10),
1060 Point::new(10, 10),
1061 Point::new(10, 20),
1062 Point::new(0, 20),
1063 ];
1064 assert!(Polygon::new(&square(30), &[touching]).is_err());
1065 }
1066
1067 #[test]
1068 fn detects_reflex_vertices() {
1069 // An L-shape: exactly one reflex corner, at the inner elbow.
1070 let l = Polygon::from_outer(&[
1071 Point::new(0, 0),
1072 Point::new(20, 0),
1073 Point::new(20, 10),
1074 Point::new(10, 10), // reflex elbow
1075 Point::new(10, 20),
1076 Point::new(0, 20),
1077 ])
1078 .unwrap();
1079
1080 let reflex: Vec<_> = l.vertex_ids().filter(|&v| l.is_reflex(v)).collect();
1081 assert_eq!(reflex, vec![VertexId(3)]);
1082 }
1083
1084 #[test]
1085 fn convex_polygons_have_no_reflex_vertices() {
1086 let p = Polygon::from_outer(&square(10)).unwrap();
1087 assert!(p.vertex_ids().all(|v| !p.is_reflex(v)));
1088 }
1089
1090 #[test]
1091 fn hole_vertices_are_reflex_from_the_interiors_view() {
1092 // A hole's convex-looking corners bulge *into* the material, so under
1093 // the interior-on-the-left rule they are reflex. This is what makes
1094 // holes generate split events.
1095 let p = Polygon::new(
1096 &square(30),
1097 &[vec![
1098 Point::new(10, 10),
1099 Point::new(20, 10),
1100 Point::new(20, 20),
1101 Point::new(10, 20),
1102 ]],
1103 )
1104 .unwrap();
1105 for v in 4..8 {
1106 assert!(p.is_reflex(VertexId(v)), "hole vertex {v} should be reflex");
1107 }
1108 }
1109
1110 #[test]
1111 fn point_in_ring_basics() {
1112 let sq = square(10);
1113 assert!(point_in_ring(Point::new(5, 5), &sq));
1114 assert!(!point_in_ring(Point::new(15, 5), &sq));
1115 assert!(!point_in_ring(Point::new(-1, 5), &sq));
1116 // Boundary counts as inside.
1117 assert!(point_in_ring(Point::new(0, 5), &sq));
1118 assert!(point_in_ring(Point::new(0, 0), &sq));
1119 }
1120
1121 #[test]
1122 fn point_in_ring_handles_rays_through_vertices() {
1123 // A diamond: the +x ray from (0, 0) passes exactly through vertex
1124 // (10, 0), which a naive crossing count would tally twice.
1125 let diamond = vec![
1126 Point::new(10, 0),
1127 Point::new(20, 10),
1128 Point::new(10, 20),
1129 Point::new(0, 10),
1130 ];
1131 assert!(!point_in_ring(Point::new(-5, 0), &diamond));
1132 assert!(point_in_ring(Point::new(10, 10), &diamond));
1133 assert!(!point_in_ring(Point::new(10, 25), &diamond));
1134 }
1135
1136 #[test]
1137 fn display_for_errors_names_the_ring() {
1138 let e = PolygonError::HoleOutsideOuter { ring: RingId(2) };
1139 assert!(alloc::format!("{e}").contains('2'));
1140 }
1141}