1use crate::triangulate::triangulate;
9use crate::tritri::{orient2d_approx, side2};
10use crate::{QueryError, Result, allocation_allowed};
11
12type P2 = [f64; 2];
13
14type Seg = [P2; 2];
16
17#[derive(Debug, Clone, Default, PartialEq)]
19pub struct Fill {
20 pub points: Vec<[f64; 2]>,
23 pub triangles: Vec<[u32; 3]>,
25}
26
27#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct Gap2 {
30 pub distance: f64,
32 pub a: [f64; 2],
34 pub b: [f64; 2],
36}
37
38pub fn contour_length(points: &[[f64; 2]], closed: bool) -> f64 {
41 let open: f64 = points.windows(2).map(|w| dist(w[0], w[1])).sum();
42 match points {
43 [first, .., last] if closed => open + dist(*last, *first),
44 _ => open,
45 }
46}
47
48pub fn signed_area(points: &[[f64; 2]]) -> f64 {
52 let Some(&o) = points.first() else {
53 return 0.0;
54 };
55 let twice: f64 = points
58 .windows(2)
59 .skip(1)
60 .map(|w| cross(sub(w[0], o), sub(w[1], o)))
61 .sum();
62 0.5 * twice
63}
64
65pub fn fill(contours: &[(&[[f64; 2]], bool)], out: &mut Fill) -> Result<bool> {
96 out.points.clear();
97 out.triangles.clear();
98 let r = fill_into(contours, out);
99 if r.is_err() {
100 out.points.clear();
101 out.triangles.clear();
102 }
103 r
104}
105
106#[derive(Debug, Clone, Copy, PartialEq)]
108enum Role {
109 Skip,
111 Outer,
112 Hole(usize),
114}
115
116struct Info {
118 area: f64,
119 lo: P2,
120 hi: P2,
121}
122
123fn fill_into(contours: &[(&[[f64; 2]], bool)], out: &mut Fill) -> Result<bool> {
124 let total = contours
125 .iter()
126 .try_fold(0usize, |n, c| n.checked_add(c.0.len()))
127 .filter(|&n| n <= u32::MAX as usize)
128 .ok_or(QueryError::InvalidInput("too many contour points"))?;
129 let n = contours.len();
130 let mut info = Vec::new();
131 reserve(&mut info, n)?;
132 for &(pts, _) in contours {
133 let (lo, hi) = bounds(pts);
134 info.push(Info {
135 area: signed_area(pts),
136 lo,
137 hi,
138 });
139 }
140 let holds = |j: usize, i: usize| {
142 let c = &info[j];
143 j != i
144 && c.area != 0.0
145 && contours[i]
146 .0
147 .first()
148 .is_some_and(|&p| in_box(p, c.lo, c.hi) && contains(contours[j].0, p))
149 };
150 let container = |i: usize, allowed: &dyn Fn(usize) -> bool| {
152 (0..n)
153 .filter(|&j| allowed(j) && holds(j, i))
154 .min_by(|&a, &b| info[a].area.abs().total_cmp(&info[b].area.abs()))
155 };
156
157 let mut roles = Vec::new();
158 reserve(&mut roles, n)?;
159 let mut expected = 0.0;
160 let mut scale = 0.0;
161 if contours.iter().all(|c| c.1) {
162 let sign = if info.iter().map(|c| c.area).sum::<f64>() < 0.0 {
166 -1.0
167 } else {
168 1.0
169 };
170 for (i, c) in info.iter().enumerate() {
171 let area = sign * c.area;
172 roles.push(if area > 0.0 {
173 Role::Outer
174 } else if area < 0.0 {
175 container(i, &|j| sign * info[j].area > 0.0).map_or(Role::Skip, Role::Hole)
176 } else {
177 Role::Skip
178 });
179 expected += area;
180 scale += area.abs();
181 }
182 } else {
183 let mut depth = Vec::new();
184 reserve(&mut depth, n)?;
185 for i in 0..n {
186 depth.push((0..n).filter(|&j| holds(j, i)).count());
187 }
188 for (i, c) in info.iter().enumerate() {
189 let a = c.area.abs();
190 roles.push(if c.area == 0.0 {
191 Role::Skip
192 } else if depth[i] % 2 == 0 {
193 expected += a;
194 Role::Outer
195 } else {
196 expected -= a;
197 container(i, &|j| depth[j] % 2 == 0).map_or(Role::Skip, Role::Hole)
198 });
199 scale += a;
200 }
201 }
202
203 reserve(&mut out.points, total)?;
204 let mut holes: Vec<&[[f64; 2]]> = Vec::new();
205 reserve(&mut holes, n)?;
206 let mut ok = true;
207 for (o, &(outer, _)) in contours.iter().enumerate() {
208 if roles[o] != Role::Outer {
209 continue;
210 }
211 holes.clear();
212 holes.extend(
213 (0..n)
214 .filter(|&i| roles[i] == Role::Hole(o))
215 .map(|i| contours[i].0),
216 );
217 let base = out.points.len() as u32;
219 out.points.extend_from_slice(outer);
220 for h in &holes {
221 out.points.extend_from_slice(h);
222 }
223 let start = out.triangles.len();
224 ok &= triangulate(outer, &holes, &mut out.triangles)?;
225 for t in &mut out.triangles[start..] {
226 *t = t.map(|k| k + base);
227 }
228 }
229
230 let area: f64 = out
231 .triangles
232 .iter()
233 .map(|t| {
234 let [a, b, c] = t.map(|k| out.points[k as usize]);
235 0.5 * cross(sub(b, a), sub(c, a))
236 })
237 .sum();
238 let matches = (area - expected).abs() <= 1e-6 * f64::max(1.0, scale);
240 Ok(ok && matches)
241}
242
243fn contains(poly: &[P2], p: P2) -> bool {
245 let mut inside = false;
246 let mut j = poly.len().wrapping_sub(1);
247 for (i, &a) in poly.iter().enumerate() {
248 let b = poly[j];
249 if (a[1] > p[1]) != (b[1] > p[1])
250 && p[0] < (b[0] - a[0]) * (p[1] - a[1]) / (b[1] - a[1]) + a[0]
251 {
252 inside = !inside;
253 }
254 j = i;
255 }
256 inside
257}
258
259fn bounds(pts: &[P2]) -> (P2, P2) {
261 let mut lo = [f64::INFINITY; 2];
262 let mut hi = [f64::NEG_INFINITY; 2];
263 for p in pts {
264 for k in 0..2 {
265 lo[k] = lo[k].min(p[k]);
266 hi[k] = hi[k].max(p[k]);
267 }
268 }
269 (lo, hi)
270}
271
272fn in_box(p: P2, lo: P2, hi: P2) -> bool {
273 (lo[0]..=hi[0]).contains(&p[0]) && (lo[1]..=hi[1]).contains(&p[1])
274}
275
276pub fn contour_gap(a: &[(&[[f64; 2]], bool)], b: &[(&[[f64; 2]], bool)]) -> Result<Option<Gap2>> {
300 let mut sa = Vec::new();
301 collect_segments(a, &mut sa)?;
302 let mut sb = Vec::new();
303 collect_segments(b, &mut sb)?;
304 if sa.is_empty() || sb.is_empty() {
305 return Ok(None);
306 }
307 let grid = Grid::build(&sb)?;
308 let to_b = |s: &Seg| {
313 let (l, h) = bounds(s);
314 box_gap(l, h, grid.lo, grid.hi)
315 };
316 sa.sort_unstable_by(|p, q| to_b(p).total_cmp(&to_b(q)));
317 let mut seen = Vec::new();
318 reserve(&mut seen, sb.len())?;
319 seen.resize(sb.len(), 0usize);
320 let mut best = segment_gap(sa[0], sb[0]);
321 for (k, &s) in sa.iter().enumerate() {
322 if best.0 == 0.0 || to_b(&s) >= best.0 {
323 break;
324 }
325 grid.search(s, &sb, &mut seen, k + 1, &mut best);
326 }
327 Ok(Some(Gap2 {
328 distance: best.0.sqrt(),
329 a: best.1,
330 b: best.2,
331 }))
332}
333
334fn collect_segments(lines: &[(&[[f64; 2]], bool)], out: &mut Vec<Seg>) -> Result<()> {
336 for &(pts, closed) in lines {
337 if !pts.iter().flatten().all(|c| c.is_finite()) {
338 return Err(QueryError::InvalidInput("non-finite contour point"));
339 }
340 let closing = match pts {
341 [first, .., last] if closed => Some([*last, *first]),
342 _ => None,
343 };
344 let count = pts.len().saturating_sub(1) + usize::from(closing.is_some());
345 reserve(out, count)?;
346 out.extend(pts.windows(2).map(|w| [w[0], w[1]]));
347 out.extend(closing);
348 }
349 Ok(())
350}
351
352fn segment_gap(p: Seg, q: Seg) -> (f64, P2, P2) {
356 #[cfg(test)]
357 PAIRS_TESTED.with(|c| c.set(c.get() + 1));
358 let [a, b] = p;
359 let [c, d] = q;
360 let (s1, s2) = (side2::<false>(a, b, c), side2::<false>(a, b, d));
361 let (s3, s4) = (side2::<false>(c, d, a), side2::<false>(c, d, b));
362 if s1 * s2 < 0 && s3 * s4 < 0 {
363 let (o3, o4) = (orient2d_approx(c, d, a), orient2d_approx(c, d, b));
365 let t = o3 / (o3 - o4);
366 let t = if t.is_finite() {
367 t.clamp(0.0, 1.0)
368 } else {
369 0.5
370 };
371 let x = lerp(a, b, t);
372 return (0.0, x, x);
373 }
374 for (s, pt, [e, f]) in [(s1, c, p), (s2, d, p), (s3, a, q), (s4, b, q)] {
376 let (lo, hi) = bounds(&[e, f]);
377 if s == 0 && in_box(pt, lo, hi) {
378 return (0.0, pt, pt);
379 }
380 }
381 let candidates = [
383 (closest_on(c, a, b), c),
384 (closest_on(d, a, b), d),
385 (a, closest_on(a, c, d)),
386 (b, closest_on(b, c, d)),
387 ];
388 let mut best = (f64::INFINITY, a, c);
389 for (x, y) in candidates {
390 let d2 = dist2(x, y);
391 if d2 < best.0 {
392 best = (d2, x, y);
393 }
394 }
395 best
396}
397
398#[cfg(test)]
399thread_local! {
400 static PAIRS_TESTED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
402 static RINGS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
404}
405
406fn closest_on(p: P2, a: P2, b: P2) -> P2 {
408 let ab = sub(b, a);
409 let len2 = dot(ab, ab);
410 if len2 == 0.0 {
411 return a;
412 }
413 let t = dot(sub(p, a), ab) / len2;
414 if t <= 0.0 {
415 a
416 } else if t >= 1.0 {
417 b
418 } else {
419 lerp(a, b, t)
420 }
421}
422
423struct Grid {
428 lo: P2,
429 hi: P2,
430 size: P2,
432 nx: usize,
433 ny: usize,
434 start: Vec<usize>,
436 items: Vec<usize>,
437}
438
439impl Grid {
440 fn build(segs: &[Seg]) -> Result<Grid> {
441 let (mut lo, mut hi) = ([f64::INFINITY; 2], [f64::NEG_INFINITY; 2]);
442 for s in segs {
443 let (l, h) = bounds(s);
444 for k in 0..2 {
445 lo[k] = lo[k].min(l[k]);
446 hi[k] = hi[k].max(h[k]);
447 }
448 }
449 let (w, h) = (hi[0] - lo[0], hi[1] - lo[1]);
450 let n = segs.len().max(1);
451 let nf = n as f64;
452 let c = (w * h / nf).sqrt();
455 let (nx, ny) = if !(w.is_finite() && h.is_finite()) {
456 (1, 1)
457 } else if w > 0.0 && h > 0.0 {
458 if c > 0.0 && c.is_finite() {
459 let along = |e: f64| ((e / c).ceil() as usize).clamp(1, n);
460 (along(w), along(h))
461 } else {
462 (1, 1)
463 }
464 } else {
465 (if w > 0.0 { n } else { 1 }, if h > 0.0 { n } else { 1 })
466 };
467 let size_of = |e: f64, m: usize| {
468 let s = e / m as f64;
469 if s > 0.0 && s.is_finite() { s } else { 1.0 }
470 };
471 let mut grid = Grid {
472 lo,
473 hi,
474 size: [size_of(w, nx), size_of(h, ny)],
475 nx,
476 ny,
477 start: Vec::new(),
478 items: Vec::new(),
479 };
480 let cells = nx * ny;
481 reserve(&mut grid.start, cells + 1)?;
482 grid.start.resize(cells + 1, 0);
483 let (lo, size) = (grid.lo, grid.size);
484 let mut total = 0usize;
485 for s in segs {
486 let mut count = 0;
487 Grid::visit_cells(lo, size, nx, ny, s, |c| {
488 grid.start[c + 1] += 1;
489 count += 1;
490 });
491 total = total.saturating_add(count);
492 }
493 for c in 0..cells {
494 grid.start[c + 1] += grid.start[c];
495 }
496 reserve(&mut grid.items, total)?;
497 grid.items.resize(total, 0);
498 let mut end = Vec::new();
500 reserve(&mut end, cells)?;
501 end.extend_from_slice(&grid.start[1..]);
502 let items = &mut grid.items;
503 for (j, s) in segs.iter().enumerate() {
504 Grid::visit_cells(lo, size, nx, ny, s, |c| {
505 end[c] -= 1;
506 items[end[c]] = j;
507 });
508 }
509 Ok(grid)
510 }
511
512 fn coord(&self, x: f64, k: usize) -> i64 {
515 let n = [self.nx, self.ny][k] as f64;
516 ((x - self.lo[k]) / self.size[k]).floor().clamp(-1.0, n) as i64
517 }
518
519 fn visit_cells(lo: P2, size: P2, nx: usize, ny: usize, s: &Seg, mut f: impl FnMut(usize)) {
527 let coord = |p: f64, k: usize| {
528 let n = [nx, ny][k] as f64;
529 (((p - lo[k]) / size[k]).floor().clamp(-1.0, n) as i64).clamp(0, [nx, ny][k] as i64 - 1)
530 };
531 let g = |p: f64, k: usize| (p - lo[k]) / size[k];
532 let [mut x, mut y] = [coord(s[0][0], 0), coord(s[0][1], 1)];
533 let [ex, ey] = [coord(s[1][0], 0), coord(s[1][1], 1)];
534 let (dx, dy) = (g(s[1][0], 0) - g(s[0][0], 0), g(s[1][1], 1) - g(s[0][1], 1));
535 let (sx, sy) = ((ex - x).signum(), (ey - y).signum());
536 let t_max = |d: f64, c: i64, e: i64, g0: f64| {
540 if e == c {
541 (f64::INFINITY, f64::INFINITY)
542 } else if e > c {
543 ((c as f64 + 1.0 - g0) / d, 1.0 / d)
544 } else {
545 ((g0 - c as f64) / -d, -1.0 / d)
546 }
547 };
548 let (mut t_max_x, t_delta_x) = t_max(dx, x, ex, g(s[0][0], 0));
549 let (mut t_max_y, t_delta_y) = t_max(dy, y, ey, g(s[0][1], 1));
550 loop {
551 f((x + y * nx as i64) as usize);
552 if x == ex && y == ey {
553 break;
554 }
555 if x != ex && (y == ey || t_max_x <= t_max_y) {
558 x += sx;
559 t_max_x += t_delta_x;
560 } else {
561 y += sy;
562 t_max_y += t_delta_y;
563 }
564 }
565 }
566
567 fn search(
574 &self,
575 s: Seg,
576 segs: &[Seg],
577 seen: &mut [usize],
578 stamp: usize,
579 best: &mut (f64, P2, P2),
580 ) {
581 let (lo, hi) = bounds(&s);
582 let to_grid = box_gap(lo, hi, self.lo, self.hi);
584 if to_grid >= best.0 {
585 return;
586 }
587 let (x0, x1) = (self.coord(lo[0], 0), self.coord(hi[0], 0));
588 let (y0, y1) = (self.coord(lo[1], 1), self.coord(hi[1], 1));
589 let (nx, ny) = (self.nx as i64, self.ny as i64);
590 let cell = |i: i64, k: usize| self.lo[k] + i as f64 * self.size[k];
592 let mut visit = |x: i64, y: i64, best: &mut (f64, P2, P2)| {
593 if !(0..nx).contains(&x) || !(0..ny).contains(&y) {
594 return;
595 }
596 let near = box_gap(
599 lo,
600 hi,
601 [cell(x - 1, 0), cell(y - 1, 1)],
602 [cell(x + 2, 0), cell(y + 2, 1)],
603 );
604 if near >= best.0 {
605 return;
606 }
607 let c = (x + y * nx) as usize;
608 for &j in &self.items[self.start[c]..self.start[c + 1]] {
609 if seen[j] != stamp {
610 seen[j] = stamp;
611 let (l, h) = bounds(&segs[j]);
612 if box_gap(lo, hi, l, h) >= best.0 {
613 continue;
614 }
615 let g = segment_gap(s, segs[j]);
616 if g.0 < best.0 {
617 *best = g;
618 }
619 }
620 }
621 };
622 let mut r = 0i64;
623 loop {
624 #[cfg(test)]
625 RINGS.with(|c| c.set(c.get() + 1));
626 let (rx0, rx1, ry0, ry1) = (x0 - r, x1 + r, y0 - r, y1 + r);
627 for y in ry0.max(0)..=ry1.min(ny - 1) {
628 if r == 0 || y == ry0 || y == ry1 {
629 for x in rx0.max(0)..=rx1.min(nx - 1) {
630 visit(x, y, best);
631 }
632 } else {
633 visit(rx0, y, best);
634 visit(rx1, y, best);
635 }
636 }
637 let (glo, ghi) = (self.lo, self.hi);
640 let strips = [
641 (rx0 > 0).then(|| (glo, [cell(rx0 + 1, 0), ghi[1]])),
642 (rx1 < nx - 1).then(|| ([cell(rx1, 0), glo[1]], ghi)),
643 (ry0 > 0).then(|| (glo, [ghi[0], cell(ry0 + 1, 1)])),
644 (ry1 < ny - 1).then(|| ([glo[0], cell(ry1, 1)], ghi)),
645 ];
646 let nearest = strips
647 .into_iter()
648 .flatten()
649 .map(|(l, h)| box_gap(lo, hi, l, h))
650 .fold(f64::INFINITY, f64::min);
651 if best.0 == 0.0 || nearest.max(to_grid) >= best.0 {
652 return;
653 }
654 r += 1;
655 }
656 }
657}
658
659fn box_gap(alo: P2, ahi: P2, blo: P2, bhi: P2) -> f64 {
661 let gap = |k: usize| (blo[k] - ahi[k]).max(alo[k] - bhi[k]).max(0.0);
662 let d = [gap(0), gap(1)];
663 dot(d, d)
664}
665
666fn reserve<T>(v: &mut Vec<T>, n: usize) -> Result<()> {
668 allocation_allowed()?;
669 v.try_reserve(n).map_err(|_| QueryError::OutOfMemory)
670}
671
672fn sub(a: P2, b: P2) -> P2 {
673 [a[0] - b[0], a[1] - b[1]]
674}
675
676fn dot(a: P2, b: P2) -> f64 {
677 a[0] * b[0] + a[1] * b[1]
678}
679
680fn cross(a: P2, b: P2) -> f64 {
681 a[0] * b[1] - a[1] * b[0]
682}
683
684fn lerp(a: P2, b: P2, t: f64) -> P2 {
685 [a[0] + t * (b[0] - a[0]), a[1] + t * (b[1] - a[1])]
686}
687
688fn dist2(a: P2, b: P2) -> f64 {
689 let d = sub(a, b);
690 dot(d, d)
691}
692
693fn dist(a: P2, b: P2) -> f64 {
694 let d = sub(a, b);
695 d[0].hypot(d[1])
696}
697
698#[cfg(test)]
699mod tests {
700 use super::*;
701 use crate::{QueryError, fail_allocations_after};
702 use proptest::prelude::*;
703 use std::f64::consts::PI;
704
705 fn square(c: [f64; 2], half: f64, ccw: bool) -> Vec<[f64; 2]> {
706 let mut s = vec![
707 [c[0] - half, c[1] - half],
708 [c[0] + half, c[1] - half],
709 [c[0] + half, c[1] + half],
710 [c[0] - half, c[1] + half],
711 ];
712 if !ccw {
713 s.reverse();
714 }
715 s
716 }
717
718 fn circle(r: f64, n: usize, ccw: bool) -> Vec<[f64; 2]> {
719 let mut s: Vec<_> = (0..n)
720 .map(|i| {
721 let a = 2.0 * PI * i as f64 / n as f64;
722 [r * a.cos(), r * a.sin()]
723 })
724 .collect();
725 if !ccw {
726 s.reverse();
727 }
728 s
729 }
730
731 fn fill_area(f: &Fill) -> f64 {
732 f.triangles
733 .iter()
734 .map(|t| {
735 let [a, b, c] = t.map(|i| f.points[i as usize]);
736 0.5 * ((b[0] - a[0]) * (c[1] - a[1]) - (b[1] - a[1]) * (c[0] - a[0]))
737 })
738 .sum()
739 }
740
741 #[test]
742 fn length_and_area_of_unit_square() {
743 let mut s = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
744 assert_eq!(contour_length(&s, true), 4.0);
745 assert_eq!(contour_length(&s, false), 3.0);
746 assert_eq!(signed_area(&s), 1.0);
747 s.reverse();
748 assert_eq!(signed_area(&s), -1.0);
749 assert_eq!(contour_length(&[], true), 0.0);
750 assert_eq!(contour_length(&[[1.0, 2.0]], true), 0.0);
751 assert_eq!(signed_area(&[[0.0, 0.0], [1.0, 0.0]]), 0.0);
752 }
753
754 #[test]
755 fn fill_ring_area() {
756 let outer = circle(2.0, 256, true);
757 let inner = circle(1.0, 256, false);
758 let mut out = Fill::default();
759 assert_eq!(fill(&[(&outer, true), (&inner, true)], &mut out), Ok(true));
760 let area = fill_area(&out);
761 assert!((area - 3.0 * PI).abs() < 3.0 * PI * 1e-3, "{area}");
762 assert_eq!(out.points.len(), 512);
763 let single = Fill {
765 points: out.points.clone(),
766 triangles: Vec::new(),
767 };
768 for &t in &out.triangles {
769 let one = Fill {
770 triangles: vec![t],
771 ..single.clone()
772 };
773 assert!(fill_area(&one) >= 0.0);
774 }
775 }
776
777 #[test]
778 fn fill_nested_island() {
779 let (a, b, c) = (
780 square([0.0, 0.0], 5.0, true),
781 square([0.0, 0.0], 3.0, false),
782 square([0.0, 0.0], 1.0, true),
783 );
784 let mut out = Fill::default();
785 for set in [[&c, &a, &b], [&a, &b, &c], [&b, &c, &a]] {
787 let input: Vec<_> = set.iter().map(|s| (s.as_slice(), true)).collect();
788 assert_eq!(fill(&input, &mut out), Ok(true));
789 assert!((fill_area(&out) - (100.0 - 36.0 + 4.0)).abs() < 1e-9);
790 }
791 }
792
793 #[test]
794 fn fill_unoriented_even_odd() {
795 let mut out = Fill::default();
796 for (wa, wb, wc) in [
797 (true, true, true),
798 (false, true, false),
799 (true, false, true),
800 ] {
801 let (a, b, c) = (
802 square([0.0, 0.0], 5.0, wa),
803 square([0.5, 0.0], 3.0, wb),
804 square([0.5, 0.5], 1.0, wc),
805 );
806 let input = [(&a[..], false), (&b[..], true), (&c[..], true)];
807 assert_eq!(fill(&input, &mut out), Ok(true));
808 assert!((fill_area(&out) - (100.0 - 36.0 + 4.0)).abs() < 1e-9);
809 }
810 }
811
812 #[test]
813 fn fill_inside_out_contours() {
814 let outer = square([0.0, 0.0], 3.0, true);
817 let hole = square([0.5, 0.0], 1.0, false);
818 let mut out = Fill::default();
819 assert_eq!(fill(&[(&outer, true), (&hole, true)], &mut out), Ok(true));
820 let area = fill_area(&out);
821 assert!((area - 32.0).abs() < 1e-9, "{area}");
822 let (outer_cw, hole_ccw) = (
823 square([0.0, 0.0], 3.0, false),
824 square([0.5, 0.0], 1.0, true),
825 );
826 assert_eq!(
827 fill(&[(&outer_cw, true), (&hole_ccw, true)], &mut out),
828 Ok(true)
829 );
830 assert_eq!(fill_area(&out), area);
831 assert_eq!(fill(&[(&outer_cw, true)], &mut out), Ok(true));
833 assert!((fill_area(&out) - 36.0).abs() < 1e-9);
834 }
835
836 #[test]
837 fn fill_hole_touching_outer_contour() {
838 let outer = square([0.0, 0.0], 2.0, true);
842 let flush = [[-2.0, 1.0], [-1.0, 1.0], [-1.0, 0.0], [-2.0, 0.0]];
843 let apex = [[-1.0, -1.0], [1.0, -1.0], [0.0, -2.0]];
844 for hole in [&flush[..], &apex[..]] {
845 let mut out = Fill::default();
846 assert_eq!(fill(&[(&outer, true), (hole, true)], &mut out), Ok(true));
847 let area = fill_area(&out);
848 assert!((area - 15.0).abs() < 1e-9, "{area}");
849 }
850 }
851
852 #[test]
853 fn fill_orphan_hole_and_reused_output() {
854 let outer = square([10.0, 0.0], 3.0, true);
857 let hole = square([0.0, 0.0], 1.0, false);
858 let mut out = Fill {
859 points: vec![[9.0, 9.0]],
860 triangles: vec![[0, 0, 0]],
861 };
862 assert_eq!(fill(&[(&outer, true), (&hole, true)], &mut out), Ok(false));
863 assert_eq!(out.points, outer);
864 assert!((fill_area(&out) - 36.0).abs() < 1e-9);
865 let line = [[0.0, 0.0], [1.0, 0.0], [2.0, 0.0]];
867 assert_eq!(fill(&[(&line, true), (&[], false)], &mut out), Ok(true));
868 assert!(out.triangles.is_empty());
869 assert_eq!(fill(&[], &mut out), Ok(true));
870 }
871
872 #[test]
873 fn fill_non_finite_is_not_ok() {
874 let mut s = square([0.0, 0.0], 1.0, true);
875 s[1][0] = f64::NAN;
876 let mut out = Fill::default();
877 assert_eq!(fill(&[(&s, true)], &mut out), Ok(false));
878 assert_eq!(fill(&[(&s, false)], &mut out), Ok(false));
879 }
880
881 #[test]
882 fn fill_self_intersecting_reports_false_or_ok_area() {
883 let bow = [[0.0, 0.0], [2.0, 2.0], [2.0, 0.0], [0.0, 2.0]];
884 let twisted = [[0.0, 0.0], [4.0, 0.0], [4.0, 4.0], [1.0, -1.0], [0.0, 4.0]];
885 let mut out = Fill::default();
886 for ring in [&bow[..], &twisted[..]] {
887 for oriented in [true, false] {
888 let ok = fill(&[(ring, oriented)], &mut out).unwrap();
889 let expected = signed_area(ring).abs();
891 if ok {
892 assert!((fill_area(&out) - expected).abs() <= 1e-6);
893 }
894 }
895 }
896 }
897
898 #[test]
899 fn fill_out_of_memory_leaves_output_empty() {
900 let (a, b, c) = (
901 square([0.0, 0.0], 5.0, true),
902 square([0.0, 0.0], 3.0, false),
903 square([0.0, 0.0], 1.0, true),
904 );
905 let input = [(&a[..], true), (&b[..], true), (&c[..], true)];
906 let mut expected = Fill::default();
907 assert_eq!(fill(&input, &mut expected), Ok(true));
908 let mut n = 0;
909 loop {
910 let mut out = Fill {
911 points: vec![[1.0, 1.0]],
912 triangles: vec![[0, 0, 0]],
913 };
914 fail_allocations_after(Some(n));
915 let r = fill(&input, &mut out);
916 fail_allocations_after(None);
917 match r {
918 Err(QueryError::OutOfMemory) => assert_eq!(out, Fill::default()),
919 Ok(ok) => {
920 assert!(ok);
921 assert_eq!(out, expected);
922 break;
923 }
924 Err(e) => panic!("{e}"),
925 }
926 n += 1;
927 }
928 assert!(n > 0);
929 }
930
931 #[test]
932 fn gap_concentric_circles() {
933 let a = circle(1.0, 512, true);
934 let b = circle(3.0, 512, false);
935 let g = contour_gap(&[(&a, true)], &[(&b, true)]).unwrap().unwrap();
936 assert!((g.distance - 2.0).abs() < 1e-3, "{g:?}");
937 let r = |p: [f64; 2]| p[0].hypot(p[1]);
938 assert!((r(g.a) - 1.0).abs() < 1e-3);
939 assert!((r(g.b) - 3.0).abs() < 1e-3);
940 assert!(((g.a[0] - g.b[0]).hypot(g.a[1] - g.b[1]) - g.distance).abs() < 1e-12);
941 }
942
943 #[test]
944 fn gap_is_not_all_pairs() {
945 let a = circle(1.0, 2048, true);
946 let b = circle(3.0, 2048, false);
947 PAIRS_TESTED.with(|c| c.set(0));
948 let g = contour_gap(&[(&a, true)], &[(&b, true)]).unwrap().unwrap();
949 let tested = PAIRS_TESTED.with(|c| c.get());
950 assert!((g.distance - 2.0).abs() < 1e-5);
951 assert!(tested < 2048 * 64, "{tested}");
953 }
954
955 #[test]
956 fn gap_near_flat_side_is_not_quadratic() {
957 let n = 5000;
960 let mut b: Vec<_> = (0..=n).map(|i| [10.0 * i as f64 / n as f64, 0.0]).collect();
961 b[1][1] = 1e-6;
962 for height in [1.0, 0.01] {
963 let a: Vec<_> = (0..=n)
964 .map(|i| [5.0 + 5.0 * i as f64 / n as f64, height])
965 .collect();
966 RINGS.with(|c| c.set(0));
967 let g = contour_gap(&[(&a, false)], &[(&b, false)])
968 .unwrap()
969 .unwrap();
970 let rings = RINGS.with(|c| c.get());
971 assert_eq!(g.distance, height);
972 assert!(rings < 8 * n, "{rings}");
974 }
975 }
976
977 #[test]
978 fn gap_long_diagonal_lists_cells_along_its_length() {
979 let mut segs = Vec::new();
984 for i in 0..4096 {
985 let (x, y) = (f64::from(i % 64) + 0.5, f64::from(i / 64) + 0.5);
986 segs.push([[x, y], [x + 0.01, y]]);
987 }
988 segs.push([[0.0, 0.0], [63.0, 63.0]]);
989 let grid = Grid::build(&segs).unwrap();
990 assert!((grid.nx * grid.ny) >= 4096, "{}x{}", grid.nx, grid.ny);
991 let listed = grid.items.iter().filter(|&&j| j == 4096).count();
992 assert!(listed < 512, "{listed}");
993 let b: Vec<_> = segs.iter().map(|s| (s.to_vec(), false)).collect();
995 let rb: Vec<_> = b.iter().map(|(l, c)| (l.as_slice(), *c)).collect();
996 let far = [[200.0, 200.0], [201.0, 200.0]];
997 let g = contour_gap(&[(&far[..], false)], &rb).unwrap().unwrap();
998 assert!(
1000 (g.distance - (200.0f64 - 63.51).hypot(200.0 - 63.5)).abs() < 1e-9,
1001 "{g:?}"
1002 );
1003 let brute = brute_gap(&[(&far[..], false)], &rb).unwrap();
1004 assert!((g.distance - brute).abs() <= 1e-12);
1005 let query = [[-50.0, 70.0], [50.0, -10.0]];
1007 let g = contour_gap(&[(&query[..], false)], &rb).unwrap().unwrap();
1008 let brute = brute_gap(&[(&query[..], false)], &rb).unwrap();
1009 assert!((g.distance - brute).abs() <= 1e-12);
1010 }
1011
1012 #[test]
1013 fn gap_cost_does_not_depend_on_where_a_starts() {
1014 let b: Vec<_> = circle(100.0, 2072, true)
1017 .into_iter()
1018 .map(|p| [p[0] + 230.0, p[1]])
1019 .collect();
1020 let mut tested = Vec::new();
1021 for start in [1036, 0] {
1022 let mut a = circle(100.0, 2072, true);
1023 a.rotate_left(start);
1024 PAIRS_TESTED.with(|c| c.set(0));
1025 let g = contour_gap(&[(&a, true)], &[(&b, true)]).unwrap().unwrap();
1026 assert!((g.distance - 30.0).abs() < 1e-9, "{g:?}");
1027 assert_eq!(g.a, [100.0, 0.0]);
1028 tested.push(PAIRS_TESTED.with(|c| c.get()));
1029 }
1030 assert!(tested.iter().all(|&t| t < 64), "{tested:?}");
1032 }
1033
1034 #[test]
1035 fn gap_crossing_is_zero() {
1036 let a = [[0.0, 0.0], [2.0, 2.0]];
1037 let b = [[0.0, 2.0], [2.0, 0.0]];
1038 let g = contour_gap(&[(&a, false)], &[(&b, false)])
1039 .unwrap()
1040 .unwrap();
1041 assert_eq!(g.distance, 0.0);
1042 assert_eq!(g.a, [1.0, 1.0]);
1043 assert_eq!(g.b, [1.0, 1.0]);
1044 let s = square([0.0, 0.0], 1.0, true);
1046 let t = square([1.5, 1.5], 1.0, true);
1047 let g = contour_gap(&[(&s, true)], &[(&t, true)]).unwrap().unwrap();
1048 assert_eq!(g.distance, 0.0);
1049 assert_eq!(g.a, g.b);
1050 let c = [[2.0, 2.0], [3.0, 5.0]];
1052 let g = contour_gap(&[(&a, false)], &[(&c, false)])
1053 .unwrap()
1054 .unwrap();
1055 assert_eq!((g.distance, g.a, g.b), (0.0, [2.0, 2.0], [2.0, 2.0]));
1056 let d = [[1.0, 1.0], [3.0, 3.0]];
1057 let g = contour_gap(&[(&a, false)], &[(&d, false)])
1058 .unwrap()
1059 .unwrap();
1060 assert_eq!(g.distance, 0.0);
1061 assert_eq!(g.a, g.b);
1062 }
1063
1064 #[test]
1065 fn gap_of_closing_segment_and_far_apart_sets() {
1066 let tri = [[0.0, 0.0], [10.0, 10.0], [0.0, 10.0]];
1068 let p = [[-1.0, 5.0], [-2.0, 5.0]];
1069 let open = contour_gap(&[(&tri, false)], &[(&p, false)])
1070 .unwrap()
1071 .unwrap();
1072 let closed = contour_gap(&[(&tri, true)], &[(&p, false)])
1073 .unwrap()
1074 .unwrap();
1075 assert!(closed.distance < open.distance);
1076 assert_eq!(
1077 (closed.distance, closed.a, closed.b),
1078 (1.0, [0.0, 5.0], [-1.0, 5.0])
1079 );
1080 let far = [[1000.0, -3.0], [1000.0, 17.0]];
1081 let g = contour_gap(&[(&far, false)], &[(&tri, true)])
1082 .unwrap()
1083 .unwrap();
1084 assert_eq!(
1085 (g.distance, g.a, g.b),
1086 (990.0, [1000.0, 10.0], [10.0, 10.0])
1087 );
1088 }
1089
1090 #[test]
1091 fn gap_none_when_either_empty() {
1092 let s = square([0.0, 0.0], 1.0, true);
1093 let one = [[5.0, 5.0]];
1094 assert_eq!(contour_gap(&[], &[(&s, true)]), Ok(None));
1095 assert_eq!(contour_gap(&[(&s, true)], &[]), Ok(None));
1096 assert_eq!(contour_gap(&[(&one, true)], &[(&s, true)]), Ok(None));
1097 assert_eq!(contour_gap(&[(&s, false)], &[(&[], true)]), Ok(None));
1098 }
1099
1100 #[test]
1101 fn gap_degenerate_and_non_finite() {
1102 let s = square([0.0, 0.0], 1.0, true);
1104 let dot = [[3.0, 0.0], [3.0, 0.0]];
1105 let g = contour_gap(&[(&s, true)], &[(&dot, false)])
1106 .unwrap()
1107 .unwrap();
1108 assert_eq!((g.distance, g.a, g.b), (2.0, [1.0, 0.0], [3.0, 0.0]));
1109 let bad = [[0.0, 0.0], [f64::INFINITY, 0.0]];
1110 assert!(matches!(
1111 contour_gap(&[(&s, true)], &[(&bad, false)]),
1112 Err(QueryError::InvalidInput(_))
1113 ));
1114 assert!(matches!(
1115 contour_gap(&[(&bad, false)], &[(&s, true)]),
1116 Err(QueryError::InvalidInput(_))
1117 ));
1118 }
1119
1120 #[test]
1121 fn gap_out_of_memory() {
1122 let a = circle(1.0, 64, true);
1123 let b = circle(3.0, 64, false);
1124 let expected = contour_gap(&[(&a, true)], &[(&b, true)]).unwrap();
1125 let mut n = 0;
1126 loop {
1127 fail_allocations_after(Some(n));
1128 let r = contour_gap(&[(&a, true)], &[(&b, true)]);
1129 fail_allocations_after(None);
1130 match r {
1131 Err(QueryError::OutOfMemory) => n += 1,
1132 r => {
1133 assert_eq!(r, Ok(expected));
1134 break;
1135 }
1136 }
1137 }
1138 assert!(n > 0);
1139 }
1140
1141 fn brute_gap(a: &[(&[[f64; 2]], bool)], b: &[(&[[f64; 2]], bool)]) -> Option<f64> {
1143 let mut sa = Vec::new();
1144 let mut sb = Vec::new();
1145 collect_segments(a, &mut sa).unwrap();
1146 collect_segments(b, &mut sb).unwrap();
1147 let mut best: Option<f64> = None;
1148 for p in &sa {
1149 for q in &sb {
1150 let d = segment_gap(*p, *q).0;
1151 best = Some(best.map_or(d, |b| b.min(d)));
1152 }
1153 }
1154 best.map(f64::sqrt)
1155 }
1156
1157 fn cases(default: u32) -> u32 {
1158 std::env::var("PROPTEST_CASES")
1159 .ok()
1160 .and_then(|s| s.parse().ok())
1161 .unwrap_or(default)
1162 }
1163
1164 fn polylines() -> impl Strategy<Value = Vec<(Vec<[f64; 2]>, bool)>> {
1165 let coord = prop_oneof![(-20i32..20).prop_map(f64::from), -20.0f64..20.0];
1167 let line = (
1168 prop::collection::vec([coord.clone(), coord], 0..=51),
1169 any::<bool>(),
1170 );
1171 prop::collection::vec(line, 2..=5)
1172 }
1173
1174 proptest! {
1175 #![proptest_config(ProptestConfig::with_cases(cases(256)))]
1176
1177 #[test]
1178 fn gap_matches_brute_force(a in polylines(), b in polylines(), shift in 0.0f64..60.0) {
1179 let b: Vec<_> = b
1180 .into_iter()
1181 .map(|(l, c)| (l.into_iter().map(|p| [p[0] + shift, p[1]]).collect::<Vec<_>>(), c))
1182 .collect();
1183 let ra: Vec<_> = a.iter().map(|(l, c)| (l.as_slice(), *c)).collect();
1184 let rb: Vec<_> = b.iter().map(|(l, c)| (l.as_slice(), *c)).collect();
1185 let got = contour_gap(&ra, &rb).unwrap();
1186 let want = brute_gap(&ra, &rb);
1187 match (got, want) {
1188 (None, None) => {}
1189 (Some(g), Some(w)) => {
1190 prop_assert!((g.distance - w).abs() <= 1e-12, "{} vs {}", g.distance, w);
1191 let d = (g.a[0] - g.b[0]).hypot(g.a[1] - g.b[1]);
1192 prop_assert!((d - g.distance).abs() <= 1e-9);
1193 }
1194 (g, w) => prop_assert!(false, "{:?} vs {:?}", g, w),
1195 }
1196 }
1197 }
1198}