axiolid_measure/exact_bodies/plan.rs
1//! Plan distance, clearance and overlap between bodies of several exact
2//! solids (#237): [`crate::plan_boundary_distance`] and its kin (#217) for
3//! [`PlacedBody`] sides.
4//!
5//! # What is measured
6//!
7//! A body's shadow on the XY plane is the union of its items' shadows, and
8//! each item's shadow is its boundary's (a vertical line through a point of
9//! a solid meets its boundary). So the plan distance between two bodies is
10//! the least horizontal distance between a boundary point of an item of one
11//! and a boundary point of an item of the other: the minimum over item
12//! pairs, found by ONE branch and bound over the elements of every item, as
13//! [`super::body_boundary_distance`] in space. A pair of items far apart in
14//! plan gets a large lower bound at the root and is never refined, and the
15//! witnesses name the items they lie on.
16//!
17//! As for one pair, the distance is zero where the shadows overlap and
18//! where one item's shadow lies inside another's: every boundary point of
19//! the inner item then has a boundary point of the outer one directly above
20//! or below it. Over several items this holds item pair by item pair, so a
21//! body standing on another's footprint measures zero whichever of its
22//! items does.
23//!
24//! # Items of one body
25//!
26//! How the items of one body lie against each other does not matter in
27//! plan: their shadows may overlap freely (a column over its footing), and
28//! the union of the shadows is the union of the boundaries' shadows however
29//! the items touch, overlap or nest. So no layout is checked and nothing is
30//! cut, unlike the Hausdorff queries in [`super`]; the items are only
31//! merged and placed.
32//!
33//! # Placement
34//!
35//! The placement is applied before projecting: projection is along the
36//! world's `z`, so a body turned about `z` measures as its plan turned, and
37//! a tilted body casts the shadow of its tilted items. Witnesses are in the
38//! world.
39
40use axiolid_core::{Point2, Scalar, Tolerance};
41
42use super::{distance, BodyDistance, BodyMeasureError, Items, PlacedBody};
43use crate::exact_distance::{closed, overlap_search, Clearance, Metric, PlanOverlap};
44
45/// Plan distance between two bodies of exact solids -- between their
46/// shadows on the XY plane -- to within `accuracy` (#237): the least over
47/// item pairs, by one shared search.
48///
49/// Zero where an item's shadow overlaps or lies inside one of the other
50/// body's, as [`crate::plan_boundary_distance`] for one pair; items of one
51/// body may overlap in plan, or in space. When two planar faces show an
52/// overlap of positive area, the interval is `[0, 0]` and the witnesses
53/// project into it. `point_a` and `point_b` lie on the items `item_a` and
54/// `item_b` name, their projections at most `upper` apart, within
55/// rounding. A negative or NaN
56/// accuracy is read as zero.
57///
58/// # Errors
59///
60/// [`BodyMeasureError::EmptyBody`] for an empty body,
61/// [`BodyMeasureError::Placement`] for a placement that is not rigid, and
62/// [`BodyMeasureError::Measure`] with what
63/// [`crate::plan_boundary_distance`] refuses.
64pub fn body_plan_boundary_distance<'a, 'b>(
65 a: impl Into<PlacedBody<'a>>,
66 b: impl Into<PlacedBody<'b>>,
67 accuracy: Scalar,
68 tolerance: Tolerance,
69) -> Result<BodyDistance, BodyMeasureError> {
70 let accuracy = accuracy.max(0.0);
71 distance(
72 a.into(),
73 b.into(),
74 tolerance,
75 Metric::Plan,
76 &mut |lower, upper, floor| closed(lower, upper, accuracy, floor),
77 )
78}
79
80/// [`body_plan_boundary_distance`] refined only until it clears `limit`.
81///
82/// # Errors
83///
84/// As [`body_plan_boundary_distance`].
85pub fn body_plan_boundary_clearance<'a, 'b>(
86 a: impl Into<PlacedBody<'a>>,
87 b: impl Into<PlacedBody<'b>>,
88 limit: Scalar,
89 tolerance: Tolerance,
90) -> Result<(BodyDistance, Clearance), BodyMeasureError> {
91 let found = distance(
92 a.into(),
93 b.into(),
94 tolerance,
95 Metric::Plan,
96 &mut |lower, upper, floor| {
97 upper < limit || lower > limit || closed(lower, upper, 0.0, floor)
98 },
99 )?;
100 let clearance = found.bounds.against(limit);
101 Ok((found, clearance))
102}
103
104/// Whether the plan projections of two bodies overlap, and which items
105/// show it.
106#[derive(Debug, Clone, Copy, PartialEq)]
107pub enum BodyPlanOverlap {
108 /// Over a patch of positive area: `at` and a disc around it lie in the
109 /// shadows of item `item_a` of the first body and item `item_b` of the
110 /// second, shown by a planar face of each that is not vertical.
111 Overlapping {
112 /// A plan point inside both items' shadows.
113 at: Point2,
114 /// The item of the first body, by index in its slice.
115 item_a: usize,
116 /// The item of the second body, by index in its slice.
117 item_b: usize,
118 },
119 /// Certainly apart: no item of one comes within `gap`, positive, of an
120 /// item of the other in plan.
121 Disjoint {
122 /// A certified lower bound on the plan distance between the bodies.
123 gap: Scalar,
124 },
125 /// Neither shown: the shadows may only touch, overlap where no planar
126 /// face covers both, or lie closer than the search resolves.
127 Undecided,
128}
129
130/// Whether the plan projections of two bodies overlap over a patch of
131/// positive area -- an item of one over an item of the other -- are
132/// certainly apart, or neither could be shown (#237).
133///
134/// As [`crate::plan_overlap`] for one pair, over every item pair by one
135/// search: overlap is shown by two planar faces, one of an item of each
136/// body, not vertical, whose shadows share an open patch. Items of one body
137/// overlapping each other in plan are not an overlap between the bodies.
138///
139/// # Errors
140///
141/// As [`body_plan_boundary_distance`].
142pub fn body_plan_overlap<'a, 'b>(
143 a: impl Into<PlacedBody<'a>>,
144 b: impl Into<PlacedBody<'b>>,
145 tolerance: Tolerance,
146) -> Result<BodyPlanOverlap, BodyMeasureError> {
147 let (a, b) = Items::pair(a.into(), b.into())?;
148 let (verdict, on) = overlap_search(&a.brep, &b.brep, tolerance)?;
149 Ok(match (verdict, on) {
150 (PlanOverlap::Overlapping { at }, Some((on_a, on_b))) => BodyPlanOverlap::Overlapping {
151 at,
152 item_a: a.item(on_a),
153 item_b: b.item(on_b),
154 },
155 (PlanOverlap::Disjoint { gap }, _) => BodyPlanOverlap::Disjoint { gap },
156 _ => BodyPlanOverlap::Undecided,
157 })
158}