1#![allow(
14 clippy::suboptimal_flops,
15 clippy::many_single_char_names,
16 clippy::similar_names
17)]
18
19use crate::vec::{Point2, Point3};
20
21#[must_use]
30pub fn filtered_orient2d(a: Point2, b: Point2, c: Point2) -> f64 {
31 let acx = a.x() - c.x();
32 let bcx = b.x() - c.x();
33 let acy = a.y() - c.y();
34 let bcy = b.y() - c.y();
35
36 let det = acx * bcy - acy * bcx;
37
38 let det_sum = (acx * bcy).abs() + (acy * bcx).abs();
40
41 let eps = f64::EPSILON;
43 let err_bound = (3.0 + 16.0 * eps) * eps * det_sum;
44
45 if det.abs() > err_bound {
46 det
47 } else {
48 crate::predicates::orient2d(a, b, c)
50 }
51}
52
53#[must_use]
58pub fn filtered_orient3d(a: Point3, b: Point3, c: Point3, d: Point3) -> f64 {
59 let adx = a.x() - d.x();
60 let bdx = b.x() - d.x();
61 let cdx = c.x() - d.x();
62 let ady = a.y() - d.y();
63 let bdy = b.y() - d.y();
64 let cdy = c.y() - d.y();
65 let adz = a.z() - d.z();
66 let bdz = b.z() - d.z();
67 let cdz = c.z() - d.z();
68
69 let det = adx * (bdy * cdz - bdz * cdy) - bdx * (ady * cdz - adz * cdy)
70 + cdx * (ady * bdz - adz * bdy);
71
72 let permanent = (adx.abs() * ((bdy * cdz).abs() + (bdz * cdy).abs()))
74 + (bdx.abs() * ((ady * cdz).abs() + (adz * cdy).abs()))
75 + (cdx.abs() * ((ady * bdz).abs() + (adz * bdy).abs()));
76
77 let eps = f64::EPSILON;
78 let err_bound = (7.0 + 56.0 * eps) * eps * permanent;
79
80 if det.abs() > err_bound {
81 det
82 } else {
83 crate::predicates::orient3d(a, b, c, d)
84 }
85}
86
87#[must_use]
93pub fn filtered_in_circle(a: Point2, b: Point2, c: Point2, d: Point2) -> f64 {
94 let adx = a.x() - d.x();
95 let ady = a.y() - d.y();
96 let bdx = b.x() - d.x();
97 let bdy = b.y() - d.y();
98 let cdx = c.x() - d.x();
99 let cdy = c.y() - d.y();
100
101 let abdet = adx * bdy - bdx * ady;
102 let bcdet = bdx * cdy - cdx * bdy;
103 let cadet = cdx * ady - adx * cdy;
104 let alift = adx * adx + ady * ady;
105 let blift = bdx * bdx + bdy * bdy;
106 let clift = cdx * cdx + cdy * cdy;
107
108 let det = alift * bcdet + blift * cadet + clift * abdet;
109
110 let permanent = alift * ((bcdet).abs() + (bdx * cdy).abs() + (cdx * bdy).abs())
112 + blift * ((cadet).abs() + (cdx * ady).abs() + (adx * cdy).abs())
113 + clift * ((abdet).abs() + (adx * bdy).abs() + (bdx * ady).abs());
114
115 let eps = f64::EPSILON;
116 let err_bound = (10.0 + 96.0 * eps) * eps * permanent;
117
118 if det.abs() > err_bound {
119 det
120 } else {
121 crate::predicates::in_circle(a, b, c, d)
122 }
123}
124
125#[derive(Debug, Clone, Copy, PartialEq)]
131pub enum SegmentIntersection {
132 None,
134 Point {
136 point: Point2,
138 t1: f64,
140 t2: f64,
142 },
143 Overlap {
145 start: Point2,
147 end: Point2,
149 },
150}
151
152#[must_use]
159#[allow(clippy::too_many_lines)]
160pub fn segment_intersection(a1: Point2, a2: Point2, b1: Point2, b2: Point2) -> SegmentIntersection {
161 let d1 = filtered_orient2d(a1, a2, b1);
163 let d2 = filtered_orient2d(a1, a2, b2);
164 let d3 = filtered_orient2d(b1, b2, a1);
165 let d4 = filtered_orient2d(b1, b2, a2);
166
167 if ((d1 > 0.0 && d2 < 0.0) || (d1 < 0.0 && d2 > 0.0))
169 && ((d3 > 0.0 && d4 < 0.0) || (d3 < 0.0 && d4 > 0.0))
170 {
171 let denom = (a2.x() - a1.x()) * (b2.y() - b1.y()) - (a2.y() - a1.y()) * (b2.x() - b1.x());
173
174 if denom.abs() < f64::EPSILON * 1e3 {
175 return SegmentIntersection::None; }
177
178 let t =
179 ((b1.x() - a1.x()) * (b2.y() - b1.y()) - (b1.y() - a1.y()) * (b2.x() - b1.x())) / denom;
180
181 let u =
182 ((b1.x() - a1.x()) * (a2.y() - a1.y()) - (b1.y() - a1.y()) * (a2.x() - a1.x())) / denom;
183
184 let px = (a2.x() - a1.x()).mul_add(t, a1.x());
185 let py = (a2.y() - a1.y()).mul_add(t, a1.y());
186
187 return SegmentIntersection::Point {
188 point: Point2::new(px, py),
189 t1: t,
190 t2: u,
191 };
192 }
193
194 if d1 == 0.0 && d2 == 0.0 && d3 == 0.0 && d4 == 0.0 {
196 return collinear_overlap(a1, a2, b1, b2);
197 }
198
199 if d1 == 0.0 && on_segment(a1, a2, b1) {
201 let t = segment_param(a1, a2, b1);
202 return SegmentIntersection::Point {
203 point: b1,
204 t1: t,
205 t2: 0.0,
206 };
207 }
208 if d2 == 0.0 && on_segment(a1, a2, b2) {
209 let t = segment_param(a1, a2, b2);
210 return SegmentIntersection::Point {
211 point: b2,
212 t1: t,
213 t2: 1.0,
214 };
215 }
216 if d3 == 0.0 && on_segment(b1, b2, a1) {
217 let u = segment_param(b1, b2, a1);
218 return SegmentIntersection::Point {
219 point: a1,
220 t1: 0.0,
221 t2: u,
222 };
223 }
224 if d4 == 0.0 && on_segment(b1, b2, a2) {
225 let u = segment_param(b1, b2, a2);
226 return SegmentIntersection::Point {
227 point: a2,
228 t1: 1.0,
229 t2: u,
230 };
231 }
232
233 SegmentIntersection::None
234}
235
236fn on_segment(a: Point2, b: Point2, p: Point2) -> bool {
238 let min_x = a.x().min(b.x());
239 let max_x = a.x().max(b.x());
240 let min_y = a.y().min(b.y());
241 let max_y = a.y().max(b.y());
242
243 p.x() >= min_x - f64::EPSILON
244 && p.x() <= max_x + f64::EPSILON
245 && p.y() >= min_y - f64::EPSILON
246 && p.y() <= max_y + f64::EPSILON
247}
248
249fn segment_param(a: Point2, b: Point2, p: Point2) -> f64 {
251 let dx = b.x() - a.x();
252 let dy = b.y() - a.y();
253
254 if dx.abs() > dy.abs() {
255 (p.x() - a.x()) / dx
256 } else if dy.abs() > f64::EPSILON {
257 (p.y() - a.y()) / dy
258 } else {
259 0.0
260 }
261}
262
263fn collinear_overlap(a1: Point2, a2: Point2, b1: Point2, b2: Point2) -> SegmentIntersection {
265 let dx = (a2.x() - a1.x()).abs().max((b2.x() - b1.x()).abs());
267 let dy = (a2.y() - a1.y()).abs().max((b2.y() - b1.y()).abs());
268
269 let (_ta1, _ta2, tb1_param, tb2_param) = if dx >= dy {
270 let dir = a2.x() - a1.x();
271 if dir.abs() < f64::EPSILON {
272 return SegmentIntersection::None;
273 }
274 (0.0, 1.0, (b1.x() - a1.x()) / dir, (b2.x() - a1.x()) / dir)
275 } else {
276 let dir = a2.y() - a1.y();
277 if dir.abs() < f64::EPSILON {
278 return SegmentIntersection::None;
279 }
280 (0.0, 1.0, (b1.y() - a1.y()) / dir, (b2.y() - a1.y()) / dir)
281 };
282
283 let (tb_lo, tb_hi) = if tb1_param < tb2_param {
284 (tb1_param, tb2_param)
285 } else {
286 (tb2_param, tb1_param)
287 };
288 let lo = 0.0_f64.max(tb_lo);
289 let hi = 1.0_f64.min(tb_hi);
290
291 if lo > hi + f64::EPSILON {
292 SegmentIntersection::None
293 } else if (hi - lo).abs() < f64::EPSILON {
294 let px = (a2.x() - a1.x()).mul_add(lo, a1.x());
296 let py = (a2.y() - a1.y()).mul_add(lo, a1.y());
297 let pt = Point2::new(px, py);
298 SegmentIntersection::Point {
299 point: pt,
300 t1: lo,
301 t2: segment_param(b1, b2, pt),
302 }
303 } else {
304 let sx = (a2.x() - a1.x()).mul_add(lo, a1.x());
305 let sy = (a2.y() - a1.y()).mul_add(lo, a1.y());
306 let ex = (a2.x() - a1.x()).mul_add(hi, a1.x());
307 let ey = (a2.y() - a1.y()).mul_add(hi, a1.y());
308 SegmentIntersection::Overlap {
309 start: Point2::new(sx, sy),
310 end: Point2::new(ex, ey),
311 }
312 }
313}
314
315#[cfg(test)]
316#[allow(
317 clippy::unwrap_used,
318 clippy::expect_used,
319 clippy::float_cmp,
320 clippy::suboptimal_flops,
321 clippy::panic,
322 clippy::cast_lossless
323)]
324mod tests {
325
326 use super::*;
327 use crate::vec::{Point2, Point3};
328
329 #[test]
332 fn filtered_orient2d_ccw() {
333 let a = Point2::new(0.0, 0.0);
334 let b = Point2::new(1.0, 0.0);
335 let c = Point2::new(0.0, 1.0);
336 assert!(filtered_orient2d(a, b, c) > 0.0);
337 }
338
339 #[test]
340 fn filtered_orient2d_cw() {
341 let a = Point2::new(0.0, 0.0);
342 let b = Point2::new(0.0, 1.0);
343 let c = Point2::new(1.0, 0.0);
344 assert!(filtered_orient2d(a, b, c) < 0.0);
345 }
346
347 #[test]
348 fn filtered_orient2d_collinear() {
349 let a = Point2::new(0.0, 0.0);
350 let b = Point2::new(1.0, 1.0);
351 let c = Point2::new(2.0, 2.0);
352 assert_eq!(filtered_orient2d(a, b, c), 0.0);
353 }
354
355 #[test]
356 fn filtered_orient2d_near_collinear() {
357 let a = Point2::new(0.0, 0.0);
359 let b = Point2::new(1.0, 1.0);
360 let c = Point2::new(2.0, 2.0 + 1e-15);
361 let result = filtered_orient2d(a, b, c);
363 assert!(result >= 0.0);
365 }
366
367 #[test]
370 fn filtered_in_circle_inside() {
371 let a = Point2::new(0.0, 0.0);
372 let b = Point2::new(1.0, 0.0);
373 let c = Point2::new(0.0, 1.0);
374 let d = Point2::new(0.25, 0.25);
375 assert!(filtered_in_circle(a, b, c, d) > 0.0);
376 }
377
378 #[test]
379 fn filtered_in_circle_outside() {
380 let a = Point2::new(0.0, 0.0);
381 let b = Point2::new(1.0, 0.0);
382 let c = Point2::new(0.0, 1.0);
383 let d = Point2::new(3.0, 3.0);
384 assert!(filtered_in_circle(a, b, c, d) < 0.0);
385 }
386
387 #[test]
390 fn filtered_orient3d_basic() {
391 let a = Point3::new(0.0, 0.0, 0.0);
392 let b = Point3::new(1.0, 0.0, 0.0);
393 let c = Point3::new(0.0, 1.0, 0.0);
394 let above = Point3::new(0.0, 0.0, 1.0);
396 assert!(filtered_orient3d(a, b, c, above) < 0.0);
397 let below = Point3::new(0.0, 0.0, -1.0);
399 assert!(filtered_orient3d(a, b, c, below) > 0.0);
400 let on = Point3::new(0.5, 0.5, 0.0);
402 assert_eq!(filtered_orient3d(a, b, c, on), 0.0);
403 }
404
405 #[test]
408 fn segment_intersection_crossing() {
409 let a1 = Point2::new(0.0, 0.0);
410 let a2 = Point2::new(1.0, 1.0);
411 let b1 = Point2::new(0.0, 1.0);
412 let b2 = Point2::new(1.0, 0.0);
413
414 match segment_intersection(a1, a2, b1, b2) {
415 SegmentIntersection::Point { point, t1, t2 } => {
416 assert!((point.x() - 0.5).abs() < 1e-10);
417 assert!((point.y() - 0.5).abs() < 1e-10);
418 assert!((t1 - 0.5).abs() < 1e-10);
419 assert!((t2 - 0.5).abs() < 1e-10);
420 }
421 other => panic!("expected Point, got {other:?}"),
422 }
423 }
424
425 #[test]
426 fn segment_intersection_parallel() {
427 let a1 = Point2::new(0.0, 0.0);
428 let a2 = Point2::new(1.0, 0.0);
429 let b1 = Point2::new(0.0, 1.0);
430 let b2 = Point2::new(1.0, 1.0);
431
432 assert_eq!(
433 segment_intersection(a1, a2, b1, b2),
434 SegmentIntersection::None
435 );
436 }
437
438 #[test]
439 fn segment_intersection_t_junction() {
440 let a1 = Point2::new(0.0, 0.0);
441 let a2 = Point2::new(1.0, 0.0);
442 let b1 = Point2::new(0.5, -1.0);
443 let b2 = Point2::new(0.5, 0.0); match segment_intersection(a1, a2, b1, b2) {
446 SegmentIntersection::Point { point, .. } => {
447 assert!((point.x() - 0.5).abs() < 1e-10);
448 assert!(point.y().abs() < 1e-10);
449 }
450 other => panic!("expected Point, got {other:?}"),
451 }
452 }
453
454 #[test]
455 fn segment_intersection_collinear_overlap() {
456 let a1 = Point2::new(0.0, 0.0);
457 let a2 = Point2::new(2.0, 0.0);
458 let b1 = Point2::new(1.0, 0.0);
459 let b2 = Point2::new(3.0, 0.0);
460
461 match segment_intersection(a1, a2, b1, b2) {
462 SegmentIntersection::Overlap { start, end } => {
463 assert!((start.x() - 1.0).abs() < 1e-10);
464 assert!((end.x() - 2.0).abs() < 1e-10);
465 }
466 other => panic!("expected Overlap, got {other:?}"),
467 }
468 }
469
470 #[test]
471 fn segment_intersection_disjoint_collinear() {
472 let a1 = Point2::new(0.0, 0.0);
473 let a2 = Point2::new(1.0, 0.0);
474 let b1 = Point2::new(2.0, 0.0);
475 let b2 = Point2::new(3.0, 0.0);
476
477 assert_eq!(
478 segment_intersection(a1, a2, b1, b2),
479 SegmentIntersection::None
480 );
481 }
482
483 #[test]
484 fn segment_intersection_no_intersection() {
485 let a1 = Point2::new(0.0, 0.0);
486 let a2 = Point2::new(1.0, 0.0);
487 let b1 = Point2::new(2.0, 2.0);
488 let b2 = Point2::new(3.0, 3.0);
489
490 assert_eq!(
491 segment_intersection(a1, a2, b1, b2),
492 SegmentIntersection::None
493 );
494 }
495}