Skip to main content

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}