axiolid_measure/
proximity.rs1use core::fmt;
8
9use axiolid_core::Point3;
10
11#[derive(Debug, Clone, Copy, PartialEq)]
13pub struct ClosestPoints3 {
14 pub point_a: Point3,
16 pub point_b: Point3,
18 pub distance_squared: f64,
20}
21
22impl ClosestPoints3 {
23 fn new(point_a: Point3, point_b: Point3) -> Self {
24 Self {
25 point_a,
26 point_b,
27 distance_squared: (point_a - point_b).length_squared(),
28 }
29 }
30}
31
32#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub enum ProximityError {
35 NonFiniteInput,
37 DegenerateTriangle,
39}
40
41impl fmt::Display for ProximityError {
42 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
43 match self {
44 Self::NonFiniteInput => formatter.write_str("proximity input must be finite"),
45 Self::DegenerateTriangle => formatter.write_str("triangle has zero area"),
46 }
47 }
48}
49
50impl std::error::Error for ProximityError {}
51
52pub fn closest_points_on_segments(
57 first: [Point3; 2],
58 second: [Point3; 2],
59) -> Result<ClosestPoints3, ProximityError> {
60 if !first.into_iter().chain(second).all(Point3::is_finite) {
61 return Err(ProximityError::NonFiniteInput);
62 }
63
64 let [p1, q1] = first;
65 let [p2, q2] = second;
66 let d1 = q1 - p1;
67 let d2 = q2 - p2;
68 let r = p1 - p2;
69 let first_length_squared = d1.dot(d1);
70 let second_length_squared = d2.dot(d2);
71 let second_projection = d2.dot(r);
72
73 let (mut first_parameter, mut second_parameter);
74 if first_length_squared == 0.0 && second_length_squared == 0.0 {
75 return Ok(ClosestPoints3::new(p1, p2));
76 }
77 if first_length_squared == 0.0 {
78 first_parameter = 0.0;
79 second_parameter = (second_projection / second_length_squared).clamp(0.0, 1.0);
80 } else {
81 let first_projection = d1.dot(r);
82 if second_length_squared == 0.0 {
83 second_parameter = 0.0;
84 first_parameter = (-first_projection / first_length_squared).clamp(0.0, 1.0);
85 } else {
86 let directions_dot = d1.dot(d2);
87 let denominator =
88 first_length_squared * second_length_squared - directions_dot * directions_dot;
89 first_parameter = if denominator != 0.0 {
90 ((directions_dot * second_projection - first_projection * second_length_squared)
91 / denominator)
92 .clamp(0.0, 1.0)
93 } else {
94 0.0
95 };
96 second_parameter =
97 (directions_dot * first_parameter + second_projection) / second_length_squared;
98 if second_parameter < 0.0 {
99 second_parameter = 0.0;
100 first_parameter = (-first_projection / first_length_squared).clamp(0.0, 1.0);
101 } else if second_parameter > 1.0 {
102 second_parameter = 1.0;
103 first_parameter =
104 ((directions_dot - first_projection) / first_length_squared).clamp(0.0, 1.0);
105 }
106 }
107 }
108
109 Ok(ClosestPoints3::new(
110 p1 + d1 * first_parameter,
111 p2 + d2 * second_parameter,
112 ))
113}
114
115pub fn closest_point_on_triangle(
117 point: Point3,
118 triangle: [Point3; 3],
119) -> Result<Point3, ProximityError> {
120 validate_triangle(triangle)?;
121 if !point.is_finite() {
122 return Err(ProximityError::NonFiniteInput);
123 }
124 Ok(closest_point_on_valid_triangle(point, triangle))
125}
126
127pub fn closest_points_on_triangles(
134 first: [Point3; 3],
135 second: [Point3; 3],
136) -> Result<ClosestPoints3, ProximityError> {
137 validate_triangle(first)?;
138 validate_triangle(second)?;
139
140 let mut best = ClosestPoints3 {
141 point_a: Point3::ZERO,
142 point_b: Point3::ZERO,
143 distance_squared: f64::INFINITY,
144 };
145 for point in first {
146 update_best(
147 &mut best,
148 ClosestPoints3::new(point, closest_point_on_valid_triangle(point, second)),
149 );
150 }
151 for point in second {
152 update_best(
153 &mut best,
154 ClosestPoints3::new(closest_point_on_valid_triangle(point, first), point),
155 );
156 }
157 for first_edge in triangle_edges(first) {
158 for second_edge in triangle_edges(second) {
159 update_best(
160 &mut best,
161 closest_points_on_segments(first_edge, second_edge)?,
162 );
163 }
164 }
165 if let Some(point) = crossing_point(first, second) {
171 update_best(
172 &mut best,
173 ClosestPoints3 {
174 point_a: point,
175 point_b: point,
176 distance_squared: 0.0,
177 },
178 );
179 }
180 Ok(best)
181}
182
183fn crossing_point(first: [Point3; 3], second: [Point3; 3]) -> Option<Point3> {
189 for edge in triangle_edges(first) {
190 if let Some(point) = segment_triangle_crossing(edge, second) {
191 return Some(point);
192 }
193 }
194 for edge in triangle_edges(second) {
195 if let Some(point) = segment_triangle_crossing(edge, first) {
196 return Some(point);
197 }
198 }
199 None
200}
201
202fn segment_triangle_crossing(segment: [Point3; 2], triangle: [Point3; 3]) -> Option<Point3> {
208 let [start, end] = segment;
209 let direction = end - start;
210 let edge1 = triangle[1] - triangle[0];
211 let edge2 = triangle[2] - triangle[0];
212 let cross = direction.cross(edge2);
213 let determinant = edge1.dot(cross);
214
215 let scale = direction.length() * edge1.length() * edge2.length();
216 if determinant.abs() <= scale * f64::EPSILON {
217 return None;
220 }
221
222 let inverse = 1.0 / determinant;
223 let to_start = start - triangle[0];
224 let u = to_start.dot(cross) * inverse;
225 if !(0.0..=1.0).contains(&u) {
226 return None;
227 }
228 let q = to_start.cross(edge1);
229 let v = direction.dot(q) * inverse;
230 if v < 0.0 || u + v > 1.0 {
231 return None;
232 }
233 let t = edge2.dot(q) * inverse;
234 (0.0..=1.0).contains(&t).then(|| start + direction * t)
235}
236
237fn validate_triangle(triangle: [Point3; 3]) -> Result<(), ProximityError> {
238 if !triangle.into_iter().all(Point3::is_finite) {
239 return Err(ProximityError::NonFiniteInput);
240 }
241 let [a, b, c] = triangle;
242 ((b - a).cross(c - a).length_squared() != 0.0)
243 .then_some(())
244 .ok_or(ProximityError::DegenerateTriangle)
245}
246
247fn update_best(best: &mut ClosestPoints3, candidate: ClosestPoints3) {
248 if candidate.distance_squared < best.distance_squared {
249 *best = candidate;
250 }
251}
252
253fn triangle_edges(triangle: [Point3; 3]) -> [[Point3; 2]; 3] {
254 [
255 [triangle[0], triangle[1]],
256 [triangle[1], triangle[2]],
257 [triangle[2], triangle[0]],
258 ]
259}
260
261fn closest_point_on_valid_triangle(point: Point3, triangle: [Point3; 3]) -> Point3 {
262 let [a, b, c] = triangle;
263 let ab = b - a;
264 let ac = c - a;
265 let ap = point - a;
266 let dot_ab_ap = ab.dot(ap);
267 let dot_ac_ap = ac.dot(ap);
268 if dot_ab_ap <= 0.0 && dot_ac_ap <= 0.0 {
269 return a;
270 }
271
272 let bp = point - b;
273 let dot_ab_bp = ab.dot(bp);
274 let dot_ac_bp = ac.dot(bp);
275 if dot_ab_bp >= 0.0 && dot_ac_bp <= dot_ab_bp {
276 return b;
277 }
278
279 let determinant_c = dot_ab_ap * dot_ac_bp - dot_ab_bp * dot_ac_ap;
280 if determinant_c <= 0.0 && dot_ab_ap >= 0.0 && dot_ab_bp <= 0.0 {
281 return a + ab * (dot_ab_ap / (dot_ab_ap - dot_ab_bp));
282 }
283
284 let cp = point - c;
285 let dot_ab_cp = ab.dot(cp);
286 let dot_ac_cp = ac.dot(cp);
287 if dot_ac_cp >= 0.0 && dot_ab_cp <= dot_ac_cp {
288 return c;
289 }
290
291 let determinant_b = dot_ab_cp * dot_ac_ap - dot_ab_ap * dot_ac_cp;
292 if determinant_b <= 0.0 && dot_ac_ap >= 0.0 && dot_ac_cp <= 0.0 {
293 return a + ac * (dot_ac_ap / (dot_ac_ap - dot_ac_cp));
294 }
295
296 let determinant_a = dot_ab_bp * dot_ac_cp - dot_ab_cp * dot_ac_bp;
297 if determinant_a <= 0.0 && dot_ac_bp - dot_ab_bp >= 0.0 && dot_ab_cp - dot_ac_cp >= 0.0 {
298 let edge_parameter =
299 (dot_ac_bp - dot_ab_bp) / ((dot_ac_bp - dot_ab_bp) + (dot_ab_cp - dot_ac_cp));
300 return b + (c - b) * edge_parameter;
301 }
302
303 let denominator = 1.0 / (determinant_a + determinant_b + determinant_c);
304 a + ab * (determinant_b * denominator) + ac * (determinant_c * denominator)
305}