Skip to main content

fast_mesh_query/
triangulate.rs

1//! Triangulation of 2D polygons with holes.
2//!
3//! [`triangulate`] is ear clipping in the manner of Mapbox's earcut: the
4//! polygon is a doubly linked ring of vertices, each hole is bridged into the
5//! outer ring by a pair of coincident edges, and ears are clipped until two
6//! vertices remain. Above 80 points the ear test finds the vertices near a
7//! candidate ear through a list sorted by z-order (Morton) code, as earcut
8//! does, instead of walking the whole ring, so that large rings no longer
9//! cost `O(n²)`. It is meant for section fills and hole filling, where the
10//! input is usually simple but may not be, so it never panics and reports
11//! whether it finished instead of failing.
12
13use std::cmp::Ordering;
14
15use crate::{QueryError, Result, allocation_allowed};
16
17/// Triangulates the polygon `outer` minus the polygons in `holes`, appending
18/// the triangles to `out`.
19///
20/// Vertices are numbered `outer` first, then each hole in order; a triangle
21/// is three such numbers. Triangles are counter-clockwise (positive area in
22/// a right-handed `(x, y)` frame) when the input is a simple polygon with
23/// simple holes inside it.
24///
25/// - Either orientation is accepted: the outer ring is made counter-clockwise
26///   and the holes clockwise internally.
27/// - Consecutive equal points, including a last point equal to the first,
28///   count as one. The repeats keep their numbers but never appear in a
29///   triangle. Collinear vertices may also be left out of every triangle.
30/// - A ring with fewer than three distinct points has nothing to fill: such
31///   an outer gives no triangles and `Ok(true)`, and such a hole is ignored.
32///   An outer whose points are all collinear also gives no triangles and
33///   `Ok(true)`.
34/// - Any non-finite coordinate (NaN or infinite), in the outer ring or a
35///   hole, gives no triangles and `Ok(false)`.
36///
37/// Returns `Ok(false)` when ear clipping could not finish (typically a
38/// self-intersecting ring) or a hole could not be bridged; the triangles found
39/// so far are kept in `out`. It never panics.
40///
41/// # Errors
42/// - [`QueryError::OutOfMemory`] when working memory or room for the
43///   triangles could not be reserved; `out` is then unchanged.
44/// - [`QueryError::InvalidInput`] when there are more points in all than a
45///   `u32` can number.
46pub fn triangulate(
47    outer: &[[f64; 2]],
48    holes: &[&[[f64; 2]]],
49    out: &mut Vec<[u32; 3]>,
50) -> Result<bool> {
51    let points = holes
52        .iter()
53        .fold(outer.len(), |n, h| n.saturating_add(h.len()));
54    triangulate_with(outer, holes, out, points > Z_ORDER_MIN)
55}
56
57/// The number of points above which ears are tested through the z-order
58/// list (earcut's threshold); below it, walking the ring is as fast.
59const Z_ORDER_MIN: usize = 80;
60
61/// [`triangulate`], with the z-order ear test when `z_order` is set and the
62/// ring's box has a finite, non-zero size. Both tests give the same triangles.
63fn triangulate_with(
64    outer: &[[f64; 2]],
65    holes: &[&[[f64; 2]]],
66    out: &mut Vec<[u32; 3]>,
67    z_order: bool,
68) -> Result<bool> {
69    let total = holes
70        .iter()
71        .try_fold(outer.len(), |n, h| n.checked_add(h.len()))
72        .filter(|&n| n <= u32::MAX as usize)
73        .ok_or(QueryError::InvalidInput("too many polygon points"))?;
74    let finite = |r: &[[f64; 2]]| r.iter().flatten().all(|c| c.is_finite());
75    if !finite(outer) || !holes.iter().all(|h| finite(h)) {
76        return Ok(false);
77    }
78    // Each bridge duplicates two vertices, and every triangle removes at
79    // least one vertex from the ring until two remain.
80    // Saturating, so that an impossible size fails to reserve instead.
81    let capacity = total.saturating_add(holes.len().saturating_mul(2));
82    let mut nodes = Vec::new();
83    reserve(&mut nodes, capacity)?;
84    let mut queue = Vec::new();
85    reserve(&mut queue, holes.len())?;
86    reserve(out, capacity.saturating_sub(2))?;
87
88    let mut e = Earcut {
89        nodes,
90        out,
91        ok: true,
92        grid: None,
93    };
94    let Some(mut start) = e.ring(outer, 0, true) else {
95        return Ok(true);
96    };
97    if e.next(start) == e.prev(start) {
98        return Ok(true);
99    }
100    let mut offset = outer.len();
101    for hole in holes {
102        if let Some(h) = e.ring(hole, offset, false) {
103            // A hole of one or two distinct points has no area to remove.
104            if e.next(h) != e.prev(h) {
105                queue.push(e.leftmost(h));
106            }
107        }
108        offset += hole.len();
109    }
110    // Bridge holes from left to right, so that each bridge sees the ones
111    // bridged before it as part of the outer ring.
112    queue.sort_by(|&a, &b| e.compare_xy_slope(a, b));
113    for &h in &queue {
114        start = e.eliminate_hole(h, start);
115    }
116    let start = e.live(start);
117    if z_order {
118        e.index_curve(start);
119    }
120    let finished = e.clip(start);
121    Ok(finished && e.ok)
122}
123
124/// Reserves room for `n` more elements, honouring the test fault hook.
125fn reserve<T>(v: &mut Vec<T>, n: usize) -> Result<()> {
126    allocation_allowed()?;
127    v.try_reserve(n).map_err(|_| QueryError::OutOfMemory)
128}
129
130/// A vertex of the ring: its input number, position and neighbours.
131#[derive(Debug, Clone, Copy)]
132struct Node {
133    i: u32,
134    x: f64,
135    y: f64,
136    prev: usize,
137    next: usize,
138    /// The z-order code of the point, once the ring is indexed.
139    z: u32,
140    /// The neighbours in the list of the ring's nodes sorted by `z`.
141    prev_z: Option<usize>,
142    next_z: Option<usize>,
143    /// Unlinked from its ring (and from the z-order list). Its `prev` and `next` still name the
144    /// neighbours it had when it was removed.
145    removed: bool,
146}
147
148/// Twice the signed area of the turn `p → q → r`, negated: negative when it
149/// turns counter-clockwise. The sign convention is earcut's, so that the
150/// predicates below read as there.
151fn area(p: &Node, q: &Node, r: &Node) -> f64 {
152    (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y)
153}
154
155/// The z-order (Morton) code of a point: its coordinates scaled to 15 bits
156/// over the ring's box and interleaved. It never decreases as `x` or `y`
157/// grows, so the points of a box have codes between those of its corners.
158fn z_order(x: f64, y: f64, grid: &ZGrid) -> u32 {
159    // Clamped, so that rounding at the box's edges stays in 0..=32767.
160    let scale = |v: f64, lo: f64| ((v - lo) * grid.inv_size).clamp(0.0, 32767.0) as u32;
161    let spread = |mut v: u32| {
162        v = (v | (v << 8)) & 0x00FF_00FF;
163        v = (v | (v << 4)) & 0x0F0F_0F0F;
164        v = (v | (v << 2)) & 0x3333_3333;
165        (v | (v << 1)) & 0x5555_5555
166    };
167    spread(scale(x, grid.min_x)) | (spread(scale(y, grid.min_y)) << 1)
168}
169
170fn equals(a: &Node, b: &Node) -> bool {
171    a.x == b.x && a.y == b.y
172}
173
174/// Whether `(px, py)` is inside or on the counter-clockwise triangle `abc`.
175#[allow(clippy::too_many_arguments)]
176fn point_in_triangle(
177    ax: f64,
178    ay: f64,
179    bx: f64,
180    by: f64,
181    cx: f64,
182    cy: f64,
183    px: f64,
184    py: f64,
185) -> bool {
186    (cx - px) * (ay - py) >= (ax - px) * (cy - py)
187        && (ax - px) * (by - py) >= (bx - px) * (ay - py)
188        && (bx - px) * (cy - py) >= (cx - px) * (by - py)
189}
190
191fn sign(v: f64) -> i8 {
192    if v > 0.0 {
193        1
194    } else if v < 0.0 {
195        -1
196    } else {
197        0
198    }
199}
200
201/// Whether `q` lies in the bounding box of `p` and `r` (`q` collinear with them).
202fn on_segment(p: &Node, q: &Node, r: &Node) -> bool {
203    q.x <= p.x.max(r.x) && q.x >= p.x.min(r.x) && q.y <= p.y.max(r.y) && q.y >= p.y.min(r.y)
204}
205
206/// Whether the segments `p1 q1` and `p2 q2` intersect, touching included.
207fn intersects(p1: &Node, q1: &Node, p2: &Node, q2: &Node) -> bool {
208    let o1 = sign(area(p1, q1, p2));
209    let o2 = sign(area(p1, q1, q2));
210    let o3 = sign(area(p2, q2, p1));
211    let o4 = sign(area(p2, q2, q1));
212    (o1 != o2 && o3 != o4)
213        || (o1 == 0 && on_segment(p1, p2, q1))
214        || (o2 == 0 && on_segment(p1, q2, q1))
215        || (o3 == 0 && on_segment(p2, p1, q2))
216        || (o4 == 0 && on_segment(p2, q1, q2))
217}
218
219#[cfg(test)]
220thread_local! {
221    /// Test-only count of twists removed by
222    /// [`Earcut::cure_local_intersections`] on this thread.
223    static CURES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
224}
225
226/// The ring under construction and the triangles found so far.
227///
228/// Removed nodes stay in `nodes`, unlinked. Every loop below either removes
229/// a node or walks once around the ring, so all terminate, whatever the
230/// coordinates.
231struct Earcut<'a> {
232    nodes: Vec<Node>,
233    out: &'a mut Vec<[u32; 3]>,
234    /// False once a hole could not be bridged.
235    ok: bool,
236    /// The box z-order codes are taken over, once the ring is indexed.
237    grid: Option<ZGrid>,
238}
239
240/// The lower corner of the ring's box, and `32767` over its larger side.
241#[derive(Debug, Clone, Copy)]
242struct ZGrid {
243    min_x: f64,
244    min_y: f64,
245    inv_size: f64,
246}
247
248impl Earcut<'_> {
249    fn n(&self, k: usize) -> &Node {
250        &self.nodes[k]
251    }
252
253    fn next(&self, k: usize) -> usize {
254        self.nodes[k].next
255    }
256
257    fn prev(&self, k: usize) -> usize {
258        self.nodes[k].prev
259    }
260
261    /// `area` of the turn at three nodes.
262    fn turn(&self, p: usize, q: usize, r: usize) -> f64 {
263        area(self.n(p), self.n(q), self.n(r))
264    }
265
266    fn same(&self, a: usize, b: usize) -> bool {
267        equals(self.n(a), self.n(b))
268    }
269
270    /// Adds a node after `last` (or a ring of one when there is none).
271    fn insert(&mut self, i: u32, p: [f64; 2], last: Option<usize>) -> usize {
272        let k = self.nodes.len();
273        // Adding zero turns -0.0 into 0.0, so that sorting treats them alike.
274        let (x, y) = (p[0] + 0.0, p[1] + 0.0);
275        let (prev, next) = match last {
276            Some(l) => (l, self.next(l)),
277            None => (k, k),
278        };
279        // Capacity was reserved for every node up front.
280        self.nodes.push(Node {
281            i,
282            x,
283            y,
284            prev,
285            next,
286            z: 0,
287            prev_z: None,
288            next_z: None,
289            removed: false,
290        });
291        if let Some(l) = last {
292            self.nodes[next].prev = k;
293            self.nodes[l].next = k;
294        }
295        k
296    }
297
298    fn remove(&mut self, k: usize) {
299        let Node {
300            prev,
301            next,
302            prev_z,
303            next_z,
304            ..
305        } = self.nodes[k];
306        self.nodes[next].prev = prev;
307        self.nodes[prev].next = next;
308        if let Some(p) = prev_z {
309            self.nodes[p].next_z = next_z;
310        }
311        if let Some(n) = next_z {
312            self.nodes[n].prev_z = prev_z;
313        }
314        self.nodes[k].removed = true;
315    }
316
317    /// `k`, or the first node still in a ring after it when `k` was removed.
318    ///
319    /// A node handed back by one pass may be removed by the next (filtering
320    /// can cascade backwards past it); walking a ring from a removed node
321    /// would never come back to it. Following `next` from a removed node
322    /// always reaches a node removed later or not at all, so this ends.
323    fn live(&self, mut k: usize) -> usize {
324        for _ in 0..self.nodes.len() {
325            if !self.nodes[k].removed {
326                break;
327            }
328            k = self.next(k);
329        }
330        k
331    }
332
333    /// Links `points` (numbered from `offset`) into a ring, counter-clockwise
334    /// when `ccw` and clockwise otherwise, skipping consecutive repeats.
335    /// Returns its last node, or `None` for no points.
336    fn ring(&mut self, points: &[[f64; 2]], offset: usize, ccw: bool) -> Option<usize> {
337        let n = points.len();
338        let o = points.first().copied().unwrap_or_default();
339        // Twice the shoelace area, positive when counter-clockwise, taken
340        // about the first point: that keeps the products small when the ring
341        // is far from the origin.
342        let twice_area: f64 = (0..n)
343            .map(|j| {
344                let (p, q) = (points[j], points[(j + 1) % n]);
345                (p[0] - o[0]) * (q[1] - o[1]) - (q[0] - o[0]) * (p[1] - o[1])
346            })
347            .sum();
348        let forward = (twice_area > 0.0) == ccw;
349        let mut last: Option<usize> = None;
350        for j in 0..n {
351            let j = if forward { j } else { n - 1 - j };
352            let p = points[j];
353            if last.is_some_and(|l| self.n(l).x == p[0] && self.n(l).y == p[1]) {
354                continue;
355            }
356            // `j + offset` is below the total, which fits a `u32`.
357            last = Some(self.insert((offset + j) as u32, p, last));
358        }
359        let l = last?;
360        if l != self.next(l) && self.same(l, self.next(l)) {
361            let first = self.next(l);
362            self.remove(l);
363            return Some(first);
364        }
365        Some(l)
366    }
367
368    /// The leftmost node of the ring through `start`, lowest on ties.
369    fn leftmost(&self, start: usize) -> usize {
370        let (mut p, mut best) = (start, start);
371        loop {
372            let (a, b) = (self.n(p), self.n(best));
373            if a.x < b.x || (a.x == b.x && a.y < b.y) {
374                best = p;
375            }
376            p = self.next(p);
377            if p == start {
378                return best;
379            }
380        }
381    }
382
383    /// Orders hole start nodes by `x`, then `y`, then the slope of their
384    /// first edge. A total order, so that sorting cannot panic.
385    fn compare_xy_slope(&self, a: usize, b: usize) -> Ordering {
386        let slope = |k: usize| {
387            let (p, q) = (self.n(k), self.n(self.next(k)));
388            (q.y - p.y) / (q.x - p.x)
389        };
390        let (pa, pb) = (self.n(a), self.n(b));
391        pa.x.total_cmp(&pb.x)
392            .then(pa.y.total_cmp(&pb.y))
393            .then_with(|| slope(a).total_cmp(&slope(b)))
394    }
395
396    /// Removes repeated and collinear vertices from `start` around to `end`
397    /// (the whole ring when `end` is `None`), returning a node still in it.
398    fn filter_points(&mut self, start: usize, end: Option<usize>) -> usize {
399        let start = self.live(start);
400        let mut end = end.map_or(start, |e| self.live(e));
401        let mut p = start;
402        loop {
403            let (prev, next) = (self.prev(p), self.next(p));
404            let again = if self.same(p, next) || self.turn(prev, p, next) == 0.0 {
405                self.remove(p);
406                p = prev;
407                end = prev;
408                if p == self.next(p) {
409                    break;
410                }
411                true
412            } else {
413                p = next;
414                false
415            };
416            if !again && p == end {
417                break;
418            }
419        }
420        end
421    }
422
423    /// Whether the vertex `ear` and its neighbours form a triangle that can
424    /// be cut off: convex, with no reflex vertex of the ring inside it.
425    fn is_ear(&self, ear: usize) -> bool {
426        let (a, b, c) = (self.prev(ear), ear, self.next(ear));
427        if self.turn(a, b, c) >= 0.0 {
428            return false;
429        }
430        let (na, nb, nc) = (self.n(a), self.n(b), self.n(c));
431        let (x0, x1) = (na.x.min(nb.x).min(nc.x), na.x.max(nb.x).max(nc.x));
432        let (y0, y1) = (na.y.min(nb.y).min(nc.y), na.y.max(nb.y).max(nc.y));
433        let mut p = self.next(c);
434        while p != a {
435            let np = self.n(p);
436            if np.x >= x0 && np.x <= x1 && np.y >= y0 && np.y <= y1 && self.blocks(p, a, b, c) {
437                return false;
438            }
439            p = self.next(p);
440        }
441        true
442    }
443
444    /// Whether `p`, a node in the box of the triangle `abc`, stops it being
445    /// an ear: a reflex (or straight) vertex inside or on it, other than at `a`.
446    fn blocks(&self, p: usize, a: usize, b: usize, c: usize) -> bool {
447        let (na, nb, nc, np) = (self.n(a), self.n(b), self.n(c), self.n(p));
448        !equals(na, np)
449            && point_in_triangle(na.x, na.y, nb.x, nb.y, nc.x, nc.y, np.x, np.y)
450            && self.turn(self.prev(p), p, self.next(p)) >= 0.0
451    }
452
453    /// [`Earcut::is_ear`] through the z-order list. Only nodes whose codes
454    /// lie between those of the triangle box's corners can be in the box, and
455    /// they are found by walking the list both ways from `ear`. It tests the
456    /// same nodes as `is_ear` (plus `ear` itself, which is convex and so
457    /// never blocks), so it gives the same answer.
458    fn is_ear_hashed(&self, ear: usize, grid: &ZGrid) -> bool {
459        let (a, b, c) = (self.prev(ear), ear, self.next(ear));
460        if self.turn(a, b, c) >= 0.0 {
461            return false;
462        }
463        let (na, nb, nc) = (self.n(a), self.n(b), self.n(c));
464        let (x0, x1) = (na.x.min(nb.x).min(nc.x), na.x.max(nb.x).max(nc.x));
465        let (y0, y1) = (na.y.min(nb.y).min(nc.y), na.y.max(nb.y).max(nc.y));
466        let (min_z, max_z) = (z_order(x0, y0, grid), z_order(x1, y1, grid));
467        let stops = |p: usize| {
468            let np = self.n(p);
469            p != a
470                && p != c
471                && np.x >= x0
472                && np.x <= x1
473                && np.y >= y0
474                && np.y <= y1
475                && self.blocks(p, a, b, c)
476        };
477        // The list is finite and acyclic, so both walks end.
478        let mut p = self.nodes[ear].prev_z;
479        while let Some(k) = p.filter(|&k| self.n(k).z >= min_z) {
480            if stops(k) {
481                return false;
482            }
483            p = self.nodes[k].prev_z;
484        }
485        let mut n = self.nodes[ear].next_z;
486        while let Some(k) = n.filter(|&k| self.n(k).z <= max_z) {
487            if stops(k) {
488                return false;
489            }
490            n = self.nodes[k].next_z;
491        }
492        true
493    }
494
495    /// Codes every node of the ring through `start` and links them into a
496    /// list sorted by code, used from then on by the ear test. Does nothing
497    /// when the ring's box has no finite, non-zero size.
498    ///
499    /// Nodes are only removed after this (bridging is done), and removal
500    /// unlinks them from the list too, so the list stays exactly the ring.
501    fn index_curve(&mut self, start: usize) {
502        let (mut x0, mut y0) = (f64::INFINITY, f64::INFINITY);
503        let (mut x1, mut y1) = (f64::NEG_INFINITY, f64::NEG_INFINITY);
504        let mut p = start;
505        loop {
506            let np = self.n(p);
507            (x0, y0) = (x0.min(np.x), y0.min(np.y));
508            (x1, y1) = (x1.max(np.x), y1.max(np.y));
509            p = self.next(p);
510            if p == start {
511                break;
512            }
513        }
514        let size = (x1 - x0).max(y1 - y0);
515        if !(size.is_finite() && size > 0.0) {
516            return;
517        }
518        let grid = ZGrid {
519            min_x: x0,
520            min_y: y0,
521            inv_size: 32767.0 / size,
522        };
523        loop {
524            let (prev, next) = (self.prev(p), self.next(p));
525            let z = z_order(self.n(p).x, self.n(p).y, &grid);
526            let node = &mut self.nodes[p];
527            node.z = z;
528            node.prev_z = (p != start).then_some(prev);
529            node.next_z = (next != start).then_some(next);
530            p = next;
531            if p == start {
532                break;
533            }
534        }
535        self.sort_z(start);
536        self.grid = Some(grid);
537    }
538
539    /// Sorts the z-order list that starts at `head` by code, in place and
540    /// without allocating: Simon Tatham's bottom-up merge sort of a linked
541    /// list, as in earcut.
542    fn sort_z(&mut self, head: usize) {
543        let mut list = Some(head);
544        let mut in_size = 1usize;
545        loop {
546            let mut p = list;
547            list = None;
548            let mut tail: Option<usize> = None;
549            let mut merges = 0;
550            while let Some(run) = p {
551                merges += 1;
552                // Merge the run of up to `in_size` nodes at `p` with the
553                // run of up to `in_size` nodes after it, at `q`.
554                let mut q = Some(run);
555                let mut p_size = 0usize;
556                while let Some(k) = q {
557                    if p_size == in_size {
558                        break;
559                    }
560                    p_size += 1;
561                    q = self.nodes[k].next_z;
562                }
563                let mut q_size = in_size;
564                loop {
565                    let q_open = q_size > 0 && q.is_some();
566                    if p_size == 0 && !q_open {
567                        break;
568                    }
569                    let from_p = p_size > 0
570                        && (!q_open
571                            || match (p, q) {
572                                (Some(pk), Some(qk)) => self.n(pk).z <= self.n(qk).z,
573                                _ => true,
574                            });
575                    let e = if from_p {
576                        p_size -= 1;
577                        p
578                    } else {
579                        q_size -= 1;
580                        q
581                    };
582                    // `p` holds `p_size` more nodes, so this always matches.
583                    let Some(e) = e else { break };
584                    if from_p {
585                        p = self.nodes[e].next_z;
586                    } else {
587                        q = self.nodes[e].next_z;
588                    }
589                    match tail {
590                        Some(t) => self.nodes[t].next_z = Some(e),
591                        None => list = Some(e),
592                    }
593                    self.nodes[e].prev_z = tail;
594                    tail = Some(e);
595                }
596                p = q;
597            }
598            if let Some(t) = tail {
599                self.nodes[t].next_z = None;
600            }
601            if merges <= 1 {
602                return;
603            }
604            in_size = in_size.saturating_mul(2);
605        }
606    }
607
608    fn emit(&mut self, a: usize, b: usize, c: usize) {
609        // Room for every triangle was reserved up front.
610        self.out.push([self.n(a).i, self.n(b).i, self.n(c).i]);
611    }
612
613    /// Clips ears from the ring through `ear` until two vertices remain.
614    /// When a full turn finds no ear it first drops repeated and collinear
615    /// vertices, then cures local self-intersections, then gives up and
616    /// returns false.
617    fn clip(&mut self, mut ear: usize) -> bool {
618        let mut pass = 0;
619        let mut stop = ear;
620        while self.prev(ear) != self.next(ear) {
621            let (prev, next) = (self.prev(ear), self.next(ear));
622            let is_ear = match self.grid {
623                Some(grid) => self.is_ear_hashed(ear, &grid),
624                None => self.is_ear(ear),
625            };
626            if is_ear {
627                self.emit(prev, ear, next);
628                self.remove(ear);
629                ear = self.next(next);
630                stop = ear;
631                continue;
632            }
633            ear = next;
634            if ear == stop {
635                ear = match pass {
636                    0 => self.filter_points(ear, None),
637                    1 => {
638                        let start = self.filter_points(ear, None);
639                        self.cure_local_intersections(start)
640                    }
641                    _ => return false,
642                };
643                pass += 1;
644                stop = ear;
645            }
646        }
647        true
648    }
649
650    /// Cuts off a triangle wherever the edges before and after a vertex pair
651    /// cross (`a p` and `p.next b`), which removes that local twist.
652    fn cure_local_intersections(&mut self, start: usize) -> usize {
653        let (mut start, mut p) = (start, start);
654        loop {
655            let a = self.prev(p);
656            let pn = self.next(p);
657            let b = self.next(pn);
658            // Needs four distinct vertices `a p pn b`.
659            if b != a
660                && b != p
661                && !self.same(a, b)
662                && intersects(self.n(a), self.n(p), self.n(pn), self.n(b))
663                && self.locally_inside(a, b)
664                && self.locally_inside(b, a)
665            {
666                #[cfg(test)]
667                CURES.with(|c| c.set(c.get() + 1));
668                self.emit(a, p, b);
669                self.remove(p);
670                self.remove(pn);
671                p = b;
672                start = b;
673            }
674            p = self.next(p);
675            if p == start {
676                break;
677            }
678        }
679        self.filter_points(p, None)
680    }
681
682    /// Whether the diagonal from `a` to `b` starts into the polygon's
683    /// interior at `a`.
684    fn locally_inside(&self, a: usize, b: usize) -> bool {
685        let (ap, an) = (self.prev(a), self.next(a));
686        if self.turn(ap, a, an) < 0.0 {
687            self.turn(a, b, an) >= 0.0 && self.turn(a, ap, b) >= 0.0
688        } else {
689            self.turn(a, b, ap) < 0.0 || self.turn(a, an, b) < 0.0
690        }
691    }
692
693    /// Whether the sector of `m` contains the sector of `p` (for two
694    /// coincident bridge candidates).
695    fn sector_contains_sector(&self, m: usize, p: usize) -> bool {
696        self.turn(self.prev(m), m, self.prev(p)) < 0.0
697            && self.turn(self.next(p), m, self.prev(m)) < 0.0
698    }
699
700    /// Joins the hole ring at `hole` (its leftmost node) into the ring
701    /// through `outer` and returns a node of the joined ring. A hole with no
702    /// visible outer vertex is left out and recorded in `ok`.
703    fn eliminate_hole(&mut self, hole: usize, outer: usize) -> usize {
704        let outer = self.live(outer);
705        let Some(bridge) = self.find_hole_bridge(hole, outer) else {
706            self.ok = false;
707            return outer;
708        };
709        let reverse = self.split(bridge, hole);
710        let after = self.next(reverse);
711        self.filter_points(reverse, Some(after));
712        let bridge = self.live(bridge);
713        let after = self.next(bridge);
714        self.filter_points(bridge, Some(after))
715    }
716
717    /// The outer vertex to bridge `hole` to: cast a ray left from the hole's
718    /// leftmost point, take the nearest edge it hits, then the visible vertex
719    /// in the triangle it spans with the smallest angle to the ray.
720    fn find_hole_bridge(&self, hole: usize, outer: usize) -> Option<usize> {
721        let (hx, hy) = (self.n(hole).x, self.n(hole).y);
722        let mut qx = f64::NEG_INFINITY;
723        let mut m = None;
724        let mut p = outer;
725        if self.same(hole, p) {
726            return Some(p);
727        }
728        loop {
729            let pn = self.next(p);
730            if self.same(hole, pn) {
731                return Some(pn);
732            }
733            let (a, b) = (self.n(p), self.n(pn));
734            if hy <= a.y && hy >= b.y && b.y != a.y {
735                let x = a.x + (hy - a.y) * (b.x - a.x) / (b.y - a.y);
736                if x <= hx && x > qx {
737                    qx = x;
738                    let near = if a.x < b.x { p } else { pn };
739                    m = Some(near);
740                    if x == hx {
741                        return m;
742                    }
743                }
744            }
745            p = pn;
746            if p == outer {
747                break;
748            }
749        }
750        let mut m = m?;
751        let stop = m;
752        let (mx, my) = (self.n(m).x, self.n(m).y);
753        let mut tan_min = f64::INFINITY;
754        p = m;
755        loop {
756            let np = self.n(p);
757            let (lx, rx) = if hy < my { (hx, qx) } else { (qx, hx) };
758            if hx >= np.x
759                && np.x >= mx
760                && hx != np.x
761                && point_in_triangle(lx, hy, mx, my, rx, hy, np.x, np.y)
762            {
763                let tan = (hy - np.y).abs() / (hx - np.x);
764                let nm = self.n(m);
765                if self.locally_inside(p, hole)
766                    && (tan < tan_min
767                        || (tan == tan_min
768                            && (np.x > nm.x
769                                || (np.x == nm.x && self.sector_contains_sector(m, p)))))
770                {
771                    m = p;
772                    tan_min = tan;
773                }
774            }
775            p = self.next(p);
776            if p == stop {
777                break;
778            }
779        }
780        Some(m)
781    }
782
783    /// Links `a` to `b` with a pair of coincident edges, splitting the ring
784    /// in two if they are on one ring or joining two rings into one. Returns
785    /// the copy of `b`.
786    fn split(&mut self, a: usize, b: usize) -> usize {
787        let (an, bp) = (self.next(a), self.prev(b));
788        let (na, nb) = (*self.n(a), *self.n(b));
789        let a2 = self.nodes.len();
790        let b2 = a2 + 1;
791        // Capacity for two copies per hole was reserved up front.
792        self.nodes.push(Node {
793            next: an,
794            prev: b2,
795            ..na
796        });
797        self.nodes.push(Node {
798            next: a2,
799            prev: bp,
800            ..nb
801        });
802        self.nodes[a].next = b;
803        self.nodes[b].prev = a;
804        self.nodes[an].prev = a2;
805        self.nodes[bp].next = b2;
806        b2
807    }
808}
809
810#[cfg(test)]
811mod tests {
812    use super::*;
813    use crate::{QueryError, fail_allocations_after};
814    use proptest::prelude::*;
815
816    /// The points of `outer` then of each hole, as numbered by [`triangulate`].
817    fn all_points(outer: &[[f64; 2]], holes: &[&[[f64; 2]]]) -> Vec<[f64; 2]> {
818        let mut pts = outer.to_vec();
819        for h in holes {
820            pts.extend_from_slice(h);
821        }
822        pts
823    }
824
825    fn signed_area(pts: &[[f64; 2]], t: [u32; 3]) -> f64 {
826        let [a, b, c] = t.map(|i| pts[i as usize]);
827        0.5 * ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]))
828    }
829
830    fn shoelace(ring: &[[f64; 2]]) -> f64 {
831        let n = ring.len();
832        (0..n)
833            .map(|i| {
834                let (p, q) = (ring[i], ring[(i + 1) % n]);
835                p[0] * q[1] - q[0] * p[1]
836            })
837            .sum::<f64>()
838            * 0.5
839    }
840
841    /// Triangulates, checks that it finished with counter-clockwise triangles
842    /// and returns them with their total area.
843    fn fill(outer: &[[f64; 2]], holes: &[&[[f64; 2]]]) -> (Vec<[u32; 3]>, f64) {
844        let mut out = Vec::new();
845        assert_eq!(triangulate(outer, holes, &mut out), Ok(true));
846        let pts = all_points(outer, holes);
847        let mut area = 0.0;
848        for &t in &out {
849            let a = signed_area(&pts, t);
850            assert!(a > 0.0, "triangle {t:?} has area {a}");
851            area += a;
852        }
853        (out, area)
854    }
855
856    fn square(x: f64, y: f64, s: f64) -> Vec<[f64; 2]> {
857        vec![[x, y], [x + s, y], [x + s, y + s], [x, y + s]]
858    }
859
860    fn inside(ring: &[[f64; 2]], p: [f64; 2]) -> bool {
861        let mut c = false;
862        for i in 0..ring.len() {
863            let (a, b) = (ring[i], ring[(i + 1) % ring.len()]);
864            if (a[1] > p[1]) != (b[1] > p[1])
865                && p[0] < a[0] + (p[1] - a[1]) * (b[0] - a[0]) / (b[1] - a[1])
866            {
867                c = !c;
868            }
869        }
870        c
871    }
872
873    #[test]
874    fn square_gives_two_triangles() {
875        let (tris, area) = fill(&square(0.0, 0.0, 1.0), &[]);
876        assert_eq!(tris.len(), 2);
877        assert!((area - 1.0).abs() < 1e-12);
878    }
879
880    #[test]
881    fn concave_l_shape_area_exact() {
882        let l = [
883            [0.0, 0.0],
884            [2.0, 0.0],
885            [2.0, 1.0],
886            [1.0, 1.0],
887            [1.0, 2.0],
888            [0.0, 2.0],
889        ];
890        let (tris, area) = fill(&l, &[]);
891        assert_eq!(tris.len(), 4);
892        assert!((area - 3.0).abs() < 1e-12, "area {area}");
893    }
894
895    #[test]
896    fn square_with_square_hole() {
897        let hole = square(1.0, 1.0, 2.0);
898        let (_, area) = fill(&square(0.0, 0.0, 4.0), &[&hole]);
899        assert!((area - 12.0).abs() < 1e-12, "area {area}");
900    }
901
902    #[test]
903    fn two_holes() {
904        let outer = square(0.0, 0.0, 10.0);
905        let h1 = square(1.0, 1.0, 3.0);
906        let h2 = square(6.0, 5.0, 2.0);
907        let (tris, area) = fill(&outer, &[&h1, &h2]);
908        assert!((area - (100.0 - 9.0 - 4.0)).abs() < 1e-12, "area {area}");
909        let pts = all_points(&outer, &[&h1, &h2]);
910        for t in tris {
911            let [a, b, c] = t.map(|i| pts[i as usize]);
912            let g = [(a[0] + b[0] + c[0]) / 3.0, (a[1] + b[1] + c[1]) / 3.0];
913            assert!(
914                !inside(&h1, g) && !inside(&h2, g),
915                "centroid {g:?} in a hole"
916            );
917        }
918    }
919
920    #[test]
921    fn duplicate_consecutive_points_ignored() {
922        // Duplicates in the middle and across the wrap (last equals first).
923        let outer = [
924            [0.0, 0.0],
925            [0.0, 0.0],
926            [2.0, 0.0],
927            [2.0, 2.0],
928            [2.0, 2.0],
929            [2.0, 2.0],
930            [0.0, 2.0],
931            [0.0, 0.0],
932        ];
933        let hole = [[0.5, 0.5], [0.5, 1.0], [0.5, 1.0], [1.0, 1.0], [1.0, 0.5]];
934        let (tris, area) = fill(&outer, &[&hole]);
935        assert!((area - 3.75).abs() < 1e-12, "area {area}");
936        // The duplicates: outer 1, 4, 5, 7 (equal to 0) and hole 2 (8 + 2).
937        for t in tris {
938            for i in t {
939                assert!(![1, 4, 5, 7, 10].contains(&i), "duplicate {i} in {t:?}");
940            }
941        }
942    }
943
944    #[test]
945    fn collinear_points_ok() {
946        let outer = [
947            [0.0, 0.0],
948            [1.0, 0.0],
949            [2.0, 0.0],
950            [3.0, 0.0],
951            [3.0, 1.0],
952            [3.0, 2.0],
953            [1.5, 2.0],
954            [0.0, 2.0],
955            [0.0, 1.0],
956        ];
957        let (_, area) = fill(&outer, &[]);
958        assert!((area - 6.0).abs() < 1e-12, "area {area}");
959    }
960
961    #[test]
962    fn cw_input_is_normalised() {
963        let mut outer = square(0.0, 0.0, 4.0);
964        outer.reverse();
965        // A counter-clockwise hole, also to be normalised.
966        let hole = square(1.0, 1.0, 1.0);
967        let (_, area) = fill(&outer, &[&hole]);
968        assert!((area - 15.0).abs() < 1e-12, "area {area}");
969    }
970
971    #[test]
972    fn star_polygon_100_vertices_area() {
973        let star: Vec<[f64; 2]> = (0..100)
974            .map(|i| {
975                let a = i as f64 * std::f64::consts::TAU / 100.0;
976                let r = if i % 2 == 0 { 10.0 } else { 4.0 };
977                [r * a.cos(), r * a.sin()]
978            })
979            .collect();
980        let expected = shoelace(&star);
981        let (tris, area) = fill(&star, &[]);
982        assert_eq!(tris.len(), 98);
983        assert!(
984            ((area - expected) / expected).abs() < 1e-9,
985            "{area} vs {expected}"
986        );
987    }
988
989    #[test]
990    fn z_order_and_plain_ear_tests_agree_on_1000_points() {
991        // A wavy ring, concave in many places, with two holes.
992        let outer: Vec<[f64; 2]> = (0..1000)
993            .map(|i| {
994                let a = i as f64 * std::f64::consts::TAU / 1000.0;
995                let r = 10.0 + 2.0 * (17.0 * a).sin() + 0.5 * (61.0 * a).cos();
996                [r * a.cos(), r * a.sin()]
997            })
998            .collect();
999        let h1 = square(-3.0, -1.0, 2.0);
1000        let h2 = square(2.0, 1.0, 1.5);
1001        let holes: [&[[f64; 2]]; 2] = [&h1, &h2];
1002        let pts = all_points(&outer, &holes);
1003        let expected = shoelace(&outer) - 4.0 - 2.25;
1004        let mut areas = Vec::new();
1005        for z_order in [false, true] {
1006            let mut out = Vec::new();
1007            assert_eq!(
1008                triangulate_with(&outer, &holes, &mut out, z_order),
1009                Ok(true)
1010            );
1011            let area: f64 = out.iter().map(|&t| signed_area(&pts, t)).sum();
1012            assert!(
1013                ((area - expected) / expected).abs() < 1e-9,
1014                "{area} vs {expected}"
1015            );
1016            areas.push((area, out));
1017        }
1018        assert_eq!(areas[0], areas[1]);
1019    }
1020
1021    #[test]
1022    fn far_from_origin_orientation() {
1023        // Far from the origin, where a shoelace about the origin rounds to
1024        // 0 and the ring used to be taken as clockwise.
1025        let (_, area) = fill(&square(1e9, 1e9, 1e-3), &[]);
1026        assert!((area - 1e-6).abs() < 1e-9, "area {area}");
1027    }
1028
1029    #[test]
1030    fn degenerate_outer_has_nothing_to_fill() {
1031        let mut out = vec![[9, 9, 9]];
1032        assert_eq!(triangulate(&[], &[], &mut out), Ok(true));
1033        assert_eq!(triangulate(&[[1.0, 1.0]; 5], &[], &mut out), Ok(true));
1034        assert_eq!(
1035            triangulate(&[[0.0, 0.0], [1.0, 0.0], [0.0, 0.0]], &[], &mut out),
1036            Ok(true)
1037        );
1038        // Collinear: zero area, nothing to fill.
1039        let line = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0], [1.0, 0.0]];
1040        assert_eq!(triangulate(&line, &[], &mut out), Ok(true));
1041        assert_eq!(out, vec![[9, 9, 9]]);
1042    }
1043
1044    #[test]
1045    fn degenerate_holes_are_ignored() {
1046        let outer = square(0.0, 0.0, 2.0);
1047        let (_, area) = fill(&outer, &[&[], &[[1.0, 1.0]], &[[0.5, 0.5], [1.0, 1.0]]]);
1048        assert!((area - 4.0).abs() < 1e-12, "area {area}");
1049    }
1050
1051    #[test]
1052    fn non_finite_input_returns_false() {
1053        let mut out = vec![[9, 9, 9]];
1054        let mut bad = square(0.0, 0.0, 1.0);
1055        bad[2][0] = f64::NAN;
1056        assert_eq!(triangulate(&bad, &[], &mut out), Ok(false));
1057        bad[2][0] = f64::INFINITY;
1058        assert_eq!(triangulate(&bad, &[], &mut out), Ok(false));
1059        let hole = [[0.2, 0.2], [0.2, f64::NEG_INFINITY], [0.5, 0.5]];
1060        assert_eq!(
1061            triangulate(&square(0.0, 0.0, 1.0), &[&hole], &mut out),
1062            Ok(false)
1063        );
1064        assert_eq!(out, vec![[9, 9, 9]]);
1065    }
1066
1067    #[test]
1068    fn appends_after_existing_triangles() {
1069        let mut out = vec![[9, 9, 9]];
1070        assert_eq!(triangulate(&square(0.0, 0.0, 1.0), &[], &mut out), Ok(true));
1071        assert_eq!(out.len(), 3);
1072        assert_eq!(out[0], [9, 9, 9]);
1073    }
1074
1075    #[test]
1076    fn out_of_memory_leaves_out_unchanged() {
1077        let outer = square(0.0, 0.0, 4.0);
1078        let hole = square(1.0, 1.0, 2.0);
1079        // Every fallible reservation the call makes, failing in turn.
1080        for n in 0..3 {
1081            let mut out = vec![[9, 9, 9]];
1082            fail_allocations_after(Some(n));
1083            let r = triangulate(&outer, &[&hole], &mut out);
1084            fail_allocations_after(None);
1085            assert_eq!(r, Err(QueryError::OutOfMemory), "reservation {n}");
1086            assert_eq!(out, vec![[9, 9, 9]]);
1087        }
1088        let mut out = Vec::new();
1089        fail_allocations_after(Some(3));
1090        let r = triangulate(&outer, &[&hole], &mut out);
1091        fail_allocations_after(None);
1092        assert_eq!(r, Ok(true));
1093    }
1094
1095    /// Proptest cases: `default`, or `PROPTEST_CASES` when set.
1096    fn cases(default: u32) -> u32 {
1097        std::env::var("PROPTEST_CASES")
1098            .ok()
1099            .and_then(|s| s.parse().ok())
1100            .unwrap_or(default)
1101    }
1102
1103    fn ring(max: usize) -> impl Strategy<Value = Vec<[f64; 2]>> {
1104        // A small grid, so that duplicates, collinear runs and crossings are common.
1105        prop::collection::vec(
1106            (-4i32..=4, -4i32..=4).prop_map(|(x, y)| [f64::from(x), f64::from(y)]),
1107            0..=max,
1108        )
1109    }
1110
1111    #[test]
1112    fn cure_path_repairs_self_touching_ring() {
1113        // Two triangles sharing only (0, 0), crossed at (-1, 1/2): no ear
1114        // exists until the cure cuts the twist, and then both lobes fill.
1115        let ring = [
1116            [0.0, 0.0],
1117            [-2.0, 0.0],
1118            [0.0, 1.0],
1119            [0.0, 0.0],
1120            [1.0, 1.0],
1121            [-2.0, 1.0],
1122        ];
1123        CURES.with(|c| c.set(0));
1124        let (tris, area) = fill(&ring, &[]);
1125        assert!(CURES.with(|c| c.get()) > 0);
1126        assert!((area - 2.5).abs() < 1e-12, "area {area}");
1127        assert_eq!(tris.len(), 2);
1128    }
1129
1130    proptest! {
1131        #![proptest_config(ProptestConfig::with_cases(cases(512)))]
1132
1133        #[test]
1134        fn proptest_never_panics(
1135            outer in ring(64),
1136            holes in prop::collection::vec(ring(16), 0..4),
1137            scale in prop::sample::select(vec![1.0, 1e-3, 1e150]),
1138        ) {
1139            let outer: Vec<[f64; 2]> = outer.iter().map(|p| p.map(|c| c * scale)).collect();
1140            let holes: Vec<Vec<[f64; 2]>> = holes
1141                .iter()
1142                .map(|h| h.iter().map(|p| p.map(|c| c * scale)).collect())
1143                .collect();
1144            let hole_refs: Vec<&[[f64; 2]]> = holes.iter().map(Vec::as_slice).collect();
1145            let total = outer.len() + holes.iter().map(Vec::len).sum::<usize>();
1146            let mut out = Vec::new();
1147            prop_assert!(triangulate(&outer, &hole_refs, &mut out).is_ok());
1148            for t in &out {
1149                prop_assert!(t.iter().all(|&i| (i as usize) < total), "{:?}", t);
1150            }
1151            // Both ear tests, whatever the size, give the same result.
1152            let mut plain = Vec::new();
1153            let mut hashed = Vec::new();
1154            let p = triangulate_with(&outer, &hole_refs, &mut plain, false);
1155            let h = triangulate_with(&outer, &hole_refs, &mut hashed, true);
1156            prop_assert_eq!(p, h);
1157            prop_assert_eq!(plain, hashed);
1158        }
1159
1160        /// A random star-shaped polygon of up to 600 points with a small
1161        /// square hole near its centre, filled through the z-order path:
1162        /// the triangles' areas sum to the polygon's.
1163        #[test]
1164        fn large_star_shaped_with_hole_fills_exactly(
1165            radii in prop::collection::vec(2.0f64..10.0, 81..600),
1166            hole_size in 0.1f64..1.0,
1167            centre in (-0.4f64..0.4, -0.4f64..0.4),
1168            offset in (-1e3f64..1e3, -1e3f64..1e3),
1169        ) {
1170            let n = radii.len();
1171            let outer: Vec<[f64; 2]> = radii
1172                .iter()
1173                .enumerate()
1174                .map(|(i, r)| {
1175                    let a = i as f64 * std::f64::consts::TAU / n as f64;
1176                    [offset.0 + r * a.cos(), offset.1 + r * a.sin()]
1177                })
1178                .collect();
1179            let h = hole_size / 2.0;
1180            // Every edge stays more than 1.6 from the centre (see above), and
1181            // the hole's corners are within 0.71 + 0.57 of it.
1182            let hole = square(offset.0 + centre.0 - h, offset.1 + centre.1 - h, hole_size);
1183            let mut out = Vec::new();
1184            prop_assert_eq!(triangulate(&outer, &[&hole], &mut out), Ok(true));
1185            let pts = all_points(&outer, &[&hole]);
1186            let area: f64 = out.iter().map(|&t| signed_area(&pts, t)).sum();
1187            let expected = shoelace(&outer) - hole_size * hole_size;
1188            prop_assert!(((area - expected) / expected).abs() < 1e-9, "{} vs {}", area, expected);
1189        }
1190
1191        #[test]
1192        fn convex_polygons_fill_exactly(n in 3usize..40, r in 0.1f64..100.0) {
1193            let poly: Vec<[f64; 2]> = (0..n)
1194                .map(|i| {
1195                    let a = i as f64 * std::f64::consts::TAU / n as f64;
1196                    [r * a.cos(), r * a.sin()]
1197                })
1198                .collect();
1199            let mut out = Vec::new();
1200            prop_assert_eq!(triangulate(&poly, &[], &mut out), Ok(true));
1201            let area: f64 = out.iter().map(|&t| signed_area(&poly, t)).sum();
1202            let expected = shoelace(&poly);
1203            prop_assert!(((area - expected) / expected).abs() < 1e-9);
1204        }
1205
1206        /// A random star-shaped (so simple) polygon around a square hole
1207        /// well inside it: the fill finishes with the exact area.
1208        #[test]
1209        fn star_shaped_with_hole_fills_exactly(
1210            radii in prop::collection::vec(2.0f64..10.0, 8..80),
1211            hole_size in 0.1f64..1.0,
1212        ) {
1213            let n = radii.len();
1214            let outer: Vec<[f64; 2]> = radii
1215                .iter()
1216                .enumerate()
1217                .map(|(i, r)| {
1218                    let a = i as f64 * std::f64::consts::TAU / n as f64;
1219                    [r * a.cos(), r * a.sin()]
1220                })
1221                .collect();
1222            let h = hole_size / 2.0;
1223            let hole = square(-h, -h, hole_size);
1224            // With at least 8 spokes of radius 2 to 10 every edge stays more
1225            // than 1.6 from the centre, clear of the hole's corners (< 0.71).
1226            let mut out = Vec::new();
1227            prop_assert_eq!(triangulate(&outer, &[&hole], &mut out), Ok(true));
1228            let pts = all_points(&outer, &[&hole]);
1229            let mut area = 0.0;
1230            for &t in &out {
1231                let a = signed_area(&pts, t);
1232                prop_assert!(a > 0.0);
1233                area += a;
1234            }
1235            let expected = shoelace(&outer) - hole_size * hole_size;
1236            prop_assert!(((area - expected) / expected).abs() < 1e-9, "{} vs {}", area, expected);
1237        }
1238    }
1239}