1use super::expansion::{
42 compress_expansion, expansion_sum, negate_expansion, scale_expansion, sign_of_expansion,
43 two_product, Sign, MAX_EXPANSION_INCIRCLE,
44};
45use super::primitives::Point2;
46
47const FILTERED_BOUND: f64 = 32.0 * f64::EPSILON;
56
57const COMPENSATED_BOUND: f64 = 8.0 * f64::EPSILON;
60
61#[derive(Clone, Copy)]
68struct IncircleDiffs {
69 adx: f64,
70 ady: f64,
71 bdx: f64,
72 bdy: f64,
73 cdx: f64,
74 cdy: f64,
75 ad2: f64,
76 bd2: f64,
77 cd2: f64,
78}
79
80impl IncircleDiffs {
81 #[inline]
82 fn from_points(a: Point2, b: Point2, c: Point2, d: Point2) -> Self {
83 let adx = a.x - d.x;
84 let ady = a.y - d.y;
85 let bdx = b.x - d.x;
86 let bdy = b.y - d.y;
87 let cdx = c.x - d.x;
88 let cdy = c.y - d.y;
89 IncircleDiffs {
90 adx,
91 ady,
92 bdx,
93 bdy,
94 cdx,
95 cdy,
96 ad2: adx * adx + ady * ady,
97 bd2: bdx * bdx + bdy * bdy,
98 cd2: cdx * cdx + cdy * cdy,
99 }
100 }
101
102 #[inline]
104 fn permanent(&self) -> f64 {
105 let IncircleDiffs {
106 adx,
107 ady,
108 bdx,
109 bdy,
110 cdx,
111 cdy,
112 ad2,
113 bd2,
114 cd2,
115 } = *self;
116 (adx.abs() * (bdy.abs() * cd2.abs() + cdy.abs() * bd2.abs()))
117 + (ady.abs() * (bdx.abs() * cd2.abs() + cdx.abs() * bd2.abs()))
118 + (ad2.abs() * (bdx.abs() * cdy.abs() + bdy.abs() * cdx.abs()))
119 }
120}
121
122#[inline]
127fn filtered_det(d: &IncircleDiffs) -> f64 {
128 let IncircleDiffs {
129 adx,
130 ady,
131 bdx,
132 bdy,
133 cdx,
134 cdy,
135 ad2,
136 bd2,
137 cd2,
138 } = *d;
139 adx * (bdy * cd2 - cdy * bd2) - ady * (bdx * cd2 - cdx * bd2) + ad2 * (bdx * cdy - bdy * cdx)
140}
141
142#[inline]
147fn compensated_det(d: &IncircleDiffs) -> f64 {
148 let IncircleDiffs {
149 adx,
150 ady,
151 bdx,
152 bdy,
153 cdx,
154 cdy,
155 ad2,
156 bd2,
157 cd2,
158 } = *d;
159
160 let (adx2_p, adx2_e) = two_product(adx, adx);
164 let (ady2_p, ady2_e) = two_product(ady, ady);
165 let ad2_err = (adx2_p + ady2_p - ad2) + adx2_e + ady2_e;
166
167 let (bdx2_p, bdx2_e) = two_product(bdx, bdx);
168 let (bdy2_p, bdy2_e) = two_product(bdy, bdy);
169 let bd2_err = (bdx2_p + bdy2_p - bd2) + bdx2_e + bdy2_e;
170
171 let (cdx2_p, cdx2_e) = two_product(cdx, cdx);
172 let (cdy2_p, cdy2_e) = two_product(cdy, cdy);
173 let cd2_err = (cdx2_p + cdy2_p - cd2) + cdx2_e + cdy2_e;
174
175 let (p_bdy_cd2, e_bdy_cd2) = two_product(bdy, cd2);
177 let (p_cdy_bd2, e_cdy_bd2) = two_product(cdy, bd2);
178 let (p_bdx_cd2, e_bdx_cd2) = two_product(bdx, cd2);
179 let (p_cdx_bd2, e_cdx_bd2) = two_product(cdx, bd2);
180 let (p_bdx_cdy, e_bdx_cdy) = two_product(bdx, cdy);
181 let (p_bdy_cdx, e_bdy_cdx) = two_product(bdy, cdx);
182
183 let m1 = p_bdy_cd2 - p_cdy_bd2;
185 let m1_err = e_bdy_cd2 - e_cdy_bd2 + bdy * cd2_err - cdy * bd2_err;
186 let m2 = p_bdx_cd2 - p_cdx_bd2;
187 let m2_err = e_bdx_cd2 - e_cdx_bd2 + bdx * cd2_err - cdx * bd2_err;
188 let m3 = p_bdx_cdy - p_bdy_cdx;
189 let m3_err = e_bdx_cdy - e_bdy_cdx;
190
191 let o1 = adx * m1;
193 let o1_err = adx.mul_add(m1, -o1) + adx * m1_err;
194 let o2 = ady * m2;
195 let o2_err = ady.mul_add(m2, -o2) + ady * m2_err;
196 let o3 = ad2 * m3;
197 let o3_err = ad2.mul_add(m3, -o3) + ad2_err * m3 + ad2 * m3_err;
198
199 (o1 - o2 + o3) + (o1_err - o2_err + o3_err)
200}
201
202fn exact_det(d: &IncircleDiffs) -> Sign {
213 let IncircleDiffs {
214 adx,
215 ady,
216 bdx,
217 bdy,
218 cdx,
219 cdy,
220 ..
221 } = *d;
222
223 let mut ad2_exp = [0.0f64; 4];
225 let mut bd2_exp = [0.0f64; 4];
226 let mut cd2_exp = [0.0f64; 4];
227 let ad2_len = sq_dist_expansion(adx, ady, &mut ad2_exp);
228 let bd2_len = sq_dist_expansion(bdx, bdy, &mut bd2_exp);
229 let cd2_len = sq_dist_expansion(cdx, cdy, &mut cd2_exp);
230
231 let terms: [(f64, f64, &[f64], bool); 6] = [
234 (adx, bdy, &cd2_exp[..cd2_len], false),
235 (adx, cdy, &bd2_exp[..bd2_len], true),
236 (ady, bdx, &cd2_exp[..cd2_len], true),
237 (ady, cdx, &bd2_exp[..bd2_len], false),
238 (1.0, bdx, &cd2_exp[..cd2_len], false), (1.0, bdy, &cd2_exp[..cd2_len], true), ];
241
242 let mut scaled = [0.0f64; 16]; let mut term = [0.0f64; 32]; let mut accum = [0.0f64; MAX_EXPANSION_INCIRCLE];
250 let mut temp = [0.0f64; MAX_EXPANSION_INCIRCLE];
251 let mut accum_len = 0usize;
252
253 for &(s1, s2, exp, negate) in &terms[..4] {
255 let term_len = compute_term(exp, s1, s2, &mut scaled, &mut term);
256 add_term(
257 &mut accum,
258 &mut accum_len,
259 &term[..term_len],
260 negate,
261 &mut temp,
262 );
263 }
264
265 let t5_len = compute_term(&ad2_exp[..ad2_len], bdx, cdy, &mut scaled, &mut term);
268 add_term(
269 &mut accum,
270 &mut accum_len,
271 &term[..t5_len],
272 false,
273 &mut temp,
274 );
275 let t6_len = compute_term(&ad2_exp[..ad2_len], bdy, cdx, &mut scaled, &mut term);
277 add_term(&mut accum, &mut accum_len, &term[..t6_len], true, &mut temp);
278
279 let mut compressed = [0.0f64; MAX_EXPANSION_INCIRCLE];
281 let comp_len = compress_expansion(&accum[..accum_len], &mut compressed)
282 .expect("compressed buffer sized for MAX_EXPANSION_INCIRCLE");
283 sign_of_expansion(&compressed[..comp_len])
284}
285
286fn sq_dist_expansion(dx: f64, dy: f64, out: &mut [f64; 4]) -> usize {
288 let (px, ex) = two_product(dx, dx);
289 let (py, ey) = two_product(dy, dy);
290 let prod = [px, ex];
292 let other = [py, ey];
293 let len = expansion_sum(&prod, &other, out).expect("out is sized for 4");
294 let mut comp = [0.0f64; 4];
296 let comp_len = compress_expansion(&out[..len], &mut comp).expect("comp is sized for 4");
297 out[..comp_len].copy_from_slice(&comp[..comp_len]);
298 comp_len
299}
300
301fn compute_term(
307 exp: &[f64],
308 s1: f64,
309 s2: f64,
310 scaled: &mut [f64; 16],
311 term: &mut [f64; 32],
312) -> usize {
313 let len1 = scale_expansion(exp, s1, scaled).expect("scaled sized for 2*exp");
315 let mut comp = [0.0f64; 16];
317 let comp_len = compress_expansion(&scaled[..len1], &mut comp).expect("comp sized for 16");
318 let len2 = scale_expansion(&comp[..comp_len], s2, term).expect("term sized for 32");
320 let mut comp2 = [0.0f64; 32];
322 let comp2_len = compress_expansion(&term[..len2], &mut comp2).expect("comp2 sized for 32");
323 term[..comp2_len].copy_from_slice(&comp2[..comp2_len]);
324 comp2_len
325}
326
327fn add_term(
329 accum: &mut [f64],
330 accum_len: &mut usize,
331 term: &[f64],
332 negate: bool,
333 temp: &mut [f64],
334) {
335 let mut neg_term = [0.0f64; 32];
336 if negate {
337 neg_term[..term.len()].copy_from_slice(term);
338 negate_expansion(&mut neg_term[..term.len()]);
339 if *accum_len == 0 {
340 accum[..term.len()].copy_from_slice(&neg_term[..term.len()]);
341 *accum_len = term.len();
342 return;
343 }
344 let sum_len = expansion_sum(&accum[..*accum_len], &neg_term[..term.len()], temp)
345 .expect("temp sized for MAX_EXPANSION_INCIRCLE");
346 *accum_len = compress_expansion(&temp[..sum_len], accum)
347 .expect("accum sized for MAX_EXPANSION_INCIRCLE");
348 } else {
349 if *accum_len == 0 {
350 accum[..term.len()].copy_from_slice(term);
351 *accum_len = term.len();
352 return;
353 }
354 let sum_len = expansion_sum(&accum[..*accum_len], term, temp)
355 .expect("temp sized for MAX_EXPANSION_INCIRCLE");
356 *accum_len = compress_expansion(&temp[..sum_len], accum)
357 .expect("accum sized for MAX_EXPANSION_INCIRCLE");
358 }
359}
360
361pub fn incircle(a: Point2, b: Point2, c: Point2, d: Point2) -> Sign {
375 let diffs = IncircleDiffs::from_points(a, b, c, d);
376 let perm = diffs.permanent();
377
378 let det = filtered_det(&diffs);
379 if det.abs() > perm * FILTERED_BOUND {
380 return Sign::from_f64(det);
381 }
382
383 let comp = compensated_det(&diffs);
384 if comp.abs() > perm * COMPENSATED_BOUND {
385 return Sign::from_f64(comp);
386 }
387
388 exact_det(&diffs)
389}
390
391#[cfg(test)]
396mod tests {
397 use super::*;
398 use crate::specialized_libs::computational_geometry::exact_test_helper::Exact;
399 use crate::specialized_libs::computational_geometry::primitives::orientation_2;
400
401 fn exact_incircle_sign(a: Point2, b: Point2, c: Point2, d: Point2) -> Sign {
408 let adx_f = a.x - d.x;
410 let ady_f = a.y - d.y;
411 let bdx_f = b.x - d.x;
412 let bdy_f = b.y - d.y;
413 let cdx_f = c.x - d.x;
414 let cdy_f = c.y - d.y;
415
416 let adx = Exact::from_f64(adx_f);
418 let ady = Exact::from_f64(ady_f);
419 let bdx = Exact::from_f64(bdx_f);
420 let bdy = Exact::from_f64(bdy_f);
421 let cdx = Exact::from_f64(cdx_f);
422 let cdy = Exact::from_f64(cdy_f);
423
424 let ad2 = adx
425 .clone()
426 .mul(adx.clone())
427 .add(ady.clone().mul(ady.clone()));
428 let bd2 = bdx
429 .clone()
430 .mul(bdx.clone())
431 .add(bdy.clone().mul(bdy.clone()));
432 let cd2 = cdx
433 .clone()
434 .mul(cdx.clone())
435 .add(cdy.clone().mul(cdy.clone()));
436
437 let t1 = adx.clone().mul(bdy.clone()).mul(cd2.clone());
439 let t2 = adx.clone().mul(cdy.clone()).mul(bd2.clone());
440 let t3 = ady.clone().mul(bdx.clone()).mul(cd2.clone());
441 let t4 = ady.clone().mul(cdx.clone()).mul(bd2.clone());
442 let t5 = ad2.clone().mul(bdx.clone()).mul(cdy.clone());
443 let t6 = ad2.mul(bdy).mul(cdx);
444
445 let det = t1.sub(t2).sub(t3).add(t4).add(t5).sub(t6);
446 det.sign()
447 }
448
449 #[test]
452 fn classifies_inside_circle() {
453 let a = Point2::new(1.0, 0.0);
455 let b = Point2::new(0.0, 1.0);
456 let c = Point2::new(-1.0, 0.0);
457 let d = Point2::new(0.0, 0.0);
458 assert_eq!(orientation_2(a, b, c), crate::specialized_libs::computational_geometry::primitives::Orientation::CounterClockwise);
459 assert_eq!(incircle(a, b, c, d), Sign::Positive); }
461
462 #[test]
463 fn classifies_outside_circle() {
464 let a = Point2::new(1.0, 0.0);
465 let b = Point2::new(0.0, 1.0);
466 let c = Point2::new(-1.0, 0.0);
467 let d = Point2::new(2.0, 0.0); assert_eq!(incircle(a, b, c, d), Sign::Negative);
469 }
470
471 #[test]
472 fn classifies_on_circle() {
473 let a = Point2::new(1.0, 0.0);
474 let b = Point2::new(0.0, 1.0);
475 let c = Point2::new(-1.0, 0.0);
476 let d = Point2::new(0.0, -1.0); assert_eq!(incircle(a, b, c, d), Sign::Zero);
478 }
479
480 #[test]
481 fn sign_flips_for_clockwise_abc() {
482 let a = Point2::new(1.0, 0.0);
484 let b = Point2::new(-1.0, 0.0);
485 let c = Point2::new(0.0, 1.0);
486 let d = Point2::new(0.0, 0.0); assert_eq!(
488 orientation_2(a, b, c),
489 crate::specialized_libs::computational_geometry::primitives::Orientation::Clockwise
490 );
491 assert_eq!(incircle(a, b, c, d), Sign::Negative);
493 }
494
495 #[test]
498 fn agrees_with_exact_on_basic_cases() {
499 let cases = [
500 (
501 Point2::new(1.0, 0.0),
502 Point2::new(0.0, 1.0),
503 Point2::new(-1.0, 0.0),
504 Point2::new(0.0, 0.0),
505 ),
506 (
507 Point2::new(1.0, 0.0),
508 Point2::new(0.0, 1.0),
509 Point2::new(-1.0, 0.0),
510 Point2::new(2.0, 0.0),
511 ),
512 (
513 Point2::new(1.0, 0.0),
514 Point2::new(0.0, 1.0),
515 Point2::new(-1.0, 0.0),
516 Point2::new(0.0, -1.0),
517 ),
518 (
519 Point2::new(0.0, 0.0),
520 Point2::new(1.0, 0.0),
521 Point2::new(0.0, 1.0),
522 Point2::new(0.5, 0.5),
523 ),
524 (
525 Point2::new(3.0, 4.0),
526 Point2::new(0.0, 0.0),
527 Point2::new(6.0, 0.0),
528 Point2::new(3.0, 0.0),
529 ),
530 ];
531 for (a, b, c, d) in cases {
532 assert_eq!(
533 incircle(a, b, c, d),
534 exact_incircle_sign(a, b, c, d),
535 "mismatch on ({a:?}, {b:?}, {c:?}, {d:?})"
536 );
537 }
538 }
539
540 #[test]
543 fn cocircular_four_points() {
544 let a = Point2::new(1.0, 0.0);
546 let b = Point2::new(0.0, 1.0);
547 let c = Point2::new(-1.0, 0.0);
548 let d = Point2::new(0.0, -1.0);
549 assert_eq!(incircle(a, b, c, d), Sign::Zero);
550 assert_eq!(exact_incircle_sign(a, b, c, d), Sign::Zero);
551 }
552
553 #[test]
554 fn cocircular_on_arbitrary_circle() {
555 let a = Point2::new(8.0, 4.0);
557 let b = Point2::new(3.0, 9.0);
558 let c = Point2::new(-2.0, 4.0);
559 let d = Point2::new(3.0, -1.0);
560 assert_eq!(incircle(a, b, c, d), Sign::Zero);
561 assert_eq!(exact_incircle_sign(a, b, c, d), Sign::Zero);
562 }
563
564 #[test]
567 fn near_cocircular_1ulp_off() {
568 let a = Point2::new(1.0, 0.0);
570 let b = Point2::new(0.0, 1.0);
571 let c = Point2::new(-1.0, 0.0);
572 let d0 = Point2::new(0.0, -1.0); for &delta_bits in &[1i64, -1, 2, -2, 5, -5, 100, -100] {
575 let dy = f64::from_bits((d0.y.to_bits() as i64 + delta_bits) as u64);
576 let d = Point2::new(d0.x, dy);
577 assert_eq!(
578 incircle(a, b, c, d),
579 exact_incircle_sign(a, b, c, d),
580 "mismatch on near-cocircular delta_bits={delta_bits}"
581 );
582 }
583 }
584
585 #[test]
588 fn extreme_exponents_agree_with_exact() {
589 let cases = [
593 (
594 Point2::new(1e50, 0.0),
595 Point2::new(0.0, 1e50),
596 Point2::new(-1e50, 0.0),
597 Point2::new(0.0, 0.0),
598 ),
599 (
600 Point2::new(1e-50, 0.0),
601 Point2::new(0.0, 1e-50),
602 Point2::new(-1e-50, 0.0),
603 Point2::new(0.0, 0.0),
604 ),
605 (
606 Point2::new(1e50, 0.0),
607 Point2::new(0.0, 1e50),
608 Point2::new(-1e50, 0.0),
609 Point2::new(1e50, 1e-50),
610 ),
611 ];
612 for (a, b, c, d) in cases {
613 assert_eq!(
614 incircle(a, b, c, d),
615 exact_incircle_sign(a, b, c, d),
616 "mismatch on extreme-exponent case ({a:?}, {b:?}, {c:?}, {d:?})"
617 );
618 }
619 }
620
621 #[test]
624 fn cancellation_agrees_with_exact() {
625 let a = Point2::new(1e50, 0.0);
628 let b = Point2::new(0.0, 1e50);
629 let c = Point2::new(-1e50, 0.0);
630 let d = Point2::new(0.0, -1e50); assert_eq!(incircle(a, b, c, d), exact_incircle_sign(a, b, c, d));
632 }
633
634 #[test]
635 fn massive_cancellation_agrees_with_exact() {
636 let a = Point2::new(1e50, 0.0);
638 let b = Point2::new(0.0, 1e50);
639 let c = Point2::new(-1e50, 0.0);
640 let d = Point2::new(0.0, -1e50 + 1.0);
642 assert_eq!(
643 incircle(a, b, c, d),
644 exact_incircle_sign(a, b, c, d),
645 "mismatch on massive cancellation"
646 );
647 }
648
649 #[test]
652 fn filtered_stage_resolves_clear_case() {
653 let a = Point2::new(1.0, 0.0);
654 let b = Point2::new(0.0, 1.0);
655 let c = Point2::new(-1.0, 0.0);
656 let d = Point2::new(0.0, 0.0);
657 let diffs = IncircleDiffs::from_points(a, b, c, d);
658 let det = filtered_det(&diffs);
659 let perm = diffs.permanent();
660 assert!(
661 det.abs() > perm * FILTERED_BOUND,
662 "filtered should resolve (det={det}, bound={})",
663 perm * FILTERED_BOUND
664 );
665 assert_eq!(incircle(a, b, c, d), Sign::Positive);
666 }
667
668 #[test]
669 fn near_cocircular_resolves_via_compensated_or_exact() {
670 let a = Point2::new(1.0, 0.0);
671 let b = Point2::new(0.0, 1.0);
672 let c = Point2::new(-1.0, 0.0);
673 let d = Point2::new(0.0, -1.0 + 1e-15);
674 assert_eq!(
675 incircle(a, b, c, d),
676 exact_incircle_sign(a, b, c, d),
677 "near-cocircular must match exact"
678 );
679 }
680
681 #[test]
684 fn deterministic_across_calls() {
685 let a = Point2::new(1.0, 0.0);
686 let b = Point2::new(0.0, 1.0);
687 let c = Point2::new(-1.0, 0.0);
688 let d = Point2::new(0.3, 0.3);
689 let s1 = incircle(a, b, c, d);
690 let s2 = incircle(a, b, c, d);
691 assert_eq!(s1, s2);
692 }
693
694 #[test]
697 fn swapping_a_b_flips_sign() {
698 let a = Point2::new(1.0, 0.0);
699 let b = Point2::new(0.0, 1.0);
700 let c = Point2::new(-1.0, 0.0);
701 let d = Point2::new(0.0, 0.0);
702 let s = incircle(a, b, c, d);
703 let s_swapped = incircle(b, a, c, d);
704 assert_eq!(s, s_swapped.flip());
705 }
706
707 #[test]
708 fn translation_invariant() {
709 let a = Point2::new(1.0, 0.0);
710 let b = Point2::new(0.0, 1.0);
711 let c = Point2::new(-1.0, 0.0);
712 let d = Point2::new(0.0, 0.0);
713 let t = Point2::new(1e10, -1e10);
714 let s = incircle(a, b, c, d);
715 let s_t = incircle(
716 Point2::new(a.x + t.x, a.y + t.y),
717 Point2::new(b.x + t.x, b.y + t.y),
718 Point2::new(c.x + t.x, c.y + t.y),
719 Point2::new(d.x + t.x, d.y + t.y),
720 );
721 assert_eq!(s, s_t);
722 }
723
724 #[test]
727 fn no_heap_allocation_in_predicate() {
728 let a = Point2::new(1.0, 0.0);
729 let b = Point2::new(0.0, 1.0);
730 let c = Point2::new(-1.0, 0.0);
731 let d = Point2::new(0.0, -1.0);
732 let _ = incircle(a, b, c, d); }
734}