1use std::cmp::Ordering;
14
15use crate::{QueryError, Result, allocation_allowed};
16
17pub 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
57const Z_ORDER_MIN: usize = 80;
60
61fn 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 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 if e.next(h) != e.prev(h) {
105 queue.push(e.leftmost(h));
106 }
107 }
108 offset += hole.len();
109 }
110 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
124fn reserve<T>(v: &mut Vec<T>, n: usize) -> Result<()> {
126 allocation_allowed()?;
127 v.try_reserve(n).map_err(|_| QueryError::OutOfMemory)
128}
129
130#[derive(Debug, Clone, Copy)]
132struct Node {
133 i: u32,
134 x: f64,
135 y: f64,
136 prev: usize,
137 next: usize,
138 z: u32,
140 prev_z: Option<usize>,
142 next_z: Option<usize>,
143 removed: bool,
146}
147
148fn 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
155fn z_order(x: f64, y: f64, grid: &ZGrid) -> u32 {
159 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#[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
201fn 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
206fn 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 static CURES: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
224}
225
226struct Earcut<'a> {
232 nodes: Vec<Node>,
233 out: &'a mut Vec<[u32; 3]>,
234 ok: bool,
236 grid: Option<ZGrid>,
238}
239
240#[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 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 fn insert(&mut self, i: u32, p: [f64; 2], last: Option<usize>) -> usize {
272 let k = self.nodes.len();
273 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 self.out.push([self.n(a).i, self.n(b).i, self.n(c).i]);
611 }
612
613 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 #[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 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 #[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 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}