Skip to main content

axioval_engine/
proximity.rs

1//! Source-neutral proximity evidence between two model objects.
2//!
3//! ADR 0004: the service measures how close two bodies come and how deeply
4//! they overlap; whether that is a clash, a clearance shortfall or an
5//! acceptable joint is decided by a capability against declared tolerances.
6//!
7//! Two properties of the measurement are carried explicitly rather than
8//! folded into one number:
9//!
10//! - **Fidelity.** A mesh of a curved part is a tessellation. Its chords cut
11//!   inside the true surface, so a separation measured on it can be wrong by up
12//!   to the chord deviation of each participant. Such evidence is marked
13//!   approximate and carries that deviation, so a policy can widen its
14//!   comparison instead of trusting a number the geometry cannot support.
15//! - **Penetration is witnessed, not computed.** Zero separation does not say
16//!   whether two bodies touch or interpenetrate. The adapter reports the
17//!   deepest point it found inside the other body. That is a lower bound on
18//!   the true depth. Only a closed solid has an inside; a surface entering a
19//!   solid is measured against the solid, and two open surfaces, which share
20//!   no volume, report `None`.
21//! - **Shape comparisons are intervals.** The extent of the intersection
22//!   along each axis ([`OverlapExtents`]) and the Hausdorff distance between
23//!   the two surfaces are reported as [`LengthInterval`]s the true values lie
24//!   in, so a policy judging them against a tolerance can tell a certain
25//!   answer from an open one.
26//! - **Volumes are certified intervals.** The volume two closed bodies share
27//!   and each body's own volume ([`IntersectionVolume`]) are
28//!   [`VolumeInterval`]s sure to contain the true values, never a point
29//!   estimate.
30//!
31//! - **Extents along the bodies' own axes.** World axes misjudge a wall at
32//!   an angle: a slab edge sunk 10 mm into it reaches far along x and y. An
33//!   [`OverlapAlongRequest`] names the directions to measure along (such as
34//!   each body's placement axes), and [`OverlapAlongEvidence`] answers the
35//!   intersection's extent along each as a [`LengthInterval`].
36//!
37//! A second question rides on the same service: how far a body lies from
38//! one class of another body's faces ([`FaceDistanceRequest`]), signed by
39//! whether the body lies inside the other. It is what cover and protrusion
40//! checks measure.
41
42use std::sync::Arc;
43
44use axioval_ir::{Evidence, ObjectId};
45
46use crate::{ConvexPlanRegion, LengthInterval, MetricDirection, SignedDistanceInterval};
47
48/// Why a proximity measurement could not be produced.
49#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
50pub enum ProximityError {
51    /// The adapter holds no measurable geometry for an object.
52    #[error("proximity measurement is unavailable for the requested object")]
53    Unavailable,
54    /// The object is declared to occupy no material, such as a storey or an
55    /// opening. A fact about the object, not a failure to measure it: an
56    /// object whose body could not be measured is [`Self::Unavailable`].
57    #[error("the requested object occupies no material")]
58    NoBody,
59    /// Coordinates or measured quantities are non-finite, negative or incoherent.
60    #[error("proximity measurement is not finite, non-negative and coherent")]
61    InvalidMeasurement,
62    /// The evidence's exactness disagrees with the measured geometry's fidelity.
63    #[error("proximity evidence exactness must match geometry fidelity and be reviewable")]
64    EvidenceFidelityMismatch,
65    /// A body cannot be measured against itself.
66    #[error("proximity of an object to itself is undefined")]
67    SameObject,
68    /// The request's projection is not measured by this method or service.
69    #[error("the requested proximity projection is not supported")]
70    UnsupportedProjection,
71}
72
73/// An axis-aligned box in canonical metres.
74#[derive(Clone, Copy, Debug, PartialEq)]
75pub struct Bounds3 {
76    min: [f64; 3],
77    max: [f64; 3],
78}
79
80impl Bounds3 {
81    /// Creates a box, rejecting non-finite or inverted extents.
82    pub fn try_new(min: [f64; 3], max: [f64; 3]) -> Result<Self, ProximityError> {
83        let coherent = (0..3)
84            .all(|axis| min[axis].is_finite() && max[axis].is_finite() && min[axis] <= max[axis]);
85        if !coherent {
86            return Err(ProximityError::InvalidMeasurement);
87        }
88        Ok(Self { min, max })
89    }
90    pub fn min(&self) -> [f64; 3] {
91        self.min
92    }
93    pub fn max(&self) -> [f64; 3] {
94        self.max
95    }
96    /// The box grown by `margin` metres on every side.
97    #[must_use]
98    pub fn expanded(&self, margin: f64) -> Self {
99        Self {
100            min: self.min.map(|value| value - margin),
101            max: self.max.map(|value| value + margin),
102        }
103    }
104    /// Euclidean distance between the two boxes; zero when they meet.
105    ///
106    /// A lower bound on the separation of anything the boxes enclose, which
107    /// is what makes it safe to discard pairs by it.
108    pub fn gap(&self, other: &Self) -> f64 {
109        (0..3)
110            .map(|axis| {
111                let gap = (other.min[axis] - self.max[axis])
112                    .max(self.min[axis] - other.max[axis])
113                    .max(0.0);
114                gap * gap
115            })
116            .sum::<f64>()
117            .sqrt()
118    }
119}
120
121/// How faithfully the measured geometry represents an object's true shape.
122#[derive(Clone, Copy, Debug, PartialEq)]
123pub enum GeometryFidelity {
124    /// The mesh is the shape: every face of the object is planar.
125    Exact,
126    /// The mesh approximates curved faces; no point of the true surface lies
127    /// farther than `chord_deviation_metres` from the mesh.
128    Tessellated { chord_deviation_metres: f64 },
129}
130
131impl GeometryFidelity {
132    /// A tessellation with a declared chord deviation.
133    pub fn tessellated(chord_deviation_metres: f64) -> Result<Self, ProximityError> {
134        if !chord_deviation_metres.is_finite() || chord_deviation_metres < 0.0 {
135            return Err(ProximityError::InvalidMeasurement);
136        }
137        Ok(Self::Tessellated {
138            chord_deviation_metres,
139        })
140    }
141    /// Whether measurements on this geometry are exact.
142    pub fn is_exact(&self) -> bool {
143        matches!(self, Self::Exact)
144    }
145    /// How far the true surface may lie from the measured mesh.
146    pub fn deviation_metres(&self) -> f64 {
147        match self {
148            Self::Exact => 0.0,
149            Self::Tessellated {
150                chord_deviation_metres,
151            } => *chord_deviation_metres,
152        }
153    }
154    /// Fidelity of a measurement taken between two bodies.
155    ///
156    /// Deviations add: each body's true surface may sit its own deviation away
157    /// from its mesh, in the direction that shortens or lengthens the gap.
158    #[must_use]
159    pub fn combined(self, other: Self) -> Self {
160        if self.is_exact() && other.is_exact() {
161            Self::Exact
162        } else {
163            Self::Tessellated {
164                chord_deviation_metres: self.deviation_metres() + other.deviation_metres(),
165            }
166        }
167    }
168}
169
170/// An object's axis-aligned extent, for broad-phase candidate search.
171#[derive(Clone, Debug, PartialEq)]
172pub struct ObjectBounds {
173    object: ObjectId,
174    bounds: Bounds3,
175    fidelity: GeometryFidelity,
176}
177
178impl ObjectBounds {
179    pub fn try_new(
180        object: ObjectId,
181        bounds: Bounds3,
182        fidelity: GeometryFidelity,
183    ) -> Result<Self, ProximityError> {
184        // Re-validate: `Tessellated` is constructible directly.
185        let deviation = fidelity.deviation_metres();
186        if !deviation.is_finite() || deviation < 0.0 {
187            return Err(ProximityError::InvalidMeasurement);
188        }
189        Ok(Self {
190            object,
191            bounds,
192            fidelity,
193        })
194    }
195    pub fn object(&self) -> &ObjectId {
196        &self.object
197    }
198    /// The extent of the measured mesh.
199    pub fn bounds(&self) -> Bounds3 {
200        self.bounds
201    }
202    pub fn fidelity(&self) -> GeometryFidelity {
203        self.fidelity
204    }
205    /// A box guaranteed to enclose the true body, mesh extent grown by the
206    /// chord deviation. A tessellated cylinder's true surface bulges past its
207    /// mesh, so the mesh box alone could discard a pair that really clashes.
208    pub fn enclosing(&self) -> Bounds3 {
209        self.bounds.expanded(self.fidelity.deviation_metres())
210    }
211}
212
213/// The direction in which a distance between two bodies is measured.
214///
215/// No projection's distance bounds another's from below, so the broad phase
216/// prunes each by its own box gap ([`crate::projected_candidate_pairs`]).
217#[derive(Clone, Copy, Debug)]
218pub enum ProximityProjection {
219    /// Shortest distance between the two surfaces in space.
220    Minimum3d,
221    /// Plan distance between the two footprints: zero when they meet in plan.
222    Horizontal,
223    /// Gap between the two bodies' vertical extents (bottom to top), for
224    /// bodies above or below one another. The bodies are related when their
225    /// footprints overlap with positive area or, with a positive
226    /// `footprint_offset_metres`, when the counterpart's footprint comes
227    /// closer than the offset to the subject's (the subject's footprint grown
228    /// by the offset), and when the counterpart lies in `direction` from the
229    /// subject. Unrelated bodies have no distance in this projection.
230    ///
231    /// `surfaces` other than [`VerticalSurfaces::Extents`] measure between
232    /// two chosen surfaces instead (see [`VerticalSurfaces`]).
233    Vertical {
234        footprint_offset_metres: f64,
235        direction: VerticalDirection,
236        surfaces: VerticalSurfaces,
237    },
238    /// Whether the footprints overlap with positive area: distance zero when
239    /// they do, none when they do not.
240    PlanOverlap,
241}
242
243/// Where the counterpart of a [`ProximityProjection::Vertical`] distance must
244/// lie relative to the subject.
245///
246/// Compare the two vertical extents (bottom to top) end by end. A counterpart
247/// lies **above** the subject unless it lies lower at both ends (its top
248/// below the subject's top and its bottom below the subject's bottom), and
249/// **below** unless it lies higher at both ends. A counterpart overlapping
250/// the subject in height is therefore above, below or both at distance zero:
251/// a pendant reaching down past a sprinkler's top is above it, a riser
252/// passing the sprinkler is both. Every counterpart is above or below, so the
253/// `Either` distance is the lesser of the two.
254#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
255pub enum VerticalDirection {
256    /// Above or below: the gap between the two extents.
257    Either,
258    /// The counterpart lies above: the gap from the subject's top up to the
259    /// counterpart's bottom, zero when the extents overlap.
260    Above,
261    /// The counterpart lies below: the gap from the subject's bottom down to
262    /// the counterpart's top, zero when the extents overlap.
263    Below,
264}
265
266/// Which surfaces a [`ProximityProjection::Vertical`] distance runs between.
267///
268/// A subject surface is a level: the subject's highest point (`Top`) or its
269/// lowest (`Bottom`). A counterpart's `Top` and `Bottom` are levels too, and
270/// the distance is the difference of the two levels in `direction`: a
271/// counterpart level on the other side of the subject's is unrelated
272/// (`Either` takes the absolute difference); the footprints must be related
273/// as for [`Self::Extents`]. `Nearest` is the counterpart's surface directly
274/// over or under the subject's footprint (the plan part of it overlapping
275/// the footprint with positive area) nearest to the subject's level in
276/// `direction`: a sprinkler's top to the underside of a sloped slab right
277/// above it, not to the slab's lowest point elsewhere. A counterpart with no
278/// such surface in that direction is unrelated. `Nearest` needs a footprint
279/// offset of zero.
280#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
281pub enum VerticalSurfaces {
282    /// The gap between the two vertical extents, zero when they overlap.
283    #[default]
284    Extents,
285    /// From a level of the subject to a surface of the counterpart.
286    Between {
287        subject: SubjectSurface,
288        counterpart: CounterpartSurface,
289    },
290}
291
292/// The subject's level a [`VerticalSurfaces::Between`] distance starts at.
293#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
294pub enum SubjectSurface {
295    Top,
296    Bottom,
297}
298
299/// The counterpart's surface a [`VerticalSurfaces::Between`] distance ends at.
300#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
301pub enum CounterpartSurface {
302    Top,
303    Bottom,
304    /// The nearest surface overlapping the subject's footprint in plan.
305    Nearest,
306}
307
308impl SubjectSurface {
309    /// The surface's spelling in rule parameters, evidence and messages.
310    pub fn name(self) -> &'static str {
311        match self {
312            Self::Top => "top",
313            Self::Bottom => "bottom",
314        }
315    }
316}
317
318impl CounterpartSurface {
319    /// The surface's spelling in rule parameters, evidence and messages.
320    pub fn name(self) -> &'static str {
321        match self {
322            Self::Top => "top",
323            Self::Bottom => "bottom",
324            Self::Nearest => "nearest",
325        }
326    }
327}
328
329impl VerticalDirection {
330    /// The direction's spelling in rule parameters, evidence and messages.
331    pub fn name(self) -> &'static str {
332        match self {
333            Self::Either => "either",
334            Self::Above => "above",
335            Self::Below => "below",
336        }
337    }
338}
339
340impl ProximityProjection {
341    /// Whether the projection's offset is finite and non-negative.
342    fn is_valid(&self) -> bool {
343        match self {
344            Self::Vertical {
345                footprint_offset_metres,
346                surfaces,
347                ..
348            } => {
349                let nearest = matches!(
350                    surfaces,
351                    VerticalSurfaces::Between {
352                        counterpart: CounterpartSurface::Nearest,
353                        ..
354                    }
355                );
356                footprint_offset_metres.is_finite()
357                    && *footprint_offset_metres >= 0.0
358                    && !(nearest && *footprint_offset_metres > 0.0)
359            }
360            _ => true,
361        }
362    }
363    /// Whether a pair may have no distance at all in this projection.
364    pub fn may_be_unrelated(&self) -> bool {
365        matches!(self, Self::Vertical { .. } | Self::PlanOverlap)
366    }
367    /// The projection's spelling in evidence locators and messages.
368    pub fn name(&self) -> &'static str {
369        match self {
370            Self::Minimum3d => "minimum_3d",
371            Self::Horizontal => "horizontal",
372            Self::Vertical { .. } => "vertical",
373            Self::PlanOverlap => "plan_overlap",
374        }
375    }
376    fn key(&self) -> (u8, f64, VerticalDirection, VerticalSurfaces) {
377        let none = (VerticalDirection::Either, VerticalSurfaces::Extents);
378        match self {
379            Self::Minimum3d => (0, 0.0, none.0, none.1),
380            Self::Horizontal => (1, 0.0, none.0, none.1),
381            Self::Vertical {
382                footprint_offset_metres,
383                direction,
384                surfaces,
385            } => (2, *footprint_offset_metres, *direction, *surfaces),
386            Self::PlanOverlap => (3, 0.0, none.0, none.1),
387        }
388    }
389}
390
391impl PartialEq for ProximityProjection {
392    fn eq(&self, other: &Self) -> bool {
393        self.cmp(other) == std::cmp::Ordering::Equal
394    }
395}
396impl Eq for ProximityProjection {}
397impl PartialOrd for ProximityProjection {
398    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
399        Some(self.cmp(other))
400    }
401}
402impl Ord for ProximityProjection {
403    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
404        let (a, a_offset, a_direction, a_surfaces) = self.key();
405        let (b, b_offset, b_direction, b_surfaces) = other.key();
406        a.cmp(&b)
407            .then_with(|| a_offset.total_cmp(&b_offset))
408            .then_with(|| a_direction.cmp(&b_direction))
409            .then_with(|| a_surfaces.cmp(&b_surfaces))
410    }
411}
412
413/// A request to measure the proximity of two distinct objects.
414#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
415pub struct ProximityRequest {
416    subject: ObjectId,
417    counterpart: ObjectId,
418    projection: ProximityProjection,
419}
420
421impl ProximityRequest {
422    /// A request in space ([`ProximityProjection::Minimum3d`]).
423    pub fn try_new(subject: ObjectId, counterpart: ObjectId) -> Result<Self, ProximityError> {
424        Self::projected(subject, counterpart, ProximityProjection::Minimum3d)
425    }
426    /// A request measured in `projection`; a vertical offset must be finite
427    /// and non-negative.
428    pub fn projected(
429        subject: ObjectId,
430        counterpart: ObjectId,
431        projection: ProximityProjection,
432    ) -> Result<Self, ProximityError> {
433        if subject == counterpart {
434            return Err(ProximityError::SameObject);
435        }
436        if !projection.is_valid() {
437            return Err(ProximityError::InvalidMeasurement);
438        }
439        Ok(Self {
440            subject,
441            counterpart,
442            projection,
443        })
444    }
445    pub fn subject(&self) -> &ObjectId {
446        &self.subject
447    }
448    pub fn counterpart(&self) -> &ObjectId {
449        &self.counterpart
450    }
451    pub fn projection(&self) -> ProximityProjection {
452        self.projection
453    }
454}
455
456/// How far the intersection of two bodies reaches along each world axis.
457///
458/// Each axis carries a [`LengthInterval`]: the extent of the intersection's
459/// axis-aligned box along it. The lower bound is witnessed (points found in
460/// both bodies), the upper bound proven (the bodies' boxes overlap no
461/// further), so exact geometry need not report a point. An empty
462/// intersection has zero extent on every axis.
463#[derive(Clone, Copy, Debug, PartialEq)]
464pub struct OverlapExtents {
465    axes: [LengthInterval; 3],
466}
467
468impl OverlapExtents {
469    /// The extents along x, y and z.
470    #[must_use]
471    pub fn new(x: LengthInterval, y: LengthInterval, z: LengthInterval) -> Self {
472        Self { axes: [x, y, z] }
473    }
474    pub fn x(&self) -> LengthInterval {
475        self.axes[0]
476    }
477    pub fn y(&self) -> LengthInterval {
478        self.axes[1]
479    }
480    pub fn z(&self) -> LengthInterval {
481        self.axes[2]
482    }
483    /// The lesser of the x and y extents: how far the intersection reaches in
484    /// plan along its narrower axis.
485    pub fn horizontal(&self) -> LengthInterval {
486        let (x, y) = (self.axes[0], self.axes[1]);
487        LengthInterval::try_new(
488            x.lower_metres().min(y.lower_metres()),
489            x.upper_metres().min(y.upper_metres()),
490        )
491        .unwrap_or_else(|_| unreachable!("the lesser of two intervals is an interval"))
492    }
493    /// The z extent.
494    pub fn vertical(&self) -> LengthInterval {
495        self.axes[2]
496    }
497    fn is_empty(&self) -> bool {
498        self.axes.iter().all(|axis| axis.lower_metres() == 0.0)
499    }
500}
501
502/// A request for the extents of two bodies' intersection along stated
503/// directions, such as each body's own placement axes.
504#[derive(Clone, Debug, PartialEq)]
505pub struct OverlapAlongRequest {
506    subject: ObjectId,
507    counterpart: ObjectId,
508    directions: Vec<MetricDirection>,
509}
510
511impl OverlapAlongRequest {
512    /// The most directions one request may name: two bodies' three axes.
513    pub const MAX_DIRECTIONS: usize = 6;
514
515    /// Refuses one object measured against itself, and no directions or
516    /// more than [`Self::MAX_DIRECTIONS`].
517    pub fn try_new(
518        subject: ObjectId,
519        counterpart: ObjectId,
520        directions: Vec<MetricDirection>,
521    ) -> Result<Self, ProximityError> {
522        if subject == counterpart {
523            return Err(ProximityError::SameObject);
524        }
525        if directions.is_empty() || directions.len() > Self::MAX_DIRECTIONS {
526            return Err(ProximityError::InvalidMeasurement);
527        }
528        Ok(Self {
529            subject,
530            counterpart,
531            directions,
532        })
533    }
534    pub fn subject(&self) -> &ObjectId {
535        &self.subject
536    }
537    pub fn counterpart(&self) -> &ObjectId {
538        &self.counterpart
539    }
540    /// The unit directions to measure along, in request order.
541    pub fn directions(&self) -> &[MetricDirection] {
542        &self.directions
543    }
544}
545
546/// How far two bodies' intersection reaches along each direction of an
547/// [`OverlapAlongRequest`].
548///
549/// Each extent is a [`LengthInterval`] as for [`OverlapExtents`]: its lower
550/// bound witnessed (points found in both bodies), its upper bound proven
551/// (the bodies' own extents along the direction overlap no further). An
552/// empty intersection has zero extent along every direction. The evidence
553/// is exact exactly when the geometry is.
554#[derive(Clone, Debug, PartialEq)]
555pub struct OverlapAlongEvidence {
556    request: OverlapAlongRequest,
557    extents: Vec<LengthInterval>,
558    fidelity: GeometryFidelity,
559    evidence: Evidence,
560}
561
562impl OverlapAlongEvidence {
563    /// Refuses one extent too many or too few, and evidence whose
564    /// exactness does not match the geometry.
565    pub fn try_new(
566        request: OverlapAlongRequest,
567        extents: Vec<LengthInterval>,
568        fidelity: GeometryFidelity,
569        evidence: Evidence,
570    ) -> Result<Self, ProximityError> {
571        if extents.len() != request.directions.len() {
572            return Err(ProximityError::InvalidMeasurement);
573        }
574        if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
575            return Err(ProximityError::EvidenceFidelityMismatch);
576        }
577        Ok(Self {
578            request,
579            extents,
580            fidelity,
581            evidence,
582        })
583    }
584    pub fn request(&self) -> &OverlapAlongRequest {
585        &self.request
586    }
587    /// The extent along each requested direction, in request order.
588    pub fn extents(&self) -> &[LengthInterval] {
589        &self.extents
590    }
591    pub fn fidelity(&self) -> GeometryFidelity {
592        self.fidelity
593    }
594    pub fn evidence(&self) -> &Evidence {
595        &self.evidence
596    }
597}
598
599/// One body lying wholly inside the other without their surfaces meeting.
600#[derive(Clone, Copy, Debug, PartialEq, Eq)]
601pub enum BodyContainment {
602    SubjectInsideCounterpart,
603    CounterpartInsideSubject,
604}
605
606/// Bounds on a volume in cubic metres: finite, non-negative, and sure to
607/// contain the true value.
608#[derive(Clone, Copy, Debug, PartialEq)]
609pub struct VolumeInterval {
610    lower_cubic_metres: f64,
611    upper_cubic_metres: f64,
612}
613
614impl VolumeInterval {
615    /// Validates inclusive lower and upper bounds.
616    pub fn try_new(
617        lower_cubic_metres: f64,
618        upper_cubic_metres: f64,
619    ) -> Result<Self, ProximityError> {
620        let valid = |value: f64| value.is_finite() && value >= 0.0;
621        if !valid(lower_cubic_metres)
622            || !valid(upper_cubic_metres)
623            || lower_cubic_metres > upper_cubic_metres
624        {
625            return Err(ProximityError::InvalidMeasurement);
626        }
627        Ok(Self {
628            lower_cubic_metres,
629            upper_cubic_metres,
630        })
631    }
632    /// A volume known exactly.
633    pub fn exact(cubic_metres: f64) -> Result<Self, ProximityError> {
634        Self::try_new(cubic_metres, cubic_metres)
635    }
636    pub fn lower_cubic_metres(&self) -> f64 {
637        self.lower_cubic_metres
638    }
639    pub fn upper_cubic_metres(&self) -> f64 {
640        self.upper_cubic_metres
641    }
642    /// Whether the interval proves one value.
643    #[allow(clippy::float_cmp)]
644    pub fn is_exact(&self) -> bool {
645        self.lower_cubic_metres == self.upper_cubic_metres
646    }
647    /// `(lower, upper)` bounds on this volume's share of `whole`, for a
648    /// part that lies within the whole: outward rounded and clamped to
649    /// `[0, 1]`, so the true share always lies inside.
650    pub fn share_of(self, whole: Self) -> (f64, f64) {
651        let lower = if whole.upper_cubic_metres > 0.0 {
652            (self.lower_cubic_metres / whole.upper_cubic_metres).next_down()
653        } else {
654            0.0
655        };
656        let upper = if whole.lower_cubic_metres > 0.0 {
657            (self.upper_cubic_metres / whole.lower_cubic_metres).next_up()
658        } else {
659            1.0
660        };
661        let upper = upper.clamp(0.0, 1.0);
662        (lower.clamp(0.0, upper), upper)
663    }
664}
665
666/// The volume two closed bodies share, with each body's own volume.
667///
668/// Every value is a certified [`VolumeInterval`]. The shared volume cannot
669/// exceed either body's, so bounds claiming it must are refused.
670#[derive(Clone, Copy, Debug, PartialEq)]
671pub struct IntersectionVolume {
672    shared: VolumeInterval,
673    subject: VolumeInterval,
674    counterpart: VolumeInterval,
675}
676
677impl IntersectionVolume {
678    /// The shared volume and the volumes of the request's subject and
679    /// counterpart.
680    pub fn try_new(
681        shared: VolumeInterval,
682        subject: VolumeInterval,
683        counterpart: VolumeInterval,
684    ) -> Result<Self, ProximityError> {
685        let most = subject
686            .upper_cubic_metres
687            .min(counterpart.upper_cubic_metres);
688        if shared.lower_cubic_metres > most {
689            return Err(ProximityError::InvalidMeasurement);
690        }
691        Ok(Self {
692            shared,
693            subject,
694            counterpart,
695        })
696    }
697    /// The volume both bodies occupy.
698    pub fn shared(&self) -> VolumeInterval {
699        self.shared
700    }
701    /// The subject's own volume.
702    pub fn subject(&self) -> VolumeInterval {
703        self.subject
704    }
705    /// The counterpart's own volume.
706    pub fn counterpart(&self) -> VolumeInterval {
707        self.counterpart
708    }
709    /// The volume of the smaller body: the lesser of the two volumes.
710    pub fn smaller(&self) -> VolumeInterval {
711        VolumeInterval {
712            lower_cubic_metres: self
713                .subject
714                .lower_cubic_metres
715                .min(self.counterpart.lower_cubic_metres),
716            upper_cubic_metres: self
717                .subject
718                .upper_cubic_metres
719                .min(self.counterpart.upper_cubic_metres),
720        }
721    }
722    /// `(lower, upper)` bounds on the shared volume's share of the smaller
723    /// body, between zero and one: one when one body lies wholly in the
724    /// other. Rounded outward, so the true ratio always lies inside.
725    pub fn ratio_of_smaller(&self) -> (f64, f64) {
726        self.shared.share_of(self.smaller())
727    }
728}
729
730/// The certified volume one closed body encloses.
731///
732/// A tessellated body's volume is widened by the band its chord deviation
733/// allows, so the interval always holds the true volume; only an exact mesh
734/// may carry exact evidence.
735#[derive(Clone, Debug, PartialEq)]
736pub struct BodyVolume {
737    object: ObjectId,
738    volume: VolumeInterval,
739    fidelity: GeometryFidelity,
740    evidence: Evidence,
741}
742
743impl BodyVolume {
744    /// The volume of `object`. Exact evidence needs exact geometry, and
745    /// every piece of evidence needs a reviewable locator.
746    pub fn try_new(
747        object: ObjectId,
748        volume: VolumeInterval,
749        fidelity: GeometryFidelity,
750        evidence: Evidence,
751    ) -> Result<Self, ProximityError> {
752        if evidence.exact && !fidelity.is_exact() || evidence.locator.trim().is_empty() {
753            return Err(ProximityError::EvidenceFidelityMismatch);
754        }
755        Ok(Self {
756            object,
757            volume,
758            fidelity,
759            evidence,
760        })
761    }
762    /// The measured object.
763    pub fn object(&self) -> &ObjectId {
764        &self.object
765    }
766    /// Bounds on the enclosed volume.
767    pub fn volume(&self) -> VolumeInterval {
768        self.volume
769    }
770    pub fn fidelity(&self) -> GeometryFidelity {
771        self.fidelity
772    }
773    pub fn evidence(&self) -> &Evidence {
774        &self.evidence
775    }
776}
777
778/// How close two bodies come and how far they overlap.
779#[derive(Clone, Debug, PartialEq)]
780pub struct ProximityEvidence {
781    request: ProximityRequest,
782    separation_metres: f64,
783    penetration_metres: Option<f64>,
784    plan_overlap_square_metres: f64,
785    containment: Option<BodyContainment>,
786    overlap_extents: Option<OverlapExtents>,
787    hausdorff: Option<LengthInterval>,
788    volume: Option<IntersectionVolume>,
789    fidelity: GeometryFidelity,
790    evidence: Evidence,
791}
792
793impl ProximityEvidence {
794    /// Rejects incoherent measurements and evidence whose exactness does not
795    /// match the geometry it was measured on.
796    ///
797    /// - `separation_metres`: shortest distance between the two surfaces.
798    /// - `penetration_metres`: depth of the deepest witnessed point of either
799    ///   body inside the other; `None` when neither body is a closed solid.
800    /// - `plan_overlap_square_metres`: area of the two footprints' overlap.
801    pub fn try_new(
802        request: ProximityRequest,
803        separation_metres: f64,
804        penetration_metres: Option<f64>,
805        plan_overlap_square_metres: f64,
806        containment: Option<BodyContainment>,
807        fidelity: GeometryFidelity,
808        evidence: Evidence,
809    ) -> Result<Self, ProximityError> {
810        let finite_non_negative = |v: f64| v.is_finite() && v >= 0.0;
811        if !finite_non_negative(separation_metres)
812            || !finite_non_negative(plan_overlap_square_metres)
813            || penetration_metres.is_some_and(|depth| !finite_non_negative(depth))
814            || !finite_non_negative(fidelity.deviation_metres())
815        {
816            return Err(ProximityError::InvalidMeasurement);
817        }
818        // Disjoint bodies cannot share volume. BodyContainment is the one way to be
819        // apart at the surface and overlapping in volume, and it needs an
820        // inside, so it cannot be claimed without a penetration measurement.
821        let separated = separation_metres > 0.0;
822        if (separated && containment.is_none() && penetration_metres.is_some_and(|d| d > 0.0))
823            || (containment.is_some() && (!separated || penetration_metres.is_none()))
824        {
825            return Err(ProximityError::InvalidMeasurement);
826        }
827        if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
828            return Err(ProximityError::EvidenceFidelityMismatch);
829        }
830        // Penetration and containment are questions in space.
831        if request.projection() != ProximityProjection::Minimum3d {
832            return Err(ProximityError::UnsupportedProjection);
833        }
834        Ok(Self {
835            request,
836            separation_metres,
837            penetration_metres,
838            plan_overlap_square_metres,
839            containment,
840            overlap_extents: None,
841            hausdorff: None,
842            volume: None,
843            fidelity,
844            evidence,
845        })
846    }
847
848    /// Adds the extents of the bodies' intersection along each axis.
849    ///
850    /// Refused when no penetration was measured (two open surfaces share no
851    /// volume), and when bodies apart at the surface and not contained in
852    /// one another claim a non-empty intersection.
853    pub fn with_overlap_extents(mut self, extents: OverlapExtents) -> Result<Self, ProximityError> {
854        let disjoint = self.separation_metres > 0.0 && self.containment.is_none();
855        if self.penetration_metres.is_none() || (disjoint && !extents.is_empty()) {
856            return Err(ProximityError::InvalidMeasurement);
857        }
858        self.overlap_extents = Some(extents);
859        Ok(self)
860    }
861
862    /// Adds the Hausdorff distance between the two surfaces: the farthest
863    /// any point of either surface lies from the other surface.
864    ///
865    /// It is never smaller than the separation, so an interval lying wholly
866    /// below the separation's own interval is refused.
867    pub fn with_hausdorff(mut self, interval: LengthInterval) -> Result<Self, ProximityError> {
868        if interval.upper_metres() < self.separation_interval_metres().0 {
869            return Err(ProximityError::InvalidMeasurement);
870        }
871        self.hausdorff = Some(interval);
872        Ok(self)
873    }
874
875    /// Adds the certified volume the two bodies share, with their own
876    /// volumes.
877    ///
878    /// Refused when no penetration was measured (open surfaces enclose no
879    /// volume), when bodies apart at the surface and not contained in one
880    /// another claim a shared volume, and when a body said to lie inside
881    /// the other cannot share its whole volume with it.
882    pub fn with_intersection_volume(
883        mut self,
884        volume: IntersectionVolume,
885    ) -> Result<Self, ProximityError> {
886        let disjoint = self.separation_metres > 0.0 && self.containment.is_none();
887        let shared = volume.shared();
888        let whole = |inner: VolumeInterval| {
889            shared.upper_cubic_metres >= inner.lower_cubic_metres
890                && shared.lower_cubic_metres <= inner.upper_cubic_metres
891        };
892        let contained = match self.containment {
893            Some(BodyContainment::SubjectInsideCounterpart) => whole(volume.subject()),
894            Some(BodyContainment::CounterpartInsideSubject) => whole(volume.counterpart()),
895            None => true,
896        };
897        if self.penetration_metres.is_none()
898            || (disjoint && shared.lower_cubic_metres > 0.0)
899            || !contained
900        {
901            return Err(ProximityError::InvalidMeasurement);
902        }
903        self.volume = Some(volume);
904        Ok(self)
905    }
906
907    pub fn request(&self) -> &ProximityRequest {
908        &self.request
909    }
910    /// Shortest distance between the measured surfaces.
911    pub fn separation_metres(&self) -> f64 {
912        self.separation_metres
913    }
914    /// The separation the true surfaces may have, given the fidelity.
915    ///
916    /// `(lower, upper)`; both equal the measured separation for exact geometry.
917    pub fn separation_interval_metres(&self) -> (f64, f64) {
918        let deviation = self.fidelity.deviation_metres();
919        (
920            (self.separation_metres - deviation).max(0.0),
921            self.separation_metres + deviation,
922        )
923    }
924    /// Witnessed interpenetration depth, a lower bound on the true depth.
925    pub fn penetration_metres(&self) -> Option<f64> {
926        self.penetration_metres
927    }
928    pub fn plan_overlap_square_metres(&self) -> f64 {
929        self.plan_overlap_square_metres
930    }
931    pub fn containment(&self) -> Option<BodyContainment> {
932        self.containment
933    }
934    /// Extents of the intersection along each axis; `None` when the service
935    /// did not measure them or neither body is a closed solid.
936    pub fn overlap_extents(&self) -> Option<OverlapExtents> {
937        self.overlap_extents
938    }
939    /// Hausdorff distance between the two surfaces; `None` when the service
940    /// did not measure it. Zero exactly when the surfaces coincide, so it is
941    /// what tells a duplicate from a mere overlap.
942    pub fn hausdorff_interval_metres(&self) -> Option<LengthInterval> {
943        self.hausdorff
944    }
945    /// The certified volume the bodies share and their own volumes; `None`
946    /// when the service did not measure it or a body is not a closed solid.
947    pub fn intersection_volume(&self) -> Option<IntersectionVolume> {
948        self.volume
949    }
950    pub fn fidelity(&self) -> GeometryFidelity {
951        self.fidelity
952    }
953    pub fn evidence(&self) -> &Evidence {
954        &self.evidence
955    }
956}
957
958/// The distance between two bodies in a request's projection, as an interval.
959///
960/// `(lower, upper)` in metres, both equal for exact geometry. An upper bound
961/// of infinity says the bodies may be unrelated in the projection (not above
962/// one another, not overlapping in plan); a lower bound of infinity says they
963/// are. Only [`ProximityProjection::may_be_unrelated`] projections may report
964/// an infinite bound, and exact evidence is a point: whether exact bodies are
965/// related is decided, never left open.
966#[derive(Clone, Debug, PartialEq)]
967pub struct ProjectedDistanceEvidence {
968    request: ProximityRequest,
969    lower_metres: f64,
970    upper_metres: f64,
971    fidelity: GeometryFidelity,
972    evidence: Evidence,
973}
974
975impl ProjectedDistanceEvidence {
976    /// Rejects incoherent intervals and evidence whose exactness does not
977    /// match the geometry it was measured on.
978    pub fn try_new(
979        request: ProximityRequest,
980        lower_metres: f64,
981        upper_metres: f64,
982        fidelity: GeometryFidelity,
983        evidence: Evidence,
984    ) -> Result<Self, ProximityError> {
985        let deviation = fidelity.deviation_metres();
986        let unbounded_allowed = request.projection().may_be_unrelated();
987        let bound_ok = |value: f64| {
988            value >= 0.0 && (value.is_finite() || (unbounded_allowed && value == f64::INFINITY))
989        };
990        if !bound_ok(lower_metres)
991            || !bound_ok(upper_metres)
992            || lower_metres > upper_metres
993            || !deviation.is_finite()
994            || deviation < 0.0
995            || (fidelity.is_exact() && lower_metres < upper_metres)
996        {
997            return Err(ProximityError::InvalidMeasurement);
998        }
999        if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
1000            return Err(ProximityError::EvidenceFidelityMismatch);
1001        }
1002        Ok(Self {
1003            request,
1004            lower_metres,
1005            upper_metres,
1006            fidelity,
1007            evidence,
1008        })
1009    }
1010
1011    /// The distance interval of a measurement in space.
1012    pub fn from_proximity(measured: &ProximityEvidence) -> Result<Self, ProximityError> {
1013        let (lower, upper) = measured.separation_interval_metres();
1014        Self::try_new(
1015            measured.request().clone(),
1016            lower,
1017            upper,
1018            measured.fidelity(),
1019            measured.evidence().clone(),
1020        )
1021    }
1022
1023    pub fn request(&self) -> &ProximityRequest {
1024        &self.request
1025    }
1026    /// `(lower, upper)` bounds on the true distance; infinite when unrelated.
1027    pub fn interval_metres(&self) -> (f64, f64) {
1028        (self.lower_metres, self.upper_metres)
1029    }
1030    pub fn fidelity(&self) -> GeometryFidelity {
1031        self.fidelity
1032    }
1033    pub fn evidence(&self) -> &Evidence {
1034        &self.evidence
1035    }
1036}
1037
1038/// A request for the plan distance from a stated convex region to an
1039/// object's footprint, such as from the floor area a door leaf sweeps to a
1040/// column.
1041#[derive(Clone, Debug, PartialEq)]
1042pub struct RegionDistanceRequest {
1043    region: ConvexPlanRegion,
1044    counterpart: ObjectId,
1045}
1046
1047impl RegionDistanceRequest {
1048    pub fn new(region: ConvexPlanRegion, counterpart: ObjectId) -> Self {
1049        Self {
1050            region,
1051            counterpart,
1052        }
1053    }
1054    pub fn region(&self) -> &ConvexPlanRegion {
1055        &self.region
1056    }
1057    pub fn counterpart(&self) -> &ObjectId {
1058        &self.counterpart
1059    }
1060}
1061
1062/// The plan distance from a region to an object's footprint, as an
1063/// interval: zero when they meet in plan, a point exactly when the
1064/// counterpart's geometry is exact.
1065#[derive(Clone, Debug, PartialEq)]
1066pub struct RegionDistanceEvidence {
1067    request: RegionDistanceRequest,
1068    lower_metres: f64,
1069    upper_metres: f64,
1070    fidelity: GeometryFidelity,
1071    evidence: Evidence,
1072}
1073
1074impl RegionDistanceEvidence {
1075    /// Rejects incoherent intervals, evidence whose exactness does not match
1076    /// the counterpart's geometry, and evidence from another source than the
1077    /// counterpart's.
1078    pub fn try_new(
1079        request: RegionDistanceRequest,
1080        lower_metres: f64,
1081        upper_metres: f64,
1082        fidelity: GeometryFidelity,
1083        evidence: Evidence,
1084    ) -> Result<Self, ProximityError> {
1085        let bound_ok = |value: f64| value.is_finite() && value >= 0.0;
1086        if !bound_ok(lower_metres)
1087            || !bound_ok(upper_metres)
1088            || lower_metres > upper_metres
1089            || (fidelity.is_exact() && lower_metres < upper_metres)
1090        {
1091            return Err(ProximityError::InvalidMeasurement);
1092        }
1093        if evidence.exact != fidelity.is_exact()
1094            || evidence.locator.trim().is_empty()
1095            || evidence.source != request.counterpart().source
1096        {
1097            return Err(ProximityError::EvidenceFidelityMismatch);
1098        }
1099        Ok(Self {
1100            request,
1101            lower_metres,
1102            upper_metres,
1103            fidelity,
1104            evidence,
1105        })
1106    }
1107    pub fn request(&self) -> &RegionDistanceRequest {
1108        &self.request
1109    }
1110    /// `(lower, upper)` bounds on the true plan distance.
1111    pub fn interval_metres(&self) -> (f64, f64) {
1112        (self.lower_metres, self.upper_metres)
1113    }
1114    pub fn fidelity(&self) -> GeometryFidelity {
1115        self.fidelity
1116    }
1117    pub fn evidence(&self) -> &Evidence {
1118        &self.evidence
1119    }
1120}
1121
1122/// Which faces of a body a [`FaceDistanceRequest`] measures to, by the
1123/// direction of their outward normal.
1124///
1125/// A face is **top** when its outward normal points upwards within 45° of
1126/// vertical (its z component is at least √½), **bottom** when it points
1127/// downwards within 45°, and a **side** face otherwise. `Any` takes every
1128/// face.
1129#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
1130pub enum FaceClass {
1131    Top,
1132    Side,
1133    Bottom,
1134    Any,
1135}
1136
1137impl FaceClass {
1138    /// The class's spelling in rule parameters, evidence and messages.
1139    pub fn name(self) -> &'static str {
1140        match self {
1141            Self::Top => "top",
1142            Self::Side => "side",
1143            Self::Bottom => "bottom",
1144            Self::Any => "any",
1145        }
1146    }
1147    /// Parses [`Self::name`].
1148    pub fn parse(name: &str) -> Option<Self> {
1149        [Self::Top, Self::Side, Self::Bottom, Self::Any]
1150            .into_iter()
1151            .find(|class| class.name() == name)
1152    }
1153}
1154
1155/// Why a face distance could not be measured.
1156#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
1157pub enum FaceDistanceError {
1158    /// The service does not measure face distances.
1159    #[error("face distances are not measured by this service")]
1160    Unsupported,
1161    /// The adapter holds no measurable geometry for an object.
1162    #[error("face distance is unavailable for the requested object")]
1163    Unavailable,
1164    /// An object is declared to occupy no material.
1165    #[error("the requested object occupies no material")]
1166    NoBody,
1167    /// The host is not a closed solid, so it has no inside and no outward
1168    /// normals.
1169    #[error("the host body is not a closed solid")]
1170    NotClosed,
1171    /// The host has no face of the requested class.
1172    #[error("the host body has no face of the requested class")]
1173    NoFaces,
1174    /// The host is a tessellation: its chords do not state the true faces'
1175    /// normals, so no face class can be read from them.
1176    #[error("the host body is tessellated, so its face classes are unknown")]
1177    InexactHost,
1178    /// A face of the host lies at the 45° boundary between two classes
1179    /// within rounding, so which class it belongs to is undecided.
1180    #[error("a face of the host lies on the boundary between two face classes")]
1181    AmbiguousFace,
1182    /// Coordinates or measured quantities are non-finite or incoherent.
1183    #[error("face distance is not finite and coherent")]
1184    InvalidMeasurement,
1185    /// The evidence's exactness disagrees with the measured geometry's fidelity.
1186    #[error("face distance evidence exactness must match geometry fidelity and be reviewable")]
1187    EvidenceFidelityMismatch,
1188    /// A body cannot be measured against its own faces.
1189    #[error("the face distance of an object to itself is undefined")]
1190    SameObject,
1191}
1192
1193/// A request for the signed distance from one body to one class of another
1194/// body's faces.
1195#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
1196pub struct FaceDistanceRequest {
1197    body: ObjectId,
1198    host: ObjectId,
1199    faces: FaceClass,
1200}
1201
1202impl FaceDistanceRequest {
1203    /// The distance from `body` to the `faces` of `host`.
1204    pub fn try_new(
1205        body: ObjectId,
1206        host: ObjectId,
1207        faces: FaceClass,
1208    ) -> Result<Self, FaceDistanceError> {
1209        if body == host {
1210            return Err(FaceDistanceError::SameObject);
1211        }
1212        Ok(Self { body, host, faces })
1213    }
1214    /// The body measured from.
1215    pub fn body(&self) -> &ObjectId {
1216        &self.body
1217    }
1218    /// The body whose faces are measured to.
1219    pub fn host(&self) -> &ObjectId {
1220        &self.host
1221    }
1222    pub fn faces(&self) -> FaceClass {
1223        self.faces
1224    }
1225}
1226
1227/// The signed distance from a body to a class of a host body's faces.
1228///
1229/// With `F` the host's faces of the class, each point `p` of the body has
1230/// the signed distance `+d(p, F)` when it lies in the host (boundary
1231/// included) and `-d(p, F)` when it lies outside. The body's distance is the
1232/// least over its points:
1233///
1234/// - **positive**: the whole body lies in the host, that far from the faces
1235///   (a cover);
1236/// - **negative**: part of the body lies outside the host, the farthest of
1237///   it that far from the faces (a protrusion);
1238/// - **zero**: the body reaches the faces.
1239///
1240/// It is a [`SignedDistanceInterval`] sure to contain the true value, a
1241/// point only when the measurement proves one.
1242#[derive(Clone, Debug, PartialEq)]
1243pub struct FaceDistanceEvidence {
1244    request: FaceDistanceRequest,
1245    signed: SignedDistanceInterval,
1246    fidelity: GeometryFidelity,
1247    evidence: Evidence,
1248}
1249
1250impl FaceDistanceEvidence {
1251    /// Rejects evidence whose exactness does not match the geometry it was
1252    /// measured on.
1253    pub fn try_new(
1254        request: FaceDistanceRequest,
1255        signed: SignedDistanceInterval,
1256        fidelity: GeometryFidelity,
1257        evidence: Evidence,
1258    ) -> Result<Self, FaceDistanceError> {
1259        let deviation = fidelity.deviation_metres();
1260        if !deviation.is_finite() || deviation < 0.0 {
1261            return Err(FaceDistanceError::InvalidMeasurement);
1262        }
1263        if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
1264            return Err(FaceDistanceError::EvidenceFidelityMismatch);
1265        }
1266        Ok(Self {
1267            request,
1268            signed,
1269            fidelity,
1270            evidence,
1271        })
1272    }
1273    pub fn request(&self) -> &FaceDistanceRequest {
1274        &self.request
1275    }
1276    /// Bounds on the signed distance: positive inside the host, negative
1277    /// outside.
1278    pub fn signed(&self) -> SignedDistanceInterval {
1279        self.signed
1280    }
1281    pub fn fidelity(&self) -> GeometryFidelity {
1282        self.fidelity
1283    }
1284    pub fn evidence(&self) -> &Evidence {
1285        &self.evidence
1286    }
1287}
1288
1289/// Measures extents and pairwise proximity of model objects.
1290///
1291/// ADR 0004: every method returns a measurement. None decides a clash.
1292pub trait ProximityService: Send + Sync + 'static {
1293    /// The extent of one object's measured geometry.
1294    fn bounds(&self, object: &ObjectId) -> Result<ObjectBounds, ProximityError>;
1295    /// How close two objects come and how far they overlap, in space.
1296    ///
1297    /// A request in any other projection is refused with
1298    /// [`ProximityError::UnsupportedProjection`].
1299    fn measure_proximity(
1300        &self,
1301        request: &ProximityRequest,
1302    ) -> Result<ProximityEvidence, ProximityError>;
1303    /// The distance between two objects in the request's projection.
1304    ///
1305    /// The default answers [`ProximityProjection::Minimum3d`] from
1306    /// [`Self::measure_proximity`] and refuses every other projection, so a
1307    /// service that does not measure projections fails closed.
1308    fn measure_distance(
1309        &self,
1310        request: &ProximityRequest,
1311    ) -> Result<ProjectedDistanceEvidence, ProximityError> {
1312        match request.projection() {
1313            ProximityProjection::Minimum3d => {
1314                ProjectedDistanceEvidence::from_proximity(&self.measure_proximity(request)?)
1315            }
1316            _ => Err(ProximityError::UnsupportedProjection),
1317        }
1318    }
1319    /// The signed distance from one body to a class of another's faces.
1320    ///
1321    /// The default refuses with [`FaceDistanceError::Unsupported`], so a
1322    /// service that does not measure face distances fails closed.
1323    fn measure_face_distance(
1324        &self,
1325        request: &FaceDistanceRequest,
1326    ) -> Result<FaceDistanceEvidence, FaceDistanceError> {
1327        let _ = request;
1328        Err(FaceDistanceError::Unsupported)
1329    }
1330    /// The plan distance from a stated region to an object's footprint.
1331    ///
1332    /// The default refuses with [`ProximityError::UnsupportedProjection`],
1333    /// so a service that does not measure regions fails closed.
1334    fn measure_region_distance(
1335        &self,
1336        request: &RegionDistanceRequest,
1337    ) -> Result<RegionDistanceEvidence, ProximityError> {
1338        let _ = request;
1339        Err(ProximityError::UnsupportedProjection)
1340    }
1341    /// The extents of two bodies' intersection along stated directions.
1342    ///
1343    /// The default refuses with [`ProximityError::UnsupportedProjection`],
1344    /// so a service that does not measure them fails closed.
1345    fn measure_overlap_along(
1346        &self,
1347        request: &OverlapAlongRequest,
1348    ) -> Result<OverlapAlongEvidence, ProximityError> {
1349        let _ = request;
1350        Err(ProximityError::UnsupportedProjection)
1351    }
1352
1353    /// The volume one closed body encloses.
1354    ///
1355    /// The default refuses with [`ProximityError::Unavailable`], so a
1356    /// service that does not measure volumes fails closed.
1357    fn measure_body_volume(&self, object: &ObjectId) -> Result<BodyVolume, ProximityError> {
1358        let _ = object;
1359        Err(ProximityError::Unavailable)
1360    }
1361}
1362
1363/// Registry handle for a [`ProximityService`].
1364#[derive(Clone)]
1365pub struct ProximityServiceHandle(Arc<dyn ProximityService>);
1366
1367impl ProximityServiceHandle {
1368    pub fn new(service: Arc<dyn ProximityService>) -> Self {
1369        Self(service)
1370    }
1371    pub fn bounds(&self, object: &ObjectId) -> Result<ObjectBounds, ProximityError> {
1372        self.0.bounds(object)
1373    }
1374    pub fn measure_proximity(
1375        &self,
1376        request: &ProximityRequest,
1377    ) -> Result<ProximityEvidence, ProximityError> {
1378        self.0.measure_proximity(request)
1379    }
1380    /// The distance in the request's projection. Evidence answering another
1381    /// request, projection included, is refused.
1382    pub fn measure_distance(
1383        &self,
1384        request: &ProximityRequest,
1385    ) -> Result<ProjectedDistanceEvidence, ProximityError> {
1386        let measured = self.0.measure_distance(request)?;
1387        if measured.request() != request {
1388            return Err(ProximityError::InvalidMeasurement);
1389        }
1390        Ok(measured)
1391    }
1392    /// The signed face distance. Evidence answering another request is
1393    /// refused.
1394    pub fn measure_face_distance(
1395        &self,
1396        request: &FaceDistanceRequest,
1397    ) -> Result<FaceDistanceEvidence, FaceDistanceError> {
1398        let measured = self.0.measure_face_distance(request)?;
1399        if measured.request() != request {
1400            return Err(FaceDistanceError::InvalidMeasurement);
1401        }
1402        Ok(measured)
1403    }
1404    /// The intersection's extents along stated directions. Evidence
1405    /// answering another request is refused.
1406    pub fn measure_overlap_along(
1407        &self,
1408        request: &OverlapAlongRequest,
1409    ) -> Result<OverlapAlongEvidence, ProximityError> {
1410        let measured = self.0.measure_overlap_along(request)?;
1411        if measured.request() != request {
1412            return Err(ProximityError::InvalidMeasurement);
1413        }
1414        Ok(measured)
1415    }
1416    /// The volume `object` encloses. A volume naming another object answers
1417    /// a different question and is refused.
1418    pub fn measure_body_volume(&self, object: &ObjectId) -> Result<BodyVolume, ProximityError> {
1419        let measured = self.0.measure_body_volume(object)?;
1420        if measured.object() != object {
1421            return Err(ProximityError::InvalidMeasurement);
1422        }
1423        Ok(measured)
1424    }
1425    /// The plan distance from a region. Evidence answering another request
1426    /// is refused.
1427    pub fn measure_region_distance(
1428        &self,
1429        request: &RegionDistanceRequest,
1430    ) -> Result<RegionDistanceEvidence, ProximityError> {
1431        let measured = self.0.measure_region_distance(request)?;
1432        if measured.request() != request {
1433            return Err(ProximityError::InvalidMeasurement);
1434        }
1435        Ok(measured)
1436    }
1437}
1438
1439#[cfg(test)]
1440mod tests {
1441    use super::*;
1442    use axioval_ir::SourceId;
1443
1444    fn id(local: &str) -> ObjectId {
1445        ObjectId::new(SourceId::new("cad", "m").unwrap(), local).unwrap()
1446    }
1447    fn request() -> ProximityRequest {
1448        ProximityRequest::try_new(id("pipe"), id("wall")).unwrap()
1449    }
1450
1451    /// A service answering one volume whatever it is asked.
1452    struct OneVolume(BodyVolume);
1453
1454    #[test]
1455    fn a_body_volume_keeps_its_exactness_honest_and_names_its_object() {
1456        let tessellated = GeometryFidelity::Tessellated {
1457            chord_deviation_metres: 0.01,
1458        };
1459        let volume = VolumeInterval::try_new(1.0, 1.1).unwrap();
1460        assert_eq!(
1461            BodyVolume::try_new(id("wall"), volume, tessellated, exact()),
1462            Err(ProximityError::EvidenceFidelityMismatch)
1463        );
1464        let measured = BodyVolume::try_new(id("wall"), volume, tessellated, approximate()).unwrap();
1465        let handle = ProximityServiceHandle::new(Arc::new(OneVolume(measured.clone())));
1466        assert_eq!(handle.measure_body_volume(&id("wall")), Ok(measured));
1467        assert_eq!(
1468            handle.measure_body_volume(&id("pipe")),
1469            Err(ProximityError::InvalidMeasurement)
1470        );
1471    }
1472
1473    impl ProximityService for OneVolume {
1474        fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
1475            Err(ProximityError::Unavailable)
1476        }
1477        fn measure_proximity(
1478            &self,
1479            _: &ProximityRequest,
1480        ) -> Result<ProximityEvidence, ProximityError> {
1481            Err(ProximityError::Unavailable)
1482        }
1483        fn measure_body_volume(&self, _: &ObjectId) -> Result<BodyVolume, ProximityError> {
1484            Ok(self.0.clone())
1485        }
1486    }
1487    fn exact() -> Evidence {
1488        Evidence::exact(SourceId::new("cad", "m").unwrap(), "proximity:pipe:wall")
1489    }
1490    fn approximate() -> Evidence {
1491        Evidence {
1492            exact: false,
1493            ..exact()
1494        }
1495    }
1496
1497    #[test]
1498    fn an_object_is_not_measured_against_itself() {
1499        assert_eq!(
1500            ProximityRequest::try_new(id("pipe"), id("pipe")),
1501            Err(ProximityError::SameObject)
1502        );
1503    }
1504
1505    /// A tessellation cannot be presented as fact, nor exact geometry as an
1506    /// estimate: either would misstate what a reviewer can rely on.
1507    #[test]
1508    fn evidence_exactness_must_match_fidelity() {
1509        let tessellated = GeometryFidelity::tessellated(0.002).unwrap();
1510        assert_eq!(
1511            ProximityEvidence::try_new(request(), 0.1, Some(0.0), 0.0, None, tessellated, exact()),
1512            Err(ProximityError::EvidenceFidelityMismatch)
1513        );
1514        assert_eq!(
1515            ProximityEvidence::try_new(
1516                request(),
1517                0.1,
1518                Some(0.0),
1519                0.0,
1520                None,
1521                GeometryFidelity::Exact,
1522                approximate()
1523            ),
1524            Err(ProximityError::EvidenceFidelityMismatch)
1525        );
1526        assert!(
1527            ProximityEvidence::try_new(
1528                request(),
1529                0.1,
1530                Some(0.0),
1531                0.0,
1532                None,
1533                tessellated,
1534                approximate()
1535            )
1536            .is_ok()
1537        );
1538    }
1539
1540    #[test]
1541    fn separated_bodies_cannot_penetrate_unless_one_contains_the_other() {
1542        let exact_fidelity = GeometryFidelity::Exact;
1543        assert_eq!(
1544            ProximityEvidence::try_new(
1545                request(),
1546                0.1,
1547                Some(0.05),
1548                0.0,
1549                None,
1550                exact_fidelity,
1551                exact()
1552            ),
1553            Err(ProximityError::InvalidMeasurement)
1554        );
1555        assert!(
1556            ProximityEvidence::try_new(
1557                request(),
1558                0.1,
1559                Some(0.05),
1560                0.0,
1561                Some(BodyContainment::SubjectInsideCounterpart),
1562                exact_fidelity,
1563                exact()
1564            )
1565            .is_ok()
1566        );
1567        // Touching bodies are not contained in one another.
1568        assert_eq!(
1569            ProximityEvidence::try_new(
1570                request(),
1571                0.0,
1572                Some(0.05),
1573                0.0,
1574                Some(BodyContainment::SubjectInsideCounterpart),
1575                exact_fidelity,
1576                exact()
1577            ),
1578            Err(ProximityError::InvalidMeasurement)
1579        );
1580    }
1581
1582    #[test]
1583    fn tessellated_separation_widens_by_the_combined_deviation() {
1584        let fidelity = GeometryFidelity::tessellated(0.002)
1585            .unwrap()
1586            .combined(GeometryFidelity::tessellated(0.001).unwrap());
1587        let measured = ProximityEvidence::try_new(
1588            request(),
1589            0.01,
1590            Some(0.0),
1591            0.0,
1592            None,
1593            fidelity,
1594            approximate(),
1595        )
1596        .unwrap();
1597        let (lower, upper) = measured.separation_interval_metres();
1598        assert!((lower - 0.007).abs() < 1e-12 && (upper - 0.013).abs() < 1e-12);
1599    }
1600
1601    #[test]
1602    fn a_vertical_offset_must_be_finite_and_non_negative() {
1603        for offset in [-0.1, f64::NAN, f64::INFINITY] {
1604            assert_eq!(
1605                ProximityRequest::projected(
1606                    id("pipe"),
1607                    id("wall"),
1608                    ProximityProjection::Vertical {
1609                        footprint_offset_metres: offset,
1610                        direction: VerticalDirection::Either,
1611                        surfaces: crate::VerticalSurfaces::Extents,
1612                    }
1613                ),
1614                Err(ProximityError::InvalidMeasurement)
1615            );
1616        }
1617    }
1618
1619    #[test]
1620    fn surface_pairs_are_distinct_projections_and_nearest_takes_no_offset() {
1621        use crate::{CounterpartSurface, SubjectSurface, VerticalSurfaces};
1622        let between = |counterpart, offset| ProximityProjection::Vertical {
1623            footprint_offset_metres: offset,
1624            direction: VerticalDirection::Above,
1625            surfaces: VerticalSurfaces::Between {
1626                subject: SubjectSurface::Top,
1627                counterpart,
1628            },
1629        };
1630        let extents = ProximityProjection::Vertical {
1631            footprint_offset_metres: 0.0,
1632            direction: VerticalDirection::Above,
1633            surfaces: VerticalSurfaces::Extents,
1634        };
1635        assert_ne!(between(CounterpartSurface::Nearest, 0.0), extents);
1636        assert_ne!(
1637            between(CounterpartSurface::Nearest, 0.0),
1638            between(CounterpartSurface::Bottom, 0.0)
1639        );
1640        assert!(
1641            ProximityRequest::projected(
1642                id("pipe"),
1643                id("wall"),
1644                between(CounterpartSurface::Bottom, 0.5)
1645            )
1646            .is_ok()
1647        );
1648        assert_eq!(
1649            ProximityRequest::projected(
1650                id("pipe"),
1651                id("wall"),
1652                between(CounterpartSurface::Nearest, 0.5)
1653            ),
1654            Err(ProximityError::InvalidMeasurement)
1655        );
1656        assert_eq!(SubjectSurface::Bottom.name(), "bottom");
1657        assert_eq!(CounterpartSurface::Nearest.name(), "nearest");
1658    }
1659
1660    /// Above and below are different questions.
1661    #[test]
1662    fn a_vertical_direction_distinguishes_requests() {
1663        let vertical = |direction| ProximityProjection::Vertical {
1664            footprint_offset_metres: 0.0,
1665            direction,
1666            surfaces: crate::VerticalSurfaces::Extents,
1667        };
1668        let above = vertical(VerticalDirection::Above);
1669        assert_ne!(above, vertical(VerticalDirection::Below));
1670        assert_ne!(above, vertical(VerticalDirection::Either));
1671        assert!(vertical(VerticalDirection::Either) < above);
1672    }
1673
1674    /// Penetration is a question in space; a projected request cannot carry it.
1675    #[test]
1676    fn full_proximity_evidence_is_only_measured_in_space() {
1677        let horizontal =
1678            ProximityRequest::projected(id("pipe"), id("wall"), ProximityProjection::Horizontal)
1679                .unwrap();
1680        assert_eq!(
1681            ProximityEvidence::try_new(
1682                horizontal,
1683                0.1,
1684                Some(0.0),
1685                0.0,
1686                None,
1687                GeometryFidelity::Exact,
1688                exact()
1689            ),
1690            Err(ProximityError::UnsupportedProjection)
1691        );
1692    }
1693
1694    #[test]
1695    fn projected_distance_intervals_are_coherent() {
1696        let plan =
1697            ProximityRequest::projected(id("pipe"), id("wall"), ProximityProjection::PlanOverlap)
1698                .unwrap();
1699        let tessellated = GeometryFidelity::tessellated(0.002).unwrap();
1700        // Exact evidence is a point, related or not.
1701        assert!(
1702            ProjectedDistanceEvidence::try_new(
1703                plan.clone(),
1704                f64::INFINITY,
1705                f64::INFINITY,
1706                GeometryFidelity::Exact,
1707                exact()
1708            )
1709            .is_ok()
1710        );
1711        assert_eq!(
1712            ProjectedDistanceEvidence::try_new(
1713                plan.clone(),
1714                0.0,
1715                f64::INFINITY,
1716                GeometryFidelity::Exact,
1717                exact()
1718            ),
1719            Err(ProximityError::InvalidMeasurement)
1720        );
1721        // A tessellation may leave the relation open, but never claim exactness.
1722        assert!(
1723            ProjectedDistanceEvidence::try_new(
1724                plan.clone(),
1725                0.0,
1726                f64::INFINITY,
1727                tessellated,
1728                approximate()
1729            )
1730            .is_ok()
1731        );
1732        assert_eq!(
1733            ProjectedDistanceEvidence::try_new(plan, 0.0, 0.0, tessellated, exact()),
1734            Err(ProximityError::EvidenceFidelityMismatch)
1735        );
1736        // Bodies always have a distance in space and in plan.
1737        assert_eq!(
1738            ProjectedDistanceEvidence::try_new(
1739                request(),
1740                0.0,
1741                f64::INFINITY,
1742                tessellated,
1743                approximate()
1744            ),
1745            Err(ProximityError::InvalidMeasurement)
1746        );
1747        assert_eq!(
1748            ProjectedDistanceEvidence::try_new(request(), 0.3, 0.2, tessellated, approximate()),
1749            Err(ProximityError::InvalidMeasurement)
1750        );
1751    }
1752
1753    /// The narrower plan axis bounds how far an intersection reaches in plan.
1754    #[test]
1755    fn horizontal_extent_is_the_narrower_plan_axis() {
1756        let extents = OverlapExtents::new(
1757            LengthInterval::try_new(0.1, 0.3).unwrap(),
1758            LengthInterval::try_new(0.2, 0.25).unwrap(),
1759            LengthInterval::exact(1.0).unwrap(),
1760        );
1761        assert_eq!(
1762            extents.horizontal(),
1763            LengthInterval::try_new(0.1, 0.25).unwrap()
1764        );
1765        assert_eq!(extents.vertical(), LengthInterval::exact(1.0).unwrap());
1766    }
1767
1768    #[test]
1769    fn overlap_extents_need_a_shared_volume() {
1770        let extents = OverlapExtents::new(
1771            LengthInterval::exact(0.1).unwrap(),
1772            LengthInterval::exact(0.1).unwrap(),
1773            LengthInterval::exact(0.1).unwrap(),
1774        );
1775        let measured = |separation: f64, penetration: Option<f64>| {
1776            ProximityEvidence::try_new(
1777                request(),
1778                separation,
1779                penetration,
1780                0.0,
1781                None,
1782                GeometryFidelity::Exact,
1783                exact(),
1784            )
1785            .unwrap()
1786        };
1787        assert!(
1788            measured(0.0, Some(0.1))
1789                .with_overlap_extents(extents)
1790                .is_ok()
1791        );
1792        // Two open surfaces have no inside to share.
1793        assert_eq!(
1794            measured(0.0, None).with_overlap_extents(extents),
1795            Err(ProximityError::InvalidMeasurement)
1796        );
1797        // Bodies apart at the surface, neither inside the other, share nothing.
1798        assert_eq!(
1799            measured(0.2, Some(0.0)).with_overlap_extents(extents),
1800            Err(ProximityError::InvalidMeasurement)
1801        );
1802    }
1803
1804    /// No point of a surface lies closer to the other than the separation.
1805    #[test]
1806    fn hausdorff_distance_is_never_below_the_separation() {
1807        let measured = ProximityEvidence::try_new(
1808            request(),
1809            0.2,
1810            Some(0.0),
1811            0.0,
1812            None,
1813            GeometryFidelity::Exact,
1814            exact(),
1815        )
1816        .unwrap();
1817        assert_eq!(
1818            measured
1819                .clone()
1820                .with_hausdorff(LengthInterval::try_new(0.0, 0.1).unwrap()),
1821            Err(ProximityError::InvalidMeasurement)
1822        );
1823        assert!(
1824            measured
1825                .with_hausdorff(LengthInterval::try_new(0.2, 0.5).unwrap())
1826                .is_ok()
1827        );
1828    }
1829
1830    fn volume(lower: f64, upper: f64) -> VolumeInterval {
1831        VolumeInterval::try_new(lower, upper).unwrap()
1832    }
1833
1834    #[test]
1835    fn volume_intervals_are_finite_non_negative_and_ordered() {
1836        for (lower, upper) in [
1837            (-0.1, 0.1),
1838            (0.2, 0.1),
1839            (0.0, f64::INFINITY),
1840            (f64::NAN, 1.0),
1841        ] {
1842            assert_eq!(
1843                VolumeInterval::try_new(lower, upper),
1844                Err(ProximityError::InvalidMeasurement)
1845            );
1846        }
1847        assert!(VolumeInterval::exact(0.0).unwrap().is_exact());
1848    }
1849
1850    /// Two bodies cannot share more than the smaller of them holds.
1851    #[test]
1852    fn a_shared_volume_never_exceeds_either_body() {
1853        assert_eq!(
1854            IntersectionVolume::try_new(volume(0.5, 0.6), volume(1.0, 1.0), volume(0.4, 0.4)),
1855            Err(ProximityError::InvalidMeasurement)
1856        );
1857        let measured =
1858            IntersectionVolume::try_new(volume(0.3, 0.4), volume(1.0, 1.0), volume(0.4, 0.5))
1859                .unwrap();
1860        assert_eq!(measured.smaller(), volume(0.4, 0.5));
1861        let (lower, upper) = measured.ratio_of_smaller();
1862        assert!(lower <= 0.6 && lower > 0.599, "{lower}");
1863        assert!((upper - 1.0).abs() < f64::EPSILON, "{upper}");
1864    }
1865
1866    /// A share's bounds are rounded outward, so the true share lies inside.
1867    #[test]
1868    fn a_volume_share_is_rounded_outward_and_clamped() {
1869        let third = volume(1.0 / 3.0, 1.0 / 3.0);
1870        let (lower, upper) = third.share_of(volume(1.0, 1.0));
1871        assert!(lower < 1.0 / 3.0 && upper > 1.0 / 3.0);
1872        assert_eq!(volume(0.0, 0.0).share_of(volume(0.0, 0.0)), (0.0, 1.0));
1873        assert!((volume(1.0, 1.0).share_of(volume(1.0, 1.0)).1 - 1.0).abs() < f64::EPSILON);
1874    }
1875
1876    #[test]
1877    fn an_intersection_volume_needs_a_shared_inside() {
1878        let measured = |separation: f64, penetration: Option<f64>, containment| {
1879            ProximityEvidence::try_new(
1880                request(),
1881                separation,
1882                penetration,
1883                0.0,
1884                containment,
1885                GeometryFidelity::Exact,
1886                exact(),
1887            )
1888            .unwrap()
1889        };
1890        let shared = |lower: f64, upper: f64| {
1891            IntersectionVolume::try_new(volume(lower, upper), volume(1.0, 1.0), volume(8.0, 8.0))
1892                .unwrap()
1893        };
1894        assert!(
1895            measured(0.0, Some(0.1), None)
1896                .with_intersection_volume(shared(0.2, 0.2))
1897                .is_ok()
1898        );
1899        // Open surfaces enclose nothing.
1900        assert_eq!(
1901            measured(0.0, None, None).with_intersection_volume(shared(0.0, 0.0)),
1902            Err(ProximityError::InvalidMeasurement)
1903        );
1904        // Bodies apart at the surface share nothing unless one holds the
1905        // other,
1906        assert_eq!(
1907            measured(0.2, Some(0.0), None).with_intersection_volume(shared(0.1, 0.2)),
1908            Err(ProximityError::InvalidMeasurement)
1909        );
1910        assert!(
1911            measured(0.2, Some(0.0), None)
1912                .with_intersection_volume(shared(0.0, 0.0))
1913                .is_ok()
1914        );
1915        // and then they share the whole of the inner body.
1916        let inside = || {
1917            measured(
1918                0.2,
1919                Some(0.1),
1920                Some(BodyContainment::SubjectInsideCounterpart),
1921            )
1922        };
1923        assert!(inside().with_intersection_volume(shared(1.0, 1.0)).is_ok());
1924        assert_eq!(
1925            inside().with_intersection_volume(shared(0.5, 0.6)),
1926            Err(ProximityError::InvalidMeasurement)
1927        );
1928    }
1929
1930    #[test]
1931    fn a_face_distance_is_between_two_objects() {
1932        assert_eq!(
1933            FaceDistanceRequest::try_new(id("pipe"), id("pipe"), FaceClass::Top),
1934            Err(FaceDistanceError::SameObject)
1935        );
1936        for class in [
1937            FaceClass::Top,
1938            FaceClass::Side,
1939            FaceClass::Bottom,
1940            FaceClass::Any,
1941        ] {
1942            assert_eq!(FaceClass::parse(class.name()), Some(class));
1943        }
1944        assert_eq!(FaceClass::parse("front"), None);
1945        let request =
1946            FaceDistanceRequest::try_new(id("pipe"), id("wall"), FaceClass::Side).unwrap();
1947        let signed = SignedDistanceInterval::try_new(-0.1, 0.2).unwrap();
1948        assert_eq!(
1949            FaceDistanceEvidence::try_new(
1950                request.clone(),
1951                signed,
1952                GeometryFidelity::tessellated(0.001).unwrap(),
1953                exact()
1954            ),
1955            Err(FaceDistanceError::EvidenceFidelityMismatch)
1956        );
1957        assert!(
1958            FaceDistanceEvidence::try_new(request, signed, GeometryFidelity::Exact, exact())
1959                .is_ok()
1960        );
1961    }
1962
1963    /// A service that does not measure face distances says so.
1964    #[test]
1965    fn face_distances_are_refused_by_default() {
1966        struct Silent;
1967        impl ProximityService for Silent {
1968            fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
1969                Err(ProximityError::Unavailable)
1970            }
1971            fn measure_proximity(
1972                &self,
1973                _: &ProximityRequest,
1974            ) -> Result<ProximityEvidence, ProximityError> {
1975                Err(ProximityError::Unavailable)
1976            }
1977        }
1978        let request = FaceDistanceRequest::try_new(id("pipe"), id("wall"), FaceClass::Any).unwrap();
1979        assert_eq!(
1980            ProximityServiceHandle::new(Arc::new(Silent)).measure_face_distance(&request),
1981            Err(FaceDistanceError::Unsupported)
1982        );
1983    }
1984
1985    #[test]
1986    fn box_gap_is_euclidean_and_zero_when_boxes_meet() {
1987        let a = Bounds3::try_new([0.0; 3], [1.0; 3]).unwrap();
1988        let b = Bounds3::try_new([4.0, 5.0, 0.0], [5.0, 6.0, 1.0]).unwrap();
1989        assert!((a.gap(&b) - 5.0).abs() < 1e-12);
1990        let touching = Bounds3::try_new([1.0, 0.0, 0.0], [2.0, 1.0, 1.0]).unwrap();
1991        assert!(a.gap(&touching).abs() < f64::EPSILON);
1992        assert_eq!(
1993            Bounds3::try_new([1.0, 0.0, 0.0], [0.0, 1.0, 1.0]),
1994            Err(ProximityError::InvalidMeasurement)
1995        );
1996    }
1997
1998    #[test]
1999    fn region_distances_bind_their_request_and_default_to_refusal() {
2000        struct Nothing;
2001        impl ProximityService for Nothing {
2002            fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
2003                Err(ProximityError::Unavailable)
2004            }
2005            fn measure_proximity(
2006                &self,
2007                _: &ProximityRequest,
2008            ) -> Result<ProximityEvidence, ProximityError> {
2009                Err(ProximityError::Unavailable)
2010            }
2011        }
2012        let region = ConvexPlanRegion::try_new(vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]).unwrap();
2013        let request = RegionDistanceRequest::new(region, id("wall"));
2014        let evidence = |exact: bool| Evidence {
2015            source: SourceId::new("cad", "m").unwrap(),
2016            locator: "region".into(),
2017            exact,
2018        };
2019        assert!(
2020            RegionDistanceEvidence::try_new(
2021                request.clone(),
2022                0.5,
2023                0.5,
2024                GeometryFidelity::Exact,
2025                evidence(true)
2026            )
2027            .is_ok()
2028        );
2029        // Exact evidence is a point; tessellated evidence is not exact.
2030        assert_eq!(
2031            RegionDistanceEvidence::try_new(
2032                request.clone(),
2033                0.4,
2034                0.5,
2035                GeometryFidelity::Exact,
2036                evidence(true)
2037            ),
2038            Err(ProximityError::InvalidMeasurement)
2039        );
2040        assert_eq!(
2041            RegionDistanceEvidence::try_new(
2042                request.clone(),
2043                0.4,
2044                0.5,
2045                GeometryFidelity::tessellated(0.05).unwrap(),
2046                evidence(true)
2047            ),
2048            Err(ProximityError::EvidenceFidelityMismatch)
2049        );
2050        assert_eq!(
2051            ProximityServiceHandle::new(Arc::new(Nothing)).measure_region_distance(&request),
2052            Err(ProximityError::UnsupportedProjection)
2053        );
2054    }
2055
2056    /// Extents along stated directions: one per direction, bound to their
2057    /// request, refused by default.
2058    #[test]
2059    fn extents_along_directions_bind_their_request_and_default_to_refusal() {
2060        struct Nothing;
2061        impl ProximityService for Nothing {
2062            fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
2063                Err(ProximityError::Unavailable)
2064            }
2065            fn measure_proximity(
2066                &self,
2067                _: &ProximityRequest,
2068            ) -> Result<ProximityEvidence, ProximityError> {
2069                Err(ProximityError::Unavailable)
2070            }
2071        }
2072        /// Answers every request with the evidence for another.
2073        struct Other(OverlapAlongEvidence);
2074        impl ProximityService for Other {
2075            fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
2076                Err(ProximityError::Unavailable)
2077            }
2078            fn measure_proximity(
2079                &self,
2080                _: &ProximityRequest,
2081            ) -> Result<ProximityEvidence, ProximityError> {
2082                Err(ProximityError::Unavailable)
2083            }
2084            fn measure_overlap_along(
2085                &self,
2086                _: &OverlapAlongRequest,
2087            ) -> Result<OverlapAlongEvidence, ProximityError> {
2088                Ok(self.0.clone())
2089            }
2090        }
2091        let axis = |vector| MetricDirection::try_new(vector).unwrap();
2092        let along = |directions: Vec<MetricDirection>| {
2093            OverlapAlongRequest::try_new(id("pipe"), id("wall"), directions)
2094        };
2095        assert_eq!(along(Vec::new()), Err(ProximityError::InvalidMeasurement));
2096        assert_eq!(
2097            along(vec![axis([1.0, 0.0, 0.0]); 7]),
2098            Err(ProximityError::InvalidMeasurement)
2099        );
2100        assert_eq!(
2101            OverlapAlongRequest::try_new(id("pipe"), id("pipe"), vec![axis([0.0, 0.0, 1.0])]),
2102            Err(ProximityError::SameObject)
2103        );
2104        let request = along(vec![axis([1.0, 1.0, 0.0]), axis([0.0, 0.0, 1.0])]).unwrap();
2105        let extent = LengthInterval::try_new(0.01, 0.02).unwrap();
2106        assert_eq!(
2107            OverlapAlongEvidence::try_new(
2108                request.clone(),
2109                vec![extent],
2110                GeometryFidelity::Exact,
2111                exact()
2112            ),
2113            Err(ProximityError::InvalidMeasurement)
2114        );
2115        assert_eq!(
2116            OverlapAlongEvidence::try_new(
2117                request.clone(),
2118                vec![extent, extent],
2119                GeometryFidelity::tessellated(0.001).unwrap(),
2120                exact()
2121            ),
2122            Err(ProximityError::EvidenceFidelityMismatch)
2123        );
2124        let measured = OverlapAlongEvidence::try_new(
2125            request.clone(),
2126            vec![extent, extent],
2127            GeometryFidelity::Exact,
2128            exact(),
2129        )
2130        .unwrap();
2131        assert_eq!(measured.extents(), &[extent, extent]);
2132
2133        assert_eq!(
2134            ProximityServiceHandle::new(Arc::new(Nothing)).measure_overlap_along(&request),
2135            Err(ProximityError::UnsupportedProjection)
2136        );
2137
2138        let other = along(vec![axis([0.0, 1.0, 0.0]), axis([0.0, 0.0, 1.0])]).unwrap();
2139        assert_eq!(
2140            ProximityServiceHandle::new(Arc::new(Other(measured))).measure_overlap_along(&other),
2141            Err(ProximityError::InvalidMeasurement)
2142        );
2143    }
2144}