Skip to main content

geop_core_geometry/
nearest.rs

1//! [`least_distance`]: the least distance between two sets of points — a
2//! point, curves, surfaces, trimmed faces — and two points attaining it,
3//! by branch and bound over pieces of them.
4//!
5//! # How
6//!
7//! A [`Piece`] is part of one of the sets: a span of a curve, a patch of a
8//! surface. It knows points whose convex hull holds all of it (its control
9//! points: every weight is positive, so a rational piece lies in their
10//! hull too), a point it attains, and how to cut itself in two. For a pair
11//! of pieces the hulls give a lower bound on the distance between them,
12//! and the attained points an upper bound on the least distance overall.
13//! Pairs are taken least lower bound first; a pair whose lower bound is
14//! definitely above the best distance attained holds nothing nearer and is
15//! dropped, and any other is cut, its larger piece in two. The search is
16//! done once the attained distance is not definitely above the least lower
17//! bound left: nothing can then be shown to be nearer.
18//!
19//! The lower bound is the separation of the two hulls along the direction
20//! between them, or of their boxes, whichever is more. Along the right
21//! direction it is off by the square of a piece's size, not by the size
22//! itself, as a box's is: near a minimum that keeps a handful of pairs
23//! alive at each level, rather than a number growing with every level.
24//!
25//! # What is returned
26//!
27//! Two attained points, and the least distance as an enclosure: from the
28//! least lower bound of every pair still standing to the distance between
29//! the two points. Pieces stop being cut once they are smaller than the
30//! caller's `handoff` (see "Subdivide to isolate, then Newton to refine"
31//! in `AGENTS.md`), and the search stops at `max_nodes` pairs; either only
32//! leaves the enclosure wider, never wrong. The pairs left at the handoff
33//! are returned too, for a caller to refine each with Newton.
34//!
35//! A piece is also left as it is once its points spread no further than
36//! they are wide ([`resolved`]). Its hull points are enclosures, and the
37//! bound from them is off by their width as well as by the piece's size:
38//! cutting halves the size, never the width. A span of a traced curve, its
39//! control points `2e-6` wide, was cut until its domain was two ULPs wide
40//! because its box never came under the handoff; the distance it bounds
41//! is as wide as its data, and no cut could make it sharper.
42
43use std::{cmp::Ordering, collections::BinaryHeap};
44
45use geop_core_math::{
46    geop_error::{GeopError, GeopResult, WithContext},
47    scalars::Scalar,
48    vector::Vector3,
49};
50
51use crate::{
52    contains::surface::surface_extents,
53    nurb_curve::{NurbCurve3D, dehomogenize},
54    nurb_surface::NurbSurface3D,
55};
56
57/// Part of a set a distance is measured to (see the module docs).
58pub trait Piece: Sized {
59    /// Where on the whole a point of the piece is: its parameters, say.
60    type At: Clone;
61
62    /// Points whose convex hull holds every point of the piece.
63    fn hull(&self) -> GeopResult<Vec<Vector3>>;
64
65    /// A point of the piece and where it is — if one is known to belong to
66    /// the set: a patch of a trimmed face may hold none of the face.
67    fn point(&self) -> GeopResult<Option<(Self::At, Vector3)>>;
68
69    /// What is left of the piece cut in two — a half known to hold nothing
70    /// of the set left out — or `None` once it is within `handoff` in
71    /// size.
72    fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>>;
73}
74
75/// The least distance between two sets, as [`least_distance`] finds it.
76#[derive(Clone, Debug)]
77pub struct Nearest<A, B> {
78    /// The point of the first set, and where on it.
79    pub a: (A, Vector3),
80    /// The point of the second set, and where on it.
81    pub b: (B, Vector3),
82    /// The least distance between the sets: at least the search's lower
83    /// bound, at most the distance between `a` and `b`.
84    pub distance: Scalar,
85    /// Where on each set the pairs left at the handoff attain a point:
86    /// where else a least distance could be, for Newton to refine.
87    pub candidates: Vec<(A, B)>,
88}
89
90/// A pair of pieces waiting to be searched, with what bounds it.
91struct Pair<PA: Piece, PB: Piece> {
92    a: PA,
93    b: PB,
94    hull_a: Vec<Vector3>,
95    hull_b: Vec<Vector3>,
96    /// A lower bound on the distance between the two pieces.
97    lower: Scalar,
98}
99
100impl<PA: Piece, PB: Piece> Pair<PA, PB> {
101    fn new(a: PA, b: PB) -> GeopResult<Self> {
102        let (hull_a, hull_b) = (a.hull()?, b.hull()?);
103        let lower = separation(&hull_a, &hull_b)?;
104        Ok(Self {
105            a,
106            b,
107            hull_a,
108            hull_b,
109            lower,
110        })
111    }
112
113    /// Which pair to take first: the least lower bound. An order only,
114    /// never an answer.
115    fn key(&self) -> f64 {
116        self.lower.lower().to_f64()
117    }
118}
119
120impl<PA: Piece, PB: Piece> PartialEq for Pair<PA, PB> {
121    fn eq(&self, other: &Self) -> bool {
122        self.key() == other.key()
123    }
124}
125impl<PA: Piece, PB: Piece> Eq for Pair<PA, PB> {}
126impl<PA: Piece, PB: Piece> PartialOrd for Pair<PA, PB> {
127    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
128        Some(self.cmp(other))
129    }
130}
131impl<PA: Piece, PB: Piece> Ord for Pair<PA, PB> {
132    /// Reversed: [`BinaryHeap`] pops its greatest.
133    fn cmp(&self, other: &Self) -> Ordering {
134        other.key().total_cmp(&self.key())
135    }
136}
137
138/// The box around `points`: per axis, the union of their coordinates.
139fn bounds(points: &[Vector3]) -> [Scalar; 3] {
140    std::array::from_fn(|k| {
141        points
142            .iter()
143            .map(|p| p[k])
144            .reduce(|a, b| a.union(b))
145            .unwrap_or(Scalar::ENTIRE)
146    })
147}
148
149/// A lower bound on the distance between any point of the hull of `a` and
150/// any of the hull of `b`: the more of how far apart their boxes are, and
151/// how far apart they are along the direction from one box's centre to
152/// the other's. Which direction is a free choice; any gives a bound.
153fn separation(a: &[Vector3], b: &[Vector3]) -> GeopResult<Scalar> {
154    let (box_a, box_b) = (bounds(a), bounds(b));
155    // Apart on an axis by how much the one box's lower end is above the
156    // other's upper end, if it definitely is.
157    let gap = |x: Scalar, y: Scalar| {
158        let ahead = y.lower().sub(x.upper());
159        let behind = x.lower().sub(y.upper());
160        if ahead.definitely_greater(Scalar::ZERO) {
161            ahead
162        } else if behind.definitely_greater(Scalar::ZERO) {
163            behind
164        } else {
165            Scalar::ZERO
166        }
167    };
168    let boxes = (0..3)
169        .map(|k| gap(box_a[k], box_b[k]))
170        .fold(Scalar::ZERO, |sum, g| sum.add(g.mul(g)))
171        .sqrt()?;
172
173    let centre = |b: &[Scalar; 3]| Vector3::from_array(b.map(|x| x.midpoint()));
174    let d = centre(&box_b).sub(&centre(&box_a)).sharpen();
175    let along = |points: &[Vector3]| -> Scalar {
176        points
177            .iter()
178            .map(|p| p.prod_dot(&d))
179            .reduce(|x, y| x.union(y))
180            .unwrap_or(Scalar::ENTIRE)
181    };
182    let apart = along(b).lower().sub(along(a).upper());
183    let length = d.norm();
184    let directed =
185        if apart.definitely_greater(Scalar::ZERO) && length.definitely_greater(Scalar::ZERO) {
186            apart.div(length.upper())?.lower()
187        } else {
188            Scalar::ZERO
189        };
190    Ok(if directed.to_f64() > boxes.lower().to_f64() {
191        directed
192    } else {
193        boxes.lower()
194    })
195}
196
197/// Whether cutting a piece whose hull is `points` cannot tighten its bound:
198/// the points' midpoints spread, on no axis, definitely further than the
199/// widest of them is wide. A cut halves the spread and keeps the width,
200/// so the bound is then as sharp as the piece's enclosures can make it.
201fn resolved(points: &[Vector3]) -> bool {
202    let midpoints: Vec<Vector3> = points
203        .iter()
204        .map(|p| Vector3::from_array(p.to_array().map(|x| x.midpoint())))
205        .collect();
206    let spread = bounds(&midpoints);
207    (0..3).all(|k| {
208        let widest = points
209            .iter()
210            .map(|p| p[k].width())
211            .fold(
212                Scalar::ZERO,
213                |m, w| if w.to_f64() > m.to_f64() { w } else { m },
214            );
215        !spread[k].width().definitely_greater(widest)
216    })
217}
218
219/// The least distance attained so far, and the two points attaining it.
220type Best<A, B> = Option<(Scalar, (A, Vector3), (B, Vector3))>;
221
222/// Keeps the points `a` and `b` attain as the best pair, if they attain
223/// points and are nearer. Which of two attained pairs to keep is a free
224/// choice.
225fn offer<PA: Piece, PB: Piece>(best: &mut Best<PA::At, PB::At>, a: &PA, b: &PB) -> GeopResult<()> {
226    let (Some(pa), Some(pb)) = (a.point()?, b.point()?) else {
227        return Ok(());
228    };
229    let d = pa.1.sub(&pb.1).norm();
230    if best
231        .as_ref()
232        .is_none_or(|(e, ..)| d.midpoint().to_f64() < e.midpoint().to_f64())
233    {
234        *best = Some((d, pa, pb));
235    }
236    Ok(())
237}
238
239/// Whether `pair` definitely holds nothing nearer than `best`.
240fn beyond<PA: Piece, PB: Piece>(pair: &Pair<PA, PB>, best: &Best<PA::At, PB::At>) -> bool {
241    best.as_ref()
242        .is_some_and(|(d, ..)| pair.lower.definitely_greater(*d))
243}
244
245/// Whether the set `a` is pieces of could come within `reach` of the set
246/// `b` is pieces of: [`least_distance`]'s search for a question that needs
247/// no least distance. A pair whose lower bound is definitely beyond `reach`
248/// is dropped before anything is asked of it, attaining a point being what
249/// costs on a trimmed face, and the first points attained within it end the
250/// search. So two parts a hair apart cost a few cuts, and two lying on each
251/// other one look. What the search cannot tell apart, at the handoff or at
252/// `max_nodes` pairs, could be within it.
253pub fn could_come_within<PA: Piece + Clone, PB: Piece + Clone>(
254    a: Vec<PA>,
255    b: Vec<PB>,
256    reach: Scalar,
257    handoff: Scalar,
258    max_nodes: usize,
259) -> GeopResult<bool> {
260    let mut pending = BinaryHeap::new();
261    let mut visited = 0usize;
262    // Keeps a pair that could still be within reach, answering at once if
263    // the points it attains are.
264    let look = |pending: &mut BinaryHeap<Pair<PA, PB>>, pa: PA, pb: PB| -> GeopResult<bool> {
265        let pair = Pair::new(pa, pb)?;
266        if pair.lower.definitely_greater(reach) {
267            return Ok(false);
268        }
269        if let (Some((_, p)), Some((_, q))) = (pair.a.point()?, pair.b.point()?)
270            && !p.sub(&q).norm().definitely_greater(reach)
271        {
272            return Ok(true);
273        }
274        pending.push(pair);
275        Ok(false)
276    };
277    for pa in &a {
278        for pb in &b {
279            if look(&mut pending, pa.clone(), pb.clone())? {
280                return Ok(true);
281            }
282        }
283    }
284    while let Some(pair) = pending.pop() {
285        if visited >= max_nodes {
286            return Ok(true);
287        }
288        visited += 1;
289        let Some(children) = cut(&pair, handoff)? else {
290            return Ok(true);
291        };
292        for (ca, cb) in children {
293            if look(&mut pending, ca, cb)? {
294                return Ok(true);
295            }
296        }
297    }
298    Ok(false)
299}
300
301/// `pair` cut in two, its larger piece, or the other if it cannot be: a
302/// piece is not cut below the handoff, nor once its data resolves nothing
303/// finer (see the module docs). `None`, if neither can.
304#[allow(clippy::type_complexity)]
305fn cut<PA: Piece + Clone, PB: Piece + Clone>(
306    pair: &Pair<PA, PB>,
307    handoff: Scalar,
308) -> GeopResult<Option<Vec<(PA, PB)>>> {
309    let size = |hull: &[Vector3]| -> f64 {
310        bounds(hull)
311            .iter()
312            .map(|x| x.width().to_f64().powi(2))
313            .sum::<f64>()
314    };
315    let a_first = size(&pair.hull_a) >= size(&pair.hull_b);
316    let cut_a = |pair: &Pair<PA, PB>| -> GeopResult<Option<Vec<(PA, PB)>>> {
317        if resolved(&pair.hull_a) {
318            return Ok(None);
319        }
320        Ok(pair
321            .a
322            .split(handoff)?
323            .map(|halves| halves.into_iter().map(|h| (h, pair.b.clone())).collect()))
324    };
325    let cut_b = |pair: &Pair<PA, PB>| -> GeopResult<Option<Vec<(PA, PB)>>> {
326        if resolved(&pair.hull_b) {
327            return Ok(None);
328        }
329        Ok(pair
330            .b
331            .split(handoff)?
332            .map(|halves| halves.into_iter().map(|h| (pair.a.clone(), h)).collect()))
333    };
334    if a_first {
335        match cut_a(pair)? {
336            Some(c) => Ok(Some(c)),
337            None => cut_b(pair),
338        }
339    } else {
340        match cut_b(pair)? {
341            Some(c) => Ok(Some(c)),
342            None => cut_a(pair),
343        }
344    }
345}
346
347/// The least distance between the set `a` is pieces of and the set `b` is
348/// pieces of (see the module docs).
349///
350/// Fails only if neither set attains a point in any piece searched.
351pub fn least_distance<PA: Piece + Clone, PB: Piece + Clone>(
352    a: Vec<PA>,
353    b: Vec<PB>,
354    handoff: Scalar,
355    max_nodes: usize,
356) -> GeopResult<Nearest<PA::At, PB::At>> {
357    let mut best: Best<PA::At, PB::At> = None;
358
359    let mut pending = BinaryHeap::new();
360    for pa in &a {
361        for pb in &b {
362            offer(&mut best, pa, pb)?;
363            pending.push(Pair::new(pa.clone(), pb.clone())?);
364        }
365    }
366    let mut leaves: Vec<Pair<PA, PB>> = Vec::new();
367    let mut visited = 0usize;
368    while let Some(pair) = pending.pop() {
369        if beyond(&pair, &best) {
370            // Every pair waiting has a lower bound at least this one's.
371            pending.clear();
372            break;
373        }
374        // Nothing waiting is definitely nearer than what is attained: the
375        // enclosure from this lower bound to the attained distance is as
376        // tight as the numbers tell. Touching faces end here, where every
377        // pair of their patches could be at distance zero.
378        let converged = best
379            .as_ref()
380            .is_some_and(|(d, ..)| !d.definitely_greater(pair.lower));
381        if converged || visited >= max_nodes {
382            pending.push(pair);
383            break;
384        }
385        visited += 1;
386        let children = cut(&pair, handoff)?;
387        let Some(children) = children else {
388            leaves.push(pair);
389            continue;
390        };
391        for (ca, cb) in children {
392            offer(&mut best, &ca, &cb)?;
393            let child = Pair::new(ca, cb)?;
394            if !beyond(&child, &best) {
395                pending.push(child);
396            }
397        }
398    }
399
400    let (attained, pa, pb) = best.ok_or_else(|| {
401        GeopError::new("least_distance: no piece of one of the sets holds a point of it")
402    })?;
403    // Nothing nearer than the least lower bound of what is still standing.
404    let standing = leaves.iter().chain(pending.iter());
405    let lower = standing
406        .map(|p| p.lower.lower())
407        .reduce(|x, y| if y.to_f64() < x.to_f64() { y } else { x })
408        .unwrap_or(attained);
409    let mut candidates = Vec::new();
410    for leaf in leaves
411        .iter()
412        .filter(|leaf| !leaf.lower.definitely_greater(attained))
413    {
414        if let (Some(a), Some(b)) = (leaf.a.point()?, leaf.b.point()?) {
415            candidates.push((a.0, b.0));
416        }
417    }
418    Ok(Nearest {
419        a: pa,
420        b: pb,
421        distance: lower.lower().union(attained),
422        candidates,
423    })
424}
425
426/// A single point: a set of one piece that cannot be cut.
427#[derive(Clone, Debug)]
428pub struct PointPiece(pub Vector3);
429
430impl Piece for PointPiece {
431    type At = ();
432
433    fn hull(&self) -> GeopResult<Vec<Vector3>> {
434        Ok(vec![self.0])
435    }
436
437    fn point(&self) -> GeopResult<Option<((), Vector3)>> {
438        Ok(Some(((), self.0)))
439    }
440
441    fn split(&self, _: Scalar) -> GeopResult<Option<Vec<Self>>> {
442        Ok(None)
443    }
444}
445
446/// A span of a 3-D curve: a piece of the curve's points, `At` its
447/// parameter.
448#[derive(Clone, Debug)]
449pub struct CurveSpan(pub NurbCurve3D);
450
451impl Piece for CurveSpan {
452    type At = Scalar;
453
454    fn hull(&self) -> GeopResult<Vec<Vector3>> {
455        Ok(dehomogenize::<4, 3>(&self.0.control_points))
456    }
457
458    fn point(&self) -> GeopResult<Option<(Scalar, Vector3)>> {
459        let t = self.0.mid_parameter()?;
460        Ok(Some((t, self.0.evaluate(t)?)))
461    }
462
463    fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>> {
464        if !self.0.size()?.definitely_greater(handoff) {
465            return Ok(None);
466        }
467        let (a, b) = self.0.split_mid().with_context(&|e: GeopError| {
468            e.with_context(format!(
469                "CurveSpan::split(domain={:?}, handoff={handoff:?}, control_points={:?})",
470                self.0.domain(),
471                self.0.control_points
472            ))
473        })?;
474        Ok(Some(vec![CurveSpan(a), CurveSpan(b)]))
475    }
476}
477
478/// A patch of a surface: a piece of the surface's points, `At` its
479/// parameters.
480#[derive(Clone, Debug)]
481pub struct SurfacePatch(pub NurbSurface3D);
482
483impl SurfacePatch {
484    /// The patch in two, across the direction it is longer in.
485    pub fn halves(&self) -> GeopResult<(Self, Self)> {
486        let [along_u, along_v] = surface_extents(&self.0);
487        let (a, b) = if along_u >= along_v {
488            self.0.split_u_mid()?
489        } else {
490            self.0.split_v_mid()?
491        };
492        Ok((SurfacePatch(a), SurfacePatch(b)))
493    }
494
495    /// The middle of its domain, sharp: a free choice of a point of it.
496    pub fn middle(&self) -> GeopResult<(Scalar, Scalar)> {
497        let mid = |(lo, hi): (Scalar, Scalar)| -> GeopResult<Scalar> {
498            Ok(lo.add(hi).div(Scalar::TWO)?.sharpen())
499        };
500        Ok((mid(self.0.domain_u())?, mid(self.0.domain_v())?))
501    }
502}
503
504impl Piece for SurfacePatch {
505    type At = (Scalar, Scalar);
506
507    fn hull(&self) -> GeopResult<Vec<Vector3>> {
508        Ok(dehomogenize::<4, 3>(&self.0.control_points))
509    }
510
511    fn point(&self) -> GeopResult<Option<((Scalar, Scalar), Vector3)>> {
512        let (u, v) = self.middle()?;
513        Ok(Some(((u, v), self.0.evaluate(u, v)?)))
514    }
515
516    fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>> {
517        if !self.0.size()?.definitely_greater(handoff) {
518            return Ok(None);
519        }
520        let (a, b) = self.halves()?;
521        Ok(Some(vec![a, b]))
522    }
523}
524
525#[cfg(test)]
526mod tests {
527    use super::*;
528    use crate::nurb_curve::NurbCurve;
529    use geop_core_math::{scalars::Scalar, vector::Vector4};
530
531    /// A piece of the segment `[lo, hi]` of the `x` axis.
532    #[derive(Clone)]
533    struct Segment(Scalar, Scalar);
534
535    impl Piece for Segment {
536        type At = Scalar;
537
538        fn hull(&self) -> GeopResult<Vec<Vector3>> {
539            let at = |x: Scalar| Vector3::from_array([x, Scalar::ZERO, Scalar::ZERO]);
540            Ok(vec![at(self.0), at(self.1)])
541        }
542
543        fn point(&self) -> GeopResult<Option<(Scalar, Vector3)>> {
544            let x = self.0.add(self.1).div(Scalar::TWO)?.sharpen();
545            Ok(Some((
546                x,
547                Vector3::from_array([x, Scalar::ZERO, Scalar::ZERO]),
548            )))
549        }
550
551        fn split(&self, handoff: Scalar) -> GeopResult<Option<Vec<Self>>> {
552            if !self.1.sub(self.0).definitely_greater(handoff) {
553                return Ok(None);
554            }
555            let mid = self.0.add(self.1).div(Scalar::TWO)?.sharpen();
556            Ok(Some(vec![Segment(self.0, mid), Segment(mid, self.1)]))
557        }
558    }
559
560    /// A span of the rim of a hole drilled by a boolean, traced and fitted:
561    /// its control points are as wide as the trace, `2e-6` in `y`,
562    /// `2e-12` in `z`, beside a sharp edge of the same top, measured to two
563    /// points of the bottom, one below. The sharp edge attains the distance
564    /// one sharply; the rim's lower bound is one less its width in `z`, so
565    /// the pair is neither beyond the best nor converged. Its box can never
566    /// shrink below its points' widths, so cutting it never brought its
567    /// size under the handoff, and the search cut it until its domain was
568    /// two ULPs wide and could not be split. The distance is one, enclosed
569    /// as wide as the data is.
570    #[test]
571    fn traced_span_beside_a_sharp_edge_above_two_points() {
572        let cp = |x: [f64; 2], y: [f64; 2], z: [f64; 2], w: [f64; 2]| {
573            Vector4::from_array([
574                Scalar::new(x[0], x[1]),
575                Scalar::new(y[0], y[1]),
576                Scalar::new(z[0], z[1]),
577                Scalar::new(w[0], w[1]),
578            ])
579        };
580        let span = NurbCurve::try_new(
581            3,
582            vec![
583                cp(
584                    [1.0502014289205366, 1.0502015605016914],
585                    [1.3968357901928796, 1.3968380515018717],
586                    [0.9999999999990593, 1.0000000000009528],
587                    [0.9999999999999375, 1.0000000000000744],
588                ),
589                cp(
590                    [1.0502014289205344, 1.0502015605016937],
591                    [1.396835790192877, 1.3968380515018743],
592                    [0.9999999999990575, 1.000000000000955],
593                    [0.9999999999999357, 1.0000000000000766],
594                ),
595                cp(
596                    [1.050201428920532, 1.0502015605016959],
597                    [1.3968357901928743, 1.396838051501877],
598                    [0.9999999999990558, 1.0000000000009572],
599                    [0.9999999999999339, 1.0000000000000788],
600                ),
601                cp(
602                    [1.0502014289205295, 1.050201560501698],
603                    [1.3968357901928716, 1.3968380515018797],
604                    [0.999999999999054, 1.0000000000009595],
605                    [0.9999999999999322, 1.000000000000081],
606                ),
607            ],
608            [0.0, 0.0, 0.0, 0.0, 1.0, 1.0, 1.0, 1.0]
609                .map(Scalar::from_f64)
610                .to_vec(),
611        )
612        .unwrap();
613        let at = |p: [f64; 3]| Vector3::from_array(p.map(Scalar::from_f64));
614        let edge = crate::test_fixtures::line([4.0, 4.0, 1.0], [5.0, 5.0, 1.0]);
615        let nearest = least_distance(
616            vec![
617                PointPiece(at([1.0502015, 1.3968369, 0.0])),
618                PointPiece(at([4.5, 4.5, 0.0])),
619            ],
620            vec![CurveSpan(span), CurveSpan(edge)],
621            Scalar::from_f64(1e-7),
622            10_000,
623        )
624        .unwrap();
625        assert!(
626            nearest.distance.could_be_equal(Scalar::ONE),
627            "{:?}",
628            nearest.distance
629        );
630        assert!(
631            !nearest
632                .distance
633                .width()
634                .definitely_greater(Scalar::from_f64(1e-5)),
635            "{:?}",
636            nearest.distance
637        );
638    }
639
640    /// A point above the middle of a segment is its height from it, and the
641    /// enclosure of the least distance holds the true one. Where on the
642    /// segment is only as sharp as the square root of that: the distance is
643    /// flat at its least, which is why callers refine the point by Newton.
644    #[test]
645    fn point_above_a_segment() {
646        let target = Vector3::from_array([0.3, 2.0, 0.0].map(Scalar::from_f64));
647        let nearest = least_distance(
648            vec![PointPiece(target)],
649            vec![Segment(Scalar::from_f64(-1.0), Scalar::ONE)],
650            Scalar::from_f64(1e-6),
651            10_000,
652        )
653        .unwrap();
654        assert!(
655            nearest.distance.could_be_equal(Scalar::TWO),
656            "{:?}",
657            nearest.distance
658        );
659        assert!(
660            !nearest
661                .distance
662                .width()
663                .definitely_greater(Scalar::from_f64(1e-6)),
664            "{:?}",
665            nearest.distance
666        );
667    }
668}