use std::sync::Arc;
use axioval_ir::{Evidence, ObjectId};
use crate::{ConvexPlanRegion, LengthInterval, MetricDirection, SignedDistanceInterval};
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum ProximityError {
#[error("proximity measurement is unavailable for the requested object")]
Unavailable,
#[error("the requested object occupies no material")]
NoBody,
#[error("proximity measurement is not finite, non-negative and coherent")]
InvalidMeasurement,
#[error("proximity evidence exactness must match geometry fidelity and be reviewable")]
EvidenceFidelityMismatch,
#[error("proximity of an object to itself is undefined")]
SameObject,
#[error("the requested proximity projection is not supported")]
UnsupportedProjection,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Bounds3 {
min: [f64; 3],
max: [f64; 3],
}
impl Bounds3 {
pub fn try_new(min: [f64; 3], max: [f64; 3]) -> Result<Self, ProximityError> {
let coherent = (0..3)
.all(|axis| min[axis].is_finite() && max[axis].is_finite() && min[axis] <= max[axis]);
if !coherent {
return Err(ProximityError::InvalidMeasurement);
}
Ok(Self { min, max })
}
pub fn min(&self) -> [f64; 3] {
self.min
}
pub fn max(&self) -> [f64; 3] {
self.max
}
#[must_use]
pub fn expanded(&self, margin: f64) -> Self {
Self {
min: self.min.map(|value| value - margin),
max: self.max.map(|value| value + margin),
}
}
pub fn gap(&self, other: &Self) -> f64 {
(0..3)
.map(|axis| {
let gap = (other.min[axis] - self.max[axis])
.max(self.min[axis] - other.max[axis])
.max(0.0);
gap * gap
})
.sum::<f64>()
.sqrt()
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub enum GeometryFidelity {
Exact,
Tessellated { chord_deviation_metres: f64 },
}
impl GeometryFidelity {
pub fn tessellated(chord_deviation_metres: f64) -> Result<Self, ProximityError> {
if !chord_deviation_metres.is_finite() || chord_deviation_metres < 0.0 {
return Err(ProximityError::InvalidMeasurement);
}
Ok(Self::Tessellated {
chord_deviation_metres,
})
}
pub fn is_exact(&self) -> bool {
matches!(self, Self::Exact)
}
pub fn deviation_metres(&self) -> f64 {
match self {
Self::Exact => 0.0,
Self::Tessellated {
chord_deviation_metres,
} => *chord_deviation_metres,
}
}
#[must_use]
pub fn combined(self, other: Self) -> Self {
if self.is_exact() && other.is_exact() {
Self::Exact
} else {
Self::Tessellated {
chord_deviation_metres: self.deviation_metres() + other.deviation_metres(),
}
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ObjectBounds {
object: ObjectId,
bounds: Bounds3,
fidelity: GeometryFidelity,
}
impl ObjectBounds {
pub fn try_new(
object: ObjectId,
bounds: Bounds3,
fidelity: GeometryFidelity,
) -> Result<Self, ProximityError> {
let deviation = fidelity.deviation_metres();
if !deviation.is_finite() || deviation < 0.0 {
return Err(ProximityError::InvalidMeasurement);
}
Ok(Self {
object,
bounds,
fidelity,
})
}
pub fn object(&self) -> &ObjectId {
&self.object
}
pub fn bounds(&self) -> Bounds3 {
self.bounds
}
pub fn fidelity(&self) -> GeometryFidelity {
self.fidelity
}
pub fn enclosing(&self) -> Bounds3 {
self.bounds.expanded(self.fidelity.deviation_metres())
}
}
#[derive(Clone, Copy, Debug)]
pub enum ProximityProjection {
Minimum3d,
Horizontal,
Vertical {
footprint_offset_metres: f64,
direction: VerticalDirection,
surfaces: VerticalSurfaces,
},
PlanOverlap,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VerticalDirection {
Either,
Above,
Below,
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum VerticalSurfaces {
#[default]
Extents,
Between {
subject: SubjectSurface,
counterpart: CounterpartSurface,
},
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum SubjectSurface {
Top,
Bottom,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum CounterpartSurface {
Top,
Bottom,
Nearest,
}
impl SubjectSurface {
pub fn name(self) -> &'static str {
match self {
Self::Top => "top",
Self::Bottom => "bottom",
}
}
}
impl CounterpartSurface {
pub fn name(self) -> &'static str {
match self {
Self::Top => "top",
Self::Bottom => "bottom",
Self::Nearest => "nearest",
}
}
}
impl VerticalDirection {
pub fn name(self) -> &'static str {
match self {
Self::Either => "either",
Self::Above => "above",
Self::Below => "below",
}
}
}
impl ProximityProjection {
fn is_valid(&self) -> bool {
match self {
Self::Vertical {
footprint_offset_metres,
surfaces,
..
} => {
let nearest = matches!(
surfaces,
VerticalSurfaces::Between {
counterpart: CounterpartSurface::Nearest,
..
}
);
footprint_offset_metres.is_finite()
&& *footprint_offset_metres >= 0.0
&& !(nearest && *footprint_offset_metres > 0.0)
}
_ => true,
}
}
pub fn may_be_unrelated(&self) -> bool {
matches!(self, Self::Vertical { .. } | Self::PlanOverlap)
}
pub fn name(&self) -> &'static str {
match self {
Self::Minimum3d => "minimum_3d",
Self::Horizontal => "horizontal",
Self::Vertical { .. } => "vertical",
Self::PlanOverlap => "plan_overlap",
}
}
fn key(&self) -> (u8, f64, VerticalDirection, VerticalSurfaces) {
let none = (VerticalDirection::Either, VerticalSurfaces::Extents);
match self {
Self::Minimum3d => (0, 0.0, none.0, none.1),
Self::Horizontal => (1, 0.0, none.0, none.1),
Self::Vertical {
footprint_offset_metres,
direction,
surfaces,
} => (2, *footprint_offset_metres, *direction, *surfaces),
Self::PlanOverlap => (3, 0.0, none.0, none.1),
}
}
}
impl PartialEq for ProximityProjection {
fn eq(&self, other: &Self) -> bool {
self.cmp(other) == std::cmp::Ordering::Equal
}
}
impl Eq for ProximityProjection {}
impl PartialOrd for ProximityProjection {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for ProximityProjection {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
let (a, a_offset, a_direction, a_surfaces) = self.key();
let (b, b_offset, b_direction, b_surfaces) = other.key();
a.cmp(&b)
.then_with(|| a_offset.total_cmp(&b_offset))
.then_with(|| a_direction.cmp(&b_direction))
.then_with(|| a_surfaces.cmp(&b_surfaces))
}
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct ProximityRequest {
subject: ObjectId,
counterpart: ObjectId,
projection: ProximityProjection,
}
impl ProximityRequest {
pub fn try_new(subject: ObjectId, counterpart: ObjectId) -> Result<Self, ProximityError> {
Self::projected(subject, counterpart, ProximityProjection::Minimum3d)
}
pub fn projected(
subject: ObjectId,
counterpart: ObjectId,
projection: ProximityProjection,
) -> Result<Self, ProximityError> {
if subject == counterpart {
return Err(ProximityError::SameObject);
}
if !projection.is_valid() {
return Err(ProximityError::InvalidMeasurement);
}
Ok(Self {
subject,
counterpart,
projection,
})
}
pub fn subject(&self) -> &ObjectId {
&self.subject
}
pub fn counterpart(&self) -> &ObjectId {
&self.counterpart
}
pub fn projection(&self) -> ProximityProjection {
self.projection
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct OverlapExtents {
axes: [LengthInterval; 3],
}
impl OverlapExtents {
#[must_use]
pub fn new(x: LengthInterval, y: LengthInterval, z: LengthInterval) -> Self {
Self { axes: [x, y, z] }
}
pub fn x(&self) -> LengthInterval {
self.axes[0]
}
pub fn y(&self) -> LengthInterval {
self.axes[1]
}
pub fn z(&self) -> LengthInterval {
self.axes[2]
}
pub fn horizontal(&self) -> LengthInterval {
let (x, y) = (self.axes[0], self.axes[1]);
LengthInterval::try_new(
x.lower_metres().min(y.lower_metres()),
x.upper_metres().min(y.upper_metres()),
)
.unwrap_or_else(|_| unreachable!("the lesser of two intervals is an interval"))
}
pub fn vertical(&self) -> LengthInterval {
self.axes[2]
}
fn is_empty(&self) -> bool {
self.axes.iter().all(|axis| axis.lower_metres() == 0.0)
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OverlapAlongRequest {
subject: ObjectId,
counterpart: ObjectId,
directions: Vec<MetricDirection>,
}
impl OverlapAlongRequest {
pub const MAX_DIRECTIONS: usize = 6;
pub fn try_new(
subject: ObjectId,
counterpart: ObjectId,
directions: Vec<MetricDirection>,
) -> Result<Self, ProximityError> {
if subject == counterpart {
return Err(ProximityError::SameObject);
}
if directions.is_empty() || directions.len() > Self::MAX_DIRECTIONS {
return Err(ProximityError::InvalidMeasurement);
}
Ok(Self {
subject,
counterpart,
directions,
})
}
pub fn subject(&self) -> &ObjectId {
&self.subject
}
pub fn counterpart(&self) -> &ObjectId {
&self.counterpart
}
pub fn directions(&self) -> &[MetricDirection] {
&self.directions
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct OverlapAlongEvidence {
request: OverlapAlongRequest,
extents: Vec<LengthInterval>,
fidelity: GeometryFidelity,
evidence: Evidence,
}
impl OverlapAlongEvidence {
pub fn try_new(
request: OverlapAlongRequest,
extents: Vec<LengthInterval>,
fidelity: GeometryFidelity,
evidence: Evidence,
) -> Result<Self, ProximityError> {
if extents.len() != request.directions.len() {
return Err(ProximityError::InvalidMeasurement);
}
if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
return Err(ProximityError::EvidenceFidelityMismatch);
}
Ok(Self {
request,
extents,
fidelity,
evidence,
})
}
pub fn request(&self) -> &OverlapAlongRequest {
&self.request
}
pub fn extents(&self) -> &[LengthInterval] {
&self.extents
}
pub fn fidelity(&self) -> GeometryFidelity {
self.fidelity
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum BodyContainment {
SubjectInsideCounterpart,
CounterpartInsideSubject,
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct VolumeInterval {
lower_cubic_metres: f64,
upper_cubic_metres: f64,
}
impl VolumeInterval {
pub fn try_new(
lower_cubic_metres: f64,
upper_cubic_metres: f64,
) -> Result<Self, ProximityError> {
let valid = |value: f64| value.is_finite() && value >= 0.0;
if !valid(lower_cubic_metres)
|| !valid(upper_cubic_metres)
|| lower_cubic_metres > upper_cubic_metres
{
return Err(ProximityError::InvalidMeasurement);
}
Ok(Self {
lower_cubic_metres,
upper_cubic_metres,
})
}
pub fn exact(cubic_metres: f64) -> Result<Self, ProximityError> {
Self::try_new(cubic_metres, cubic_metres)
}
pub fn lower_cubic_metres(&self) -> f64 {
self.lower_cubic_metres
}
pub fn upper_cubic_metres(&self) -> f64 {
self.upper_cubic_metres
}
#[allow(clippy::float_cmp)]
pub fn is_exact(&self) -> bool {
self.lower_cubic_metres == self.upper_cubic_metres
}
pub fn share_of(self, whole: Self) -> (f64, f64) {
let lower = if whole.upper_cubic_metres > 0.0 {
(self.lower_cubic_metres / whole.upper_cubic_metres).next_down()
} else {
0.0
};
let upper = if whole.lower_cubic_metres > 0.0 {
(self.upper_cubic_metres / whole.lower_cubic_metres).next_up()
} else {
1.0
};
let upper = upper.clamp(0.0, 1.0);
(lower.clamp(0.0, upper), upper)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct IntersectionVolume {
shared: VolumeInterval,
subject: VolumeInterval,
counterpart: VolumeInterval,
}
impl IntersectionVolume {
pub fn try_new(
shared: VolumeInterval,
subject: VolumeInterval,
counterpart: VolumeInterval,
) -> Result<Self, ProximityError> {
let most = subject
.upper_cubic_metres
.min(counterpart.upper_cubic_metres);
if shared.lower_cubic_metres > most {
return Err(ProximityError::InvalidMeasurement);
}
Ok(Self {
shared,
subject,
counterpart,
})
}
pub fn shared(&self) -> VolumeInterval {
self.shared
}
pub fn subject(&self) -> VolumeInterval {
self.subject
}
pub fn counterpart(&self) -> VolumeInterval {
self.counterpart
}
pub fn smaller(&self) -> VolumeInterval {
VolumeInterval {
lower_cubic_metres: self
.subject
.lower_cubic_metres
.min(self.counterpart.lower_cubic_metres),
upper_cubic_metres: self
.subject
.upper_cubic_metres
.min(self.counterpart.upper_cubic_metres),
}
}
pub fn ratio_of_smaller(&self) -> (f64, f64) {
self.shared.share_of(self.smaller())
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct BodyVolume {
object: ObjectId,
volume: VolumeInterval,
fidelity: GeometryFidelity,
evidence: Evidence,
}
impl BodyVolume {
pub fn try_new(
object: ObjectId,
volume: VolumeInterval,
fidelity: GeometryFidelity,
evidence: Evidence,
) -> Result<Self, ProximityError> {
if evidence.exact && !fidelity.is_exact() || evidence.locator.trim().is_empty() {
return Err(ProximityError::EvidenceFidelityMismatch);
}
Ok(Self {
object,
volume,
fidelity,
evidence,
})
}
pub fn object(&self) -> &ObjectId {
&self.object
}
pub fn volume(&self) -> VolumeInterval {
self.volume
}
pub fn fidelity(&self) -> GeometryFidelity {
self.fidelity
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ProximityEvidence {
request: ProximityRequest,
separation_metres: f64,
penetration_metres: Option<f64>,
plan_overlap_square_metres: f64,
containment: Option<BodyContainment>,
overlap_extents: Option<OverlapExtents>,
hausdorff: Option<LengthInterval>,
volume: Option<IntersectionVolume>,
fidelity: GeometryFidelity,
evidence: Evidence,
}
impl ProximityEvidence {
pub fn try_new(
request: ProximityRequest,
separation_metres: f64,
penetration_metres: Option<f64>,
plan_overlap_square_metres: f64,
containment: Option<BodyContainment>,
fidelity: GeometryFidelity,
evidence: Evidence,
) -> Result<Self, ProximityError> {
let finite_non_negative = |v: f64| v.is_finite() && v >= 0.0;
if !finite_non_negative(separation_metres)
|| !finite_non_negative(plan_overlap_square_metres)
|| penetration_metres.is_some_and(|depth| !finite_non_negative(depth))
|| !finite_non_negative(fidelity.deviation_metres())
{
return Err(ProximityError::InvalidMeasurement);
}
let separated = separation_metres > 0.0;
if (separated && containment.is_none() && penetration_metres.is_some_and(|d| d > 0.0))
|| (containment.is_some() && (!separated || penetration_metres.is_none()))
{
return Err(ProximityError::InvalidMeasurement);
}
if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
return Err(ProximityError::EvidenceFidelityMismatch);
}
if request.projection() != ProximityProjection::Minimum3d {
return Err(ProximityError::UnsupportedProjection);
}
Ok(Self {
request,
separation_metres,
penetration_metres,
plan_overlap_square_metres,
containment,
overlap_extents: None,
hausdorff: None,
volume: None,
fidelity,
evidence,
})
}
pub fn with_overlap_extents(mut self, extents: OverlapExtents) -> Result<Self, ProximityError> {
let disjoint = self.separation_metres > 0.0 && self.containment.is_none();
if self.penetration_metres.is_none() || (disjoint && !extents.is_empty()) {
return Err(ProximityError::InvalidMeasurement);
}
self.overlap_extents = Some(extents);
Ok(self)
}
pub fn with_hausdorff(mut self, interval: LengthInterval) -> Result<Self, ProximityError> {
if interval.upper_metres() < self.separation_interval_metres().0 {
return Err(ProximityError::InvalidMeasurement);
}
self.hausdorff = Some(interval);
Ok(self)
}
pub fn with_intersection_volume(
mut self,
volume: IntersectionVolume,
) -> Result<Self, ProximityError> {
let disjoint = self.separation_metres > 0.0 && self.containment.is_none();
let shared = volume.shared();
let whole = |inner: VolumeInterval| {
shared.upper_cubic_metres >= inner.lower_cubic_metres
&& shared.lower_cubic_metres <= inner.upper_cubic_metres
};
let contained = match self.containment {
Some(BodyContainment::SubjectInsideCounterpart) => whole(volume.subject()),
Some(BodyContainment::CounterpartInsideSubject) => whole(volume.counterpart()),
None => true,
};
if self.penetration_metres.is_none()
|| (disjoint && shared.lower_cubic_metres > 0.0)
|| !contained
{
return Err(ProximityError::InvalidMeasurement);
}
self.volume = Some(volume);
Ok(self)
}
pub fn request(&self) -> &ProximityRequest {
&self.request
}
pub fn separation_metres(&self) -> f64 {
self.separation_metres
}
pub fn separation_interval_metres(&self) -> (f64, f64) {
let deviation = self.fidelity.deviation_metres();
(
(self.separation_metres - deviation).max(0.0),
self.separation_metres + deviation,
)
}
pub fn penetration_metres(&self) -> Option<f64> {
self.penetration_metres
}
pub fn plan_overlap_square_metres(&self) -> f64 {
self.plan_overlap_square_metres
}
pub fn containment(&self) -> Option<BodyContainment> {
self.containment
}
pub fn overlap_extents(&self) -> Option<OverlapExtents> {
self.overlap_extents
}
pub fn hausdorff_interval_metres(&self) -> Option<LengthInterval> {
self.hausdorff
}
pub fn intersection_volume(&self) -> Option<IntersectionVolume> {
self.volume
}
pub fn fidelity(&self) -> GeometryFidelity {
self.fidelity
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct ProjectedDistanceEvidence {
request: ProximityRequest,
lower_metres: f64,
upper_metres: f64,
fidelity: GeometryFidelity,
evidence: Evidence,
}
impl ProjectedDistanceEvidence {
pub fn try_new(
request: ProximityRequest,
lower_metres: f64,
upper_metres: f64,
fidelity: GeometryFidelity,
evidence: Evidence,
) -> Result<Self, ProximityError> {
let deviation = fidelity.deviation_metres();
let unbounded_allowed = request.projection().may_be_unrelated();
let bound_ok = |value: f64| {
value >= 0.0 && (value.is_finite() || (unbounded_allowed && value == f64::INFINITY))
};
if !bound_ok(lower_metres)
|| !bound_ok(upper_metres)
|| lower_metres > upper_metres
|| !deviation.is_finite()
|| deviation < 0.0
|| (fidelity.is_exact() && lower_metres < upper_metres)
{
return Err(ProximityError::InvalidMeasurement);
}
if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
return Err(ProximityError::EvidenceFidelityMismatch);
}
Ok(Self {
request,
lower_metres,
upper_metres,
fidelity,
evidence,
})
}
pub fn from_proximity(measured: &ProximityEvidence) -> Result<Self, ProximityError> {
let (lower, upper) = measured.separation_interval_metres();
Self::try_new(
measured.request().clone(),
lower,
upper,
measured.fidelity(),
measured.evidence().clone(),
)
}
pub fn request(&self) -> &ProximityRequest {
&self.request
}
pub fn interval_metres(&self) -> (f64, f64) {
(self.lower_metres, self.upper_metres)
}
pub fn fidelity(&self) -> GeometryFidelity {
self.fidelity
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RegionDistanceRequest {
region: ConvexPlanRegion,
counterpart: ObjectId,
}
impl RegionDistanceRequest {
pub fn new(region: ConvexPlanRegion, counterpart: ObjectId) -> Self {
Self {
region,
counterpart,
}
}
pub fn region(&self) -> &ConvexPlanRegion {
&self.region
}
pub fn counterpart(&self) -> &ObjectId {
&self.counterpart
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct RegionDistanceEvidence {
request: RegionDistanceRequest,
lower_metres: f64,
upper_metres: f64,
fidelity: GeometryFidelity,
evidence: Evidence,
}
impl RegionDistanceEvidence {
pub fn try_new(
request: RegionDistanceRequest,
lower_metres: f64,
upper_metres: f64,
fidelity: GeometryFidelity,
evidence: Evidence,
) -> Result<Self, ProximityError> {
let bound_ok = |value: f64| value.is_finite() && value >= 0.0;
if !bound_ok(lower_metres)
|| !bound_ok(upper_metres)
|| lower_metres > upper_metres
|| (fidelity.is_exact() && lower_metres < upper_metres)
{
return Err(ProximityError::InvalidMeasurement);
}
if evidence.exact != fidelity.is_exact()
|| evidence.locator.trim().is_empty()
|| evidence.source != request.counterpart().source
{
return Err(ProximityError::EvidenceFidelityMismatch);
}
Ok(Self {
request,
lower_metres,
upper_metres,
fidelity,
evidence,
})
}
pub fn request(&self) -> &RegionDistanceRequest {
&self.request
}
pub fn interval_metres(&self) -> (f64, f64) {
(self.lower_metres, self.upper_metres)
}
pub fn fidelity(&self) -> GeometryFidelity {
self.fidelity
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub enum FaceClass {
Top,
Side,
Bottom,
Any,
}
impl FaceClass {
pub fn name(self) -> &'static str {
match self {
Self::Top => "top",
Self::Side => "side",
Self::Bottom => "bottom",
Self::Any => "any",
}
}
pub fn parse(name: &str) -> Option<Self> {
[Self::Top, Self::Side, Self::Bottom, Self::Any]
.into_iter()
.find(|class| class.name() == name)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, thiserror::Error)]
pub enum FaceDistanceError {
#[error("face distances are not measured by this service")]
Unsupported,
#[error("face distance is unavailable for the requested object")]
Unavailable,
#[error("the requested object occupies no material")]
NoBody,
#[error("the host body is not a closed solid")]
NotClosed,
#[error("the host body has no face of the requested class")]
NoFaces,
#[error("the host body is tessellated, so its face classes are unknown")]
InexactHost,
#[error("a face of the host lies on the boundary between two face classes")]
AmbiguousFace,
#[error("face distance is not finite and coherent")]
InvalidMeasurement,
#[error("face distance evidence exactness must match geometry fidelity and be reviewable")]
EvidenceFidelityMismatch,
#[error("the face distance of an object to itself is undefined")]
SameObject,
}
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
pub struct FaceDistanceRequest {
body: ObjectId,
host: ObjectId,
faces: FaceClass,
}
impl FaceDistanceRequest {
pub fn try_new(
body: ObjectId,
host: ObjectId,
faces: FaceClass,
) -> Result<Self, FaceDistanceError> {
if body == host {
return Err(FaceDistanceError::SameObject);
}
Ok(Self { body, host, faces })
}
pub fn body(&self) -> &ObjectId {
&self.body
}
pub fn host(&self) -> &ObjectId {
&self.host
}
pub fn faces(&self) -> FaceClass {
self.faces
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct FaceDistanceEvidence {
request: FaceDistanceRequest,
signed: SignedDistanceInterval,
fidelity: GeometryFidelity,
evidence: Evidence,
}
impl FaceDistanceEvidence {
pub fn try_new(
request: FaceDistanceRequest,
signed: SignedDistanceInterval,
fidelity: GeometryFidelity,
evidence: Evidence,
) -> Result<Self, FaceDistanceError> {
let deviation = fidelity.deviation_metres();
if !deviation.is_finite() || deviation < 0.0 {
return Err(FaceDistanceError::InvalidMeasurement);
}
if evidence.exact != fidelity.is_exact() || evidence.locator.trim().is_empty() {
return Err(FaceDistanceError::EvidenceFidelityMismatch);
}
Ok(Self {
request,
signed,
fidelity,
evidence,
})
}
pub fn request(&self) -> &FaceDistanceRequest {
&self.request
}
pub fn signed(&self) -> SignedDistanceInterval {
self.signed
}
pub fn fidelity(&self) -> GeometryFidelity {
self.fidelity
}
pub fn evidence(&self) -> &Evidence {
&self.evidence
}
}
pub trait ProximityService: Send + Sync + 'static {
fn bounds(&self, object: &ObjectId) -> Result<ObjectBounds, ProximityError>;
fn measure_proximity(
&self,
request: &ProximityRequest,
) -> Result<ProximityEvidence, ProximityError>;
fn measure_distance(
&self,
request: &ProximityRequest,
) -> Result<ProjectedDistanceEvidence, ProximityError> {
match request.projection() {
ProximityProjection::Minimum3d => {
ProjectedDistanceEvidence::from_proximity(&self.measure_proximity(request)?)
}
_ => Err(ProximityError::UnsupportedProjection),
}
}
fn measure_face_distance(
&self,
request: &FaceDistanceRequest,
) -> Result<FaceDistanceEvidence, FaceDistanceError> {
let _ = request;
Err(FaceDistanceError::Unsupported)
}
fn measure_region_distance(
&self,
request: &RegionDistanceRequest,
) -> Result<RegionDistanceEvidence, ProximityError> {
let _ = request;
Err(ProximityError::UnsupportedProjection)
}
fn measure_overlap_along(
&self,
request: &OverlapAlongRequest,
) -> Result<OverlapAlongEvidence, ProximityError> {
let _ = request;
Err(ProximityError::UnsupportedProjection)
}
fn measure_body_volume(&self, object: &ObjectId) -> Result<BodyVolume, ProximityError> {
let _ = object;
Err(ProximityError::Unavailable)
}
}
#[derive(Clone)]
pub struct ProximityServiceHandle(Arc<dyn ProximityService>);
impl ProximityServiceHandle {
pub fn new(service: Arc<dyn ProximityService>) -> Self {
Self(service)
}
pub fn bounds(&self, object: &ObjectId) -> Result<ObjectBounds, ProximityError> {
self.0.bounds(object)
}
pub fn measure_proximity(
&self,
request: &ProximityRequest,
) -> Result<ProximityEvidence, ProximityError> {
self.0.measure_proximity(request)
}
pub fn measure_distance(
&self,
request: &ProximityRequest,
) -> Result<ProjectedDistanceEvidence, ProximityError> {
let measured = self.0.measure_distance(request)?;
if measured.request() != request {
return Err(ProximityError::InvalidMeasurement);
}
Ok(measured)
}
pub fn measure_face_distance(
&self,
request: &FaceDistanceRequest,
) -> Result<FaceDistanceEvidence, FaceDistanceError> {
let measured = self.0.measure_face_distance(request)?;
if measured.request() != request {
return Err(FaceDistanceError::InvalidMeasurement);
}
Ok(measured)
}
pub fn measure_overlap_along(
&self,
request: &OverlapAlongRequest,
) -> Result<OverlapAlongEvidence, ProximityError> {
let measured = self.0.measure_overlap_along(request)?;
if measured.request() != request {
return Err(ProximityError::InvalidMeasurement);
}
Ok(measured)
}
pub fn measure_body_volume(&self, object: &ObjectId) -> Result<BodyVolume, ProximityError> {
let measured = self.0.measure_body_volume(object)?;
if measured.object() != object {
return Err(ProximityError::InvalidMeasurement);
}
Ok(measured)
}
pub fn measure_region_distance(
&self,
request: &RegionDistanceRequest,
) -> Result<RegionDistanceEvidence, ProximityError> {
let measured = self.0.measure_region_distance(request)?;
if measured.request() != request {
return Err(ProximityError::InvalidMeasurement);
}
Ok(measured)
}
}
#[cfg(test)]
mod tests {
use super::*;
use axioval_ir::SourceId;
fn id(local: &str) -> ObjectId {
ObjectId::new(SourceId::new("cad", "m").unwrap(), local).unwrap()
}
fn request() -> ProximityRequest {
ProximityRequest::try_new(id("pipe"), id("wall")).unwrap()
}
struct OneVolume(BodyVolume);
#[test]
fn a_body_volume_keeps_its_exactness_honest_and_names_its_object() {
let tessellated = GeometryFidelity::Tessellated {
chord_deviation_metres: 0.01,
};
let volume = VolumeInterval::try_new(1.0, 1.1).unwrap();
assert_eq!(
BodyVolume::try_new(id("wall"), volume, tessellated, exact()),
Err(ProximityError::EvidenceFidelityMismatch)
);
let measured = BodyVolume::try_new(id("wall"), volume, tessellated, approximate()).unwrap();
let handle = ProximityServiceHandle::new(Arc::new(OneVolume(measured.clone())));
assert_eq!(handle.measure_body_volume(&id("wall")), Ok(measured));
assert_eq!(
handle.measure_body_volume(&id("pipe")),
Err(ProximityError::InvalidMeasurement)
);
}
impl ProximityService for OneVolume {
fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
Err(ProximityError::Unavailable)
}
fn measure_proximity(
&self,
_: &ProximityRequest,
) -> Result<ProximityEvidence, ProximityError> {
Err(ProximityError::Unavailable)
}
fn measure_body_volume(&self, _: &ObjectId) -> Result<BodyVolume, ProximityError> {
Ok(self.0.clone())
}
}
fn exact() -> Evidence {
Evidence::exact(SourceId::new("cad", "m").unwrap(), "proximity:pipe:wall")
}
fn approximate() -> Evidence {
Evidence {
exact: false,
..exact()
}
}
#[test]
fn an_object_is_not_measured_against_itself() {
assert_eq!(
ProximityRequest::try_new(id("pipe"), id("pipe")),
Err(ProximityError::SameObject)
);
}
#[test]
fn evidence_exactness_must_match_fidelity() {
let tessellated = GeometryFidelity::tessellated(0.002).unwrap();
assert_eq!(
ProximityEvidence::try_new(request(), 0.1, Some(0.0), 0.0, None, tessellated, exact()),
Err(ProximityError::EvidenceFidelityMismatch)
);
assert_eq!(
ProximityEvidence::try_new(
request(),
0.1,
Some(0.0),
0.0,
None,
GeometryFidelity::Exact,
approximate()
),
Err(ProximityError::EvidenceFidelityMismatch)
);
assert!(
ProximityEvidence::try_new(
request(),
0.1,
Some(0.0),
0.0,
None,
tessellated,
approximate()
)
.is_ok()
);
}
#[test]
fn separated_bodies_cannot_penetrate_unless_one_contains_the_other() {
let exact_fidelity = GeometryFidelity::Exact;
assert_eq!(
ProximityEvidence::try_new(
request(),
0.1,
Some(0.05),
0.0,
None,
exact_fidelity,
exact()
),
Err(ProximityError::InvalidMeasurement)
);
assert!(
ProximityEvidence::try_new(
request(),
0.1,
Some(0.05),
0.0,
Some(BodyContainment::SubjectInsideCounterpart),
exact_fidelity,
exact()
)
.is_ok()
);
assert_eq!(
ProximityEvidence::try_new(
request(),
0.0,
Some(0.05),
0.0,
Some(BodyContainment::SubjectInsideCounterpart),
exact_fidelity,
exact()
),
Err(ProximityError::InvalidMeasurement)
);
}
#[test]
fn tessellated_separation_widens_by_the_combined_deviation() {
let fidelity = GeometryFidelity::tessellated(0.002)
.unwrap()
.combined(GeometryFidelity::tessellated(0.001).unwrap());
let measured = ProximityEvidence::try_new(
request(),
0.01,
Some(0.0),
0.0,
None,
fidelity,
approximate(),
)
.unwrap();
let (lower, upper) = measured.separation_interval_metres();
assert!((lower - 0.007).abs() < 1e-12 && (upper - 0.013).abs() < 1e-12);
}
#[test]
fn a_vertical_offset_must_be_finite_and_non_negative() {
for offset in [-0.1, f64::NAN, f64::INFINITY] {
assert_eq!(
ProximityRequest::projected(
id("pipe"),
id("wall"),
ProximityProjection::Vertical {
footprint_offset_metres: offset,
direction: VerticalDirection::Either,
surfaces: crate::VerticalSurfaces::Extents,
}
),
Err(ProximityError::InvalidMeasurement)
);
}
}
#[test]
fn surface_pairs_are_distinct_projections_and_nearest_takes_no_offset() {
use crate::{CounterpartSurface, SubjectSurface, VerticalSurfaces};
let between = |counterpart, offset| ProximityProjection::Vertical {
footprint_offset_metres: offset,
direction: VerticalDirection::Above,
surfaces: VerticalSurfaces::Between {
subject: SubjectSurface::Top,
counterpart,
},
};
let extents = ProximityProjection::Vertical {
footprint_offset_metres: 0.0,
direction: VerticalDirection::Above,
surfaces: VerticalSurfaces::Extents,
};
assert_ne!(between(CounterpartSurface::Nearest, 0.0), extents);
assert_ne!(
between(CounterpartSurface::Nearest, 0.0),
between(CounterpartSurface::Bottom, 0.0)
);
assert!(
ProximityRequest::projected(
id("pipe"),
id("wall"),
between(CounterpartSurface::Bottom, 0.5)
)
.is_ok()
);
assert_eq!(
ProximityRequest::projected(
id("pipe"),
id("wall"),
between(CounterpartSurface::Nearest, 0.5)
),
Err(ProximityError::InvalidMeasurement)
);
assert_eq!(SubjectSurface::Bottom.name(), "bottom");
assert_eq!(CounterpartSurface::Nearest.name(), "nearest");
}
#[test]
fn a_vertical_direction_distinguishes_requests() {
let vertical = |direction| ProximityProjection::Vertical {
footprint_offset_metres: 0.0,
direction,
surfaces: crate::VerticalSurfaces::Extents,
};
let above = vertical(VerticalDirection::Above);
assert_ne!(above, vertical(VerticalDirection::Below));
assert_ne!(above, vertical(VerticalDirection::Either));
assert!(vertical(VerticalDirection::Either) < above);
}
#[test]
fn full_proximity_evidence_is_only_measured_in_space() {
let horizontal =
ProximityRequest::projected(id("pipe"), id("wall"), ProximityProjection::Horizontal)
.unwrap();
assert_eq!(
ProximityEvidence::try_new(
horizontal,
0.1,
Some(0.0),
0.0,
None,
GeometryFidelity::Exact,
exact()
),
Err(ProximityError::UnsupportedProjection)
);
}
#[test]
fn projected_distance_intervals_are_coherent() {
let plan =
ProximityRequest::projected(id("pipe"), id("wall"), ProximityProjection::PlanOverlap)
.unwrap();
let tessellated = GeometryFidelity::tessellated(0.002).unwrap();
assert!(
ProjectedDistanceEvidence::try_new(
plan.clone(),
f64::INFINITY,
f64::INFINITY,
GeometryFidelity::Exact,
exact()
)
.is_ok()
);
assert_eq!(
ProjectedDistanceEvidence::try_new(
plan.clone(),
0.0,
f64::INFINITY,
GeometryFidelity::Exact,
exact()
),
Err(ProximityError::InvalidMeasurement)
);
assert!(
ProjectedDistanceEvidence::try_new(
plan.clone(),
0.0,
f64::INFINITY,
tessellated,
approximate()
)
.is_ok()
);
assert_eq!(
ProjectedDistanceEvidence::try_new(plan, 0.0, 0.0, tessellated, exact()),
Err(ProximityError::EvidenceFidelityMismatch)
);
assert_eq!(
ProjectedDistanceEvidence::try_new(
request(),
0.0,
f64::INFINITY,
tessellated,
approximate()
),
Err(ProximityError::InvalidMeasurement)
);
assert_eq!(
ProjectedDistanceEvidence::try_new(request(), 0.3, 0.2, tessellated, approximate()),
Err(ProximityError::InvalidMeasurement)
);
}
#[test]
fn horizontal_extent_is_the_narrower_plan_axis() {
let extents = OverlapExtents::new(
LengthInterval::try_new(0.1, 0.3).unwrap(),
LengthInterval::try_new(0.2, 0.25).unwrap(),
LengthInterval::exact(1.0).unwrap(),
);
assert_eq!(
extents.horizontal(),
LengthInterval::try_new(0.1, 0.25).unwrap()
);
assert_eq!(extents.vertical(), LengthInterval::exact(1.0).unwrap());
}
#[test]
fn overlap_extents_need_a_shared_volume() {
let extents = OverlapExtents::new(
LengthInterval::exact(0.1).unwrap(),
LengthInterval::exact(0.1).unwrap(),
LengthInterval::exact(0.1).unwrap(),
);
let measured = |separation: f64, penetration: Option<f64>| {
ProximityEvidence::try_new(
request(),
separation,
penetration,
0.0,
None,
GeometryFidelity::Exact,
exact(),
)
.unwrap()
};
assert!(
measured(0.0, Some(0.1))
.with_overlap_extents(extents)
.is_ok()
);
assert_eq!(
measured(0.0, None).with_overlap_extents(extents),
Err(ProximityError::InvalidMeasurement)
);
assert_eq!(
measured(0.2, Some(0.0)).with_overlap_extents(extents),
Err(ProximityError::InvalidMeasurement)
);
}
#[test]
fn hausdorff_distance_is_never_below_the_separation() {
let measured = ProximityEvidence::try_new(
request(),
0.2,
Some(0.0),
0.0,
None,
GeometryFidelity::Exact,
exact(),
)
.unwrap();
assert_eq!(
measured
.clone()
.with_hausdorff(LengthInterval::try_new(0.0, 0.1).unwrap()),
Err(ProximityError::InvalidMeasurement)
);
assert!(
measured
.with_hausdorff(LengthInterval::try_new(0.2, 0.5).unwrap())
.is_ok()
);
}
fn volume(lower: f64, upper: f64) -> VolumeInterval {
VolumeInterval::try_new(lower, upper).unwrap()
}
#[test]
fn volume_intervals_are_finite_non_negative_and_ordered() {
for (lower, upper) in [
(-0.1, 0.1),
(0.2, 0.1),
(0.0, f64::INFINITY),
(f64::NAN, 1.0),
] {
assert_eq!(
VolumeInterval::try_new(lower, upper),
Err(ProximityError::InvalidMeasurement)
);
}
assert!(VolumeInterval::exact(0.0).unwrap().is_exact());
}
#[test]
fn a_shared_volume_never_exceeds_either_body() {
assert_eq!(
IntersectionVolume::try_new(volume(0.5, 0.6), volume(1.0, 1.0), volume(0.4, 0.4)),
Err(ProximityError::InvalidMeasurement)
);
let measured =
IntersectionVolume::try_new(volume(0.3, 0.4), volume(1.0, 1.0), volume(0.4, 0.5))
.unwrap();
assert_eq!(measured.smaller(), volume(0.4, 0.5));
let (lower, upper) = measured.ratio_of_smaller();
assert!(lower <= 0.6 && lower > 0.599, "{lower}");
assert!((upper - 1.0).abs() < f64::EPSILON, "{upper}");
}
#[test]
fn a_volume_share_is_rounded_outward_and_clamped() {
let third = volume(1.0 / 3.0, 1.0 / 3.0);
let (lower, upper) = third.share_of(volume(1.0, 1.0));
assert!(lower < 1.0 / 3.0 && upper > 1.0 / 3.0);
assert_eq!(volume(0.0, 0.0).share_of(volume(0.0, 0.0)), (0.0, 1.0));
assert!((volume(1.0, 1.0).share_of(volume(1.0, 1.0)).1 - 1.0).abs() < f64::EPSILON);
}
#[test]
fn an_intersection_volume_needs_a_shared_inside() {
let measured = |separation: f64, penetration: Option<f64>, containment| {
ProximityEvidence::try_new(
request(),
separation,
penetration,
0.0,
containment,
GeometryFidelity::Exact,
exact(),
)
.unwrap()
};
let shared = |lower: f64, upper: f64| {
IntersectionVolume::try_new(volume(lower, upper), volume(1.0, 1.0), volume(8.0, 8.0))
.unwrap()
};
assert!(
measured(0.0, Some(0.1), None)
.with_intersection_volume(shared(0.2, 0.2))
.is_ok()
);
assert_eq!(
measured(0.0, None, None).with_intersection_volume(shared(0.0, 0.0)),
Err(ProximityError::InvalidMeasurement)
);
assert_eq!(
measured(0.2, Some(0.0), None).with_intersection_volume(shared(0.1, 0.2)),
Err(ProximityError::InvalidMeasurement)
);
assert!(
measured(0.2, Some(0.0), None)
.with_intersection_volume(shared(0.0, 0.0))
.is_ok()
);
let inside = || {
measured(
0.2,
Some(0.1),
Some(BodyContainment::SubjectInsideCounterpart),
)
};
assert!(inside().with_intersection_volume(shared(1.0, 1.0)).is_ok());
assert_eq!(
inside().with_intersection_volume(shared(0.5, 0.6)),
Err(ProximityError::InvalidMeasurement)
);
}
#[test]
fn a_face_distance_is_between_two_objects() {
assert_eq!(
FaceDistanceRequest::try_new(id("pipe"), id("pipe"), FaceClass::Top),
Err(FaceDistanceError::SameObject)
);
for class in [
FaceClass::Top,
FaceClass::Side,
FaceClass::Bottom,
FaceClass::Any,
] {
assert_eq!(FaceClass::parse(class.name()), Some(class));
}
assert_eq!(FaceClass::parse("front"), None);
let request =
FaceDistanceRequest::try_new(id("pipe"), id("wall"), FaceClass::Side).unwrap();
let signed = SignedDistanceInterval::try_new(-0.1, 0.2).unwrap();
assert_eq!(
FaceDistanceEvidence::try_new(
request.clone(),
signed,
GeometryFidelity::tessellated(0.001).unwrap(),
exact()
),
Err(FaceDistanceError::EvidenceFidelityMismatch)
);
assert!(
FaceDistanceEvidence::try_new(request, signed, GeometryFidelity::Exact, exact())
.is_ok()
);
}
#[test]
fn face_distances_are_refused_by_default() {
struct Silent;
impl ProximityService for Silent {
fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
Err(ProximityError::Unavailable)
}
fn measure_proximity(
&self,
_: &ProximityRequest,
) -> Result<ProximityEvidence, ProximityError> {
Err(ProximityError::Unavailable)
}
}
let request = FaceDistanceRequest::try_new(id("pipe"), id("wall"), FaceClass::Any).unwrap();
assert_eq!(
ProximityServiceHandle::new(Arc::new(Silent)).measure_face_distance(&request),
Err(FaceDistanceError::Unsupported)
);
}
#[test]
fn box_gap_is_euclidean_and_zero_when_boxes_meet() {
let a = Bounds3::try_new([0.0; 3], [1.0; 3]).unwrap();
let b = Bounds3::try_new([4.0, 5.0, 0.0], [5.0, 6.0, 1.0]).unwrap();
assert!((a.gap(&b) - 5.0).abs() < 1e-12);
let touching = Bounds3::try_new([1.0, 0.0, 0.0], [2.0, 1.0, 1.0]).unwrap();
assert!(a.gap(&touching).abs() < f64::EPSILON);
assert_eq!(
Bounds3::try_new([1.0, 0.0, 0.0], [0.0, 1.0, 1.0]),
Err(ProximityError::InvalidMeasurement)
);
}
#[test]
fn region_distances_bind_their_request_and_default_to_refusal() {
struct Nothing;
impl ProximityService for Nothing {
fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
Err(ProximityError::Unavailable)
}
fn measure_proximity(
&self,
_: &ProximityRequest,
) -> Result<ProximityEvidence, ProximityError> {
Err(ProximityError::Unavailable)
}
}
let region = ConvexPlanRegion::try_new(vec![[0.0, 0.0], [1.0, 0.0], [0.0, 1.0]]).unwrap();
let request = RegionDistanceRequest::new(region, id("wall"));
let evidence = |exact: bool| Evidence {
source: SourceId::new("cad", "m").unwrap(),
locator: "region".into(),
exact,
};
assert!(
RegionDistanceEvidence::try_new(
request.clone(),
0.5,
0.5,
GeometryFidelity::Exact,
evidence(true)
)
.is_ok()
);
assert_eq!(
RegionDistanceEvidence::try_new(
request.clone(),
0.4,
0.5,
GeometryFidelity::Exact,
evidence(true)
),
Err(ProximityError::InvalidMeasurement)
);
assert_eq!(
RegionDistanceEvidence::try_new(
request.clone(),
0.4,
0.5,
GeometryFidelity::tessellated(0.05).unwrap(),
evidence(true)
),
Err(ProximityError::EvidenceFidelityMismatch)
);
assert_eq!(
ProximityServiceHandle::new(Arc::new(Nothing)).measure_region_distance(&request),
Err(ProximityError::UnsupportedProjection)
);
}
#[test]
fn extents_along_directions_bind_their_request_and_default_to_refusal() {
struct Nothing;
impl ProximityService for Nothing {
fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
Err(ProximityError::Unavailable)
}
fn measure_proximity(
&self,
_: &ProximityRequest,
) -> Result<ProximityEvidence, ProximityError> {
Err(ProximityError::Unavailable)
}
}
struct Other(OverlapAlongEvidence);
impl ProximityService for Other {
fn bounds(&self, _: &ObjectId) -> Result<ObjectBounds, ProximityError> {
Err(ProximityError::Unavailable)
}
fn measure_proximity(
&self,
_: &ProximityRequest,
) -> Result<ProximityEvidence, ProximityError> {
Err(ProximityError::Unavailable)
}
fn measure_overlap_along(
&self,
_: &OverlapAlongRequest,
) -> Result<OverlapAlongEvidence, ProximityError> {
Ok(self.0.clone())
}
}
let axis = |vector| MetricDirection::try_new(vector).unwrap();
let along = |directions: Vec<MetricDirection>| {
OverlapAlongRequest::try_new(id("pipe"), id("wall"), directions)
};
assert_eq!(along(Vec::new()), Err(ProximityError::InvalidMeasurement));
assert_eq!(
along(vec![axis([1.0, 0.0, 0.0]); 7]),
Err(ProximityError::InvalidMeasurement)
);
assert_eq!(
OverlapAlongRequest::try_new(id("pipe"), id("pipe"), vec![axis([0.0, 0.0, 1.0])]),
Err(ProximityError::SameObject)
);
let request = along(vec![axis([1.0, 1.0, 0.0]), axis([0.0, 0.0, 1.0])]).unwrap();
let extent = LengthInterval::try_new(0.01, 0.02).unwrap();
assert_eq!(
OverlapAlongEvidence::try_new(
request.clone(),
vec![extent],
GeometryFidelity::Exact,
exact()
),
Err(ProximityError::InvalidMeasurement)
);
assert_eq!(
OverlapAlongEvidence::try_new(
request.clone(),
vec![extent, extent],
GeometryFidelity::tessellated(0.001).unwrap(),
exact()
),
Err(ProximityError::EvidenceFidelityMismatch)
);
let measured = OverlapAlongEvidence::try_new(
request.clone(),
vec![extent, extent],
GeometryFidelity::Exact,
exact(),
)
.unwrap();
assert_eq!(measured.extents(), &[extent, extent]);
assert_eq!(
ProximityServiceHandle::new(Arc::new(Nothing)).measure_overlap_along(&request),
Err(ProximityError::UnsupportedProjection)
);
let other = along(vec![axis([0.0, 1.0, 0.0]), axis([0.0, 0.0, 1.0])]).unwrap();
assert_eq!(
ProximityServiceHandle::new(Arc::new(Other(measured))).measure_overlap_along(&other),
Err(ProximityError::InvalidMeasurement)
);
}
}