Skip to main content

fast_mesh_query/
section.rs

1//! Measurements on section contours, in the plane's `(u, v)` frame: length,
2//! signed area, the filled cross-section and the in-plane gap between two
3//! sets of contours.
4//!
5//! Everything here is pure 2D on polylines of `(u, v)` points, as made by
6//! [`Plane::to_uv`](crate::Plane::to_uv) from the points of a [`slice()`](crate::slice()).
7
8use crate::triangulate::triangulate;
9use crate::tritri::{orient2d_approx, side2};
10use crate::{QueryError, Result, allocation_allowed};
11
12type P2 = [f64; 2];
13
14/// A segment between two points.
15type Seg = [P2; 2];
16
17/// The filled cross-section made by [`fill`]: triangles over `(u, v)` points.
18#[derive(Debug, Clone, Default, PartialEq)]
19pub struct Fill {
20    /// The points of every filled contour, in `(u, v)`: each outer contour
21    /// followed by its holes, in input order.
22    pub points: Vec<[f64; 2]>,
23    /// Triangles numbering `points`, counter-clockwise for simple contours.
24    pub triangles: Vec<[u32; 3]>,
25}
26
27/// The closest pair between two sets of polylines, found by [`contour_gap`].
28#[derive(Debug, Clone, Copy, PartialEq)]
29pub struct Gap2 {
30    /// The distance between `a` and `b`; 0 when the polylines cross or touch.
31    pub distance: f64,
32    /// The point on the first set.
33    pub a: [f64; 2],
34    /// The point on the second set.
35    pub b: [f64; 2],
36}
37
38/// The length of a polyline: the sum of its segment lengths. A closed one
39/// also counts the segment from its last point back to its first.
40pub 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
48/// The signed area of the closed polygon `points`, by the shoelace formula:
49/// positive when it runs counter-clockwise. Fewer than three points have no
50/// area.
51pub fn signed_area(points: &[[f64; 2]]) -> f64 {
52    let Some(&o) = points.first() else {
53        return 0.0;
54    };
55    // A fan from the first point, which keeps the products small when the
56    // polygon is far from the origin.
57    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
65/// Fills the closed contours `contours` (each with whether it is oriented)
66/// and writes the triangles to `out`, replacing what it held.
67///
68/// Contours are nested into outers and holes:
69/// - When every contour is oriented, counter-clockwise contours are outers,
70///   and each clockwise contour is a hole of the smallest outer containing
71///   it. A clockwise contour inside no outer is not filled. When the summed
72///   signed area is negative (a closed mesh wound inward), the orientation is
73///   taken as reversed throughout: clockwise contours are the outers.
74/// - When any contour is unoriented, winding is ignored: a contour inside an
75///   even number of others is an outer, and one inside an odd number is a
76///   hole of the smallest such outer containing it.
77///
78/// Containment tests one point of the inner contour with the even-odd ray
79/// rule. Contours with zero area are not filled. Each outer and its holes are
80/// triangulated by [`triangulate`]; `out.points` holds their points, each
81/// outer followed by its holes.
82///
83/// Returns whether the fill is good (`fill_ok`): `false` when triangulation
84/// could not finish, or when the fill's signed area differs from the
85/// contours' summed area (signed when oriented, with the sign flipped when
86/// reversed; by nesting depth otherwise)
87/// by more than `1e-6 · max(1, Σ |area|)`. This is best effort: there is no
88/// separate self-intersection test, so a bad fill can go unreported.
89///
90/// # Errors
91/// - [`QueryError::OutOfMemory`] when memory could not be reserved; `out` is
92///   then empty.
93/// - [`QueryError::InvalidInput`] when there are more points in all than a
94///   `u32` can number.
95pub 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/// What a contour is in the fill.
107#[derive(Debug, Clone, Copy, PartialEq)]
108enum Role {
109    /// Not filled.
110    Skip,
111    Outer,
112    /// A hole of the outer with this index.
113    Hole(usize),
114}
115
116/// A contour's area and bounding box.
117struct 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    // Whether contour `j` contains a point of contour `i`.
141    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    // The smallest contour that is `allowed` and contains contour `i`.
151    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        // A closed mesh wound inward gives clockwise outers and
163        // counter-clockwise holes: a negative total means the orientation is
164        // reversed throughout.
165        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        // Fits: every point counted in `total` is at most `u32::MAX`.
218        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    // Written so that NaN fails.
239    let matches = (area - expected).abs() <= 1e-6 * f64::max(1.0, scale);
240    Ok(ok && matches)
241}
242
243/// Whether `p` is inside the polygon `poly`, by the even-odd ray rule.
244fn 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
259/// The smallest box containing `pts`; inverted (`lo > hi`) when empty.
260fn 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
276/// The minimum distance between the segments of the polylines `a` and those
277/// of `b`, each with whether it is closed, with the closest pair of points.
278///
279/// An open polyline of `n` points has `n − 1` segments, and a closed one also
280/// joins its last point to its first. When segments of the two sides cross
281/// or touch, the distance is 0 and both points are the same point where
282/// they meet. Returns `None` when either side has no segments.
283///
284/// The segments of `b` are binned in a uniform grid with about one cell per
285/// segment, and each segment of `a` searches outward through it ring by ring
286/// until no unvisited cell can be closer. The segments of `a` are taken
287/// nearest `b`'s bounding box first, and the search stops at the first one
288/// whose box is at least the best distance from it. The result is the exact
289/// minimum of the pairwise distances, not an approximation.
290///
291/// Known limits: a segment of `a` starts from every cell its box overlaps,
292/// so a very long one still visits many cells on its first ring (mesh
293/// sections have short segments). Coordinates beyond about `1e154` overflow
294/// the squared distances.
295///
296/// # Errors
297/// - [`QueryError::InvalidInput`] when a point is not finite.
298/// - [`QueryError::OutOfMemory`] when working memory could not be reserved.
299pub 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    // The segments of `a` nearest `b`'s box first: the first searches find a
309    // close pair, and the rest stop as soon as their box gap, a lower bound
310    // on their distance to every segment of `b`, reaches it. Without this the
311    // cost depended on where contour `a` happened to start.
312    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
334/// Appends the segments of `lines` to `out`.
335fn 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
352/// The squared distance between segments `p` and `q`, with the closest point
353/// on each. Crossing or touching segments give 0 and the point where they
354/// meet, decided by exact orientation tests.
355fn 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        // A proper crossing: where `ab` meets the line through `cd`.
364        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    // An endpoint on the other segment.
375    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    // Apart: the closest pair has an endpoint of one segment.
382    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    /// Test-only count of [`segment_gap`] calls on this thread.
401    static PAIRS_TESTED: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
402    /// Test-only count of rings searched by [`Grid::search`] on this thread.
403    static RINGS: std::cell::Cell<usize> = const { std::cell::Cell::new(0) };
404}
405
406/// The point of segment `ab` closest to `p`.
407fn 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
423/// A uniform grid over segments: each segment is listed in the cells it
424/// passes through (see [`Grid::visit_cells`]), so every point of a segment
425/// is in one of its cells or next to one, which the search's one-cell slack
426/// allows for.
427struct Grid {
428    lo: P2,
429    hi: P2,
430    /// The cell size along each axis.
431    size: P2,
432    nx: usize,
433    ny: usize,
434    /// The segments of cell `x + y·nx` are `items[start[c]..start[c + 1]]`.
435    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        // About one cell per segment, square where the extent allows; a flat
453        // extent gets one row or column, and an overflowing one a single cell.
454        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        // Fill each cell from its end down, leaving `start` as it was.
499        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    /// The cell coordinate of `x` along axis `k`, clamped to `-1..=n` (one
513    /// past either edge of the grid).
514    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    /// Calls `f` with every cell the segment `s` passes through, from its
520    /// start to its end (Amanatides & Woo's grid traversal), so that a long
521    /// diagonal costs cells along its length rather than its box. Each step
522    /// moves one cell towards the end cell, so every cell comes up once and
523    /// the walk ends after at most `nx + ny` steps, whatever the rounding;
524    /// a step lands at most one cell from the segment, which the search's
525    /// one-cell slack allows for.
526    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        // The fraction of the segment to the first grid line along each
537        // axis, and per cell after that (`inf` past an axis already at the
538        // end, which then never steps).
539        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            // Step the nearer grid line; ties step `x` first, visiting the
556            // corner's neighbour too. An axis at the end never steps.
557            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    /// Tests `s` against the grid's segments that could be closer than
568    /// `best`, improving it. Cells are visited in rings around the cells of
569    /// `s`'s box. After each ring the unvisited cells form up to four strips
570    /// of the grid box beyond the ring's sides, each grown by one cell toward
571    /// `s` (slack for rounding in [`Grid::coord`]); the search stops once the
572    /// nearest strip is at least `best` away.
573    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        // Every segment of the grid lies within its box.
583        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        // The edge of cell `i` along axis `k`.
591        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            // The cell grown by a cell on every side (slack for rounding):
597            // its segments lie within it, unless they reach a nearer cell.
598            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            // The unvisited strips left, right, below and above the ring,
638            // where the grid extends past it.
639            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
659/// The squared distance between the boxes `[alo, ahi]` and `[blo, bhi]`.
660fn 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
666/// Reserves room for `n` more elements, honouring the test fault hook.
667fn 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        // Every triangle is counter-clockwise.
764        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        // The order of the contours does not matter.
786        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        // A ring, then the same ring wound inward (clockwise outer,
815        // counter-clockwise hole): the same fill.
816        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        // A lone clockwise contour is an inside-out outer.
832        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        // A hole with an edge on the outer's edge (starting on it), and
839        // one touching at a single apex: both still nest as holes and
840        // fill outer minus hole.
841        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        // A clockwise contour inside no outer is not filled, so the area check
855        // fails (and only that outer is filled).
856        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        // Collinear and tiny contours fill nothing and pass.
866        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                // One ring is an outer whichever way it turns.
890                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        // About 45 pairs per segment, against 2048 for all pairs.
952        assert!(tested < 2048 * 64, "{tested}");
953    }
954
955    #[test]
956    fn gap_near_flat_side_is_not_quadratic() {
957        // Side b is one grid row thinner than its segment spacing (a 1e-6
958        // spike at one end); side a hangs over its other half.
959        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            // A few rings per segment, against about `n` each before.
973            assert!(rings < 8 * n, "{rings}");
974        }
975    }
976
977    #[test]
978    fn gap_long_diagonal_lists_cells_along_its_length() {
979        // 4096 short segments on a grid, plus one diagonal across it all:
980        // listing the diagonal in every cell its box overlaps fills the
981        // whole grid, while binning it along its length visits one row of
982        // cells.
983        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        // And the gap through the grid is still right.
994        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        // Nearest: the end (63.51, 63.5) of the corner short segment.
999        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        // A long query segment crossing the grid agrees with brute force.
1006        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        // Two circles 30 apart; contour a starts at its far side, then at
1015        // its point closest to b.
1016        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        // A handful of segments on each side, either way.
1031        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        // Two squares overlapping at a corner.
1045        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        // Touching end to end, and overlapping collinear segments.
1051        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        // Only the closing segment of the open-looking triangle comes near.
1067        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        // Every point of the second side in one place: one grid cell.
1103        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    /// The all-pairs minimum, through the same segment test.
1142    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        // A coarse lattice now and then, so that crossings and touches are common.
1166        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}