Files
OneCAD-Simulation/crates/mesh/src/predicates/adaptive.rs
T
4667655e08 mesh: exact predicates (Shewchuk adaptive, symbolic perturbation, indirect LPI/TPI) (#8)
* mesh: exact predicates (Shewchuk adaptive orient/incircle/insphere), symbolic perturbation, indirect LPI/TPI predicates

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: predicate benchmark (filter hit rate, CAD-degenerate inputs); record the predicate decision

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

* mesh: simplify predicates (destructuring over fallbacks, shared-minor exact insphere, cheaper expansion product)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NmAttHhUZTokgPeEJia8sm

---------

Co-authored-by: jyviko <2501648+jyviko@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-13 15:04:56 -04:00

450 lines
19 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Shewchuk's adaptive-precision `orient2d`, `orient3d`, `incircle` and `insphere`
//! (Shewchuk 1997, §4 and §5), implemented from the paper.
//!
//! Each predicate evaluates its determinant in plain doubles and accepts the sign when the
//! magnitude exceeds the paper's certified error bound (stage A, the filter). Otherwise it
//! escalates: stage B computes the determinant of the rounded coordinate differences exactly as
//! an expansion and accepts it under a tighter bound, or outright when the differences were exact
//! (zero tails, the common case for grid-like CAD input); stage C (`orient2d`, `orient3d`) adds
//! the first-order tail correction; the last stage evaluates the determinant exactly from the
//! input coordinates with expansion arithmetic. Shewchuk's further hand-scheduled stages between
//! C and exact are not reproduced: they only make rare inputs faster.
//!
//! The error bounds assume IEEE binary64 round-to-nearest-even arithmetic without overflow or
//! underflow (§5); the coordinate domain of [`Point2`] / [`Point3`] rules both out.
use super::eft::two_diff_tail;
use super::expansion::{Exp, Expansion, cross_diff};
use super::ring::{det2, det3, sq2, sq3};
use super::{Point2, Point3, Sign};
/// `ε = 2^-53`, half a unit in the last place of 1 (the machine epsilon of §2.1).
const EPS: f64 = f64::EPSILON / 2.0;
const RESULT_ERR: f64 = (3.0 + 8.0 * EPS) * EPS;
const CCW_A: f64 = (3.0 + 16.0 * EPS) * EPS;
const CCW_B: f64 = (2.0 + 12.0 * EPS) * EPS;
const CCW_C: f64 = (9.0 + 64.0 * EPS) * EPS * EPS;
const O3D_A: f64 = (7.0 + 56.0 * EPS) * EPS;
const O3D_B: f64 = (3.0 + 28.0 * EPS) * EPS;
const O3D_C: f64 = (26.0 + 288.0 * EPS) * EPS * EPS;
const ICC_A: f64 = (10.0 + 96.0 * EPS) * EPS;
const ICC_B: f64 = (4.0 + 48.0 * EPS) * EPS;
const ISP_A: f64 = (16.0 + 224.0 * EPS) * EPS;
const ISP_B: f64 = (5.0 + 72.0 * EPS) * EPS;
#[inline(always)]
fn certain(det: f64, errbound: f64) -> bool {
det > errbound || -det > errbound
}
// ---------------------------------------------------------------------------------------------
// orient2d
// ---------------------------------------------------------------------------------------------
/// Stage A of [`orient2d`]: the sign when the double-precision filter certifies it.
#[inline]
pub fn orient2d_filter(a: &Point2, b: &Point2, c: &Point2) -> Option<Sign> {
orient2d_a(a, b, c).ok()
}
/// `Ok(sign)` when stage A decides, else `Err(detsum)` for the adaptive stages.
#[inline(always)]
fn orient2d_a(a: &Point2, b: &Point2, c: &Point2) -> Result<Sign, f64> {
let ([ax, ay], [bx, by], [cx, cy]) = (a.0, b.0, c.0);
let det_left = (ax - cx) * (by - cy);
let det_right = (ay - cy) * (bx - cx);
let det = det_left - det_right;
// When the two products have opposite signs (or one is zero) the subtraction cannot cancel
// and the rounded result has the exact sign.
let det_sum = if det_left > 0.0 {
if det_right <= 0.0 {
return Ok(Sign::of(det));
}
det_left + det_right
} else if det_left < 0.0 {
if det_right >= 0.0 {
return Ok(Sign::of(det));
}
-det_left - det_right
} else {
return Ok(Sign::of(det));
};
if certain(det, CCW_A * det_sum) { Ok(Sign::of(det)) } else { Err(det_sum) }
}
/// The orientation of `a, b, c`: [`Sign::Positive`] when they turn counterclockwise,
/// [`Sign::Negative`] when clockwise, [`Sign::Zero`] when collinear. Exact. It is the sign of
/// `det[a - c; b - c]`.
#[inline]
pub fn orient2d(a: &Point2, b: &Point2, c: &Point2) -> Sign {
match orient2d_a(a, b, c) {
Ok(sign) => sign,
Err(det_sum) => orient2d_adapt(a, b, c, det_sum),
}
}
#[cold]
fn orient2d_adapt(a: &Point2, b: &Point2, c: &Point2, det_sum: f64) -> Sign {
let ([ax, ay], [bx, by], [cx, cy]) = (a.0, b.0, c.0);
let (acx, bcx, acy, bcy) = (ax - cx, bx - cx, ay - cy, by - cy);
// Stage B: the exact determinant of the rounded differences.
let b_exp = cross_diff(acx, bcy, acy, bcx);
let det = b_exp.estimate();
if certain(det, CCW_B * det_sum) {
return Sign::of(det);
}
let acx_t = two_diff_tail(ax, cx, acx);
let bcx_t = two_diff_tail(bx, cx, bcx);
let acy_t = two_diff_tail(ay, cy, acy);
let bcy_t = two_diff_tail(by, cy, bcy);
if acx_t == 0.0 && acy_t == 0.0 && bcx_t == 0.0 && bcy_t == 0.0 {
return b_exp.sign();
}
// Stage C: first-order correction for the tails.
let errbound = CCW_C * det_sum + RESULT_ERR * det.abs();
let det = det + ((acx * bcy_t + bcy * acx_t) - (acy * bcx_t + bcx * acy_t));
if certain(det, errbound) {
return Sign::of(det);
}
// Stage D: the remaining terms of (acx + acx_t)(bcy + bcy_t) - (acy + acy_t)(bcx + bcx_t).
let c1 = Exp::<8>::sum(&b_exp, &cross_diff(acx_t, bcy, acy_t, bcx));
let c2 = Exp::<12>::sum(&c1, &cross_diff(acx, bcy_t, acy, bcx_t));
Exp::<16>::sum(&c2, &cross_diff(acx_t, bcy_t, acy_t, bcx_t)).sign()
}
// ---------------------------------------------------------------------------------------------
// orient3d
// ---------------------------------------------------------------------------------------------
/// Stage A of [`orient3d`].
#[inline]
pub fn orient3d_filter(a: &Point3, b: &Point3, c: &Point3, d: &Point3) -> Option<Sign> {
orient3d_a(a, b, c, d).ok()
}
#[inline(always)]
fn orient3d_a(a: &Point3, b: &Point3, c: &Point3, d: &Point3) -> Result<Sign, f64> {
let ([ax, ay, az], [bx, by, bz], [cx, cy, cz], [dx, dy, dz]) = (a.0, b.0, c.0, d.0);
let (adx, bdx, cdx) = (ax - dx, bx - dx, cx - dx);
let (ady, bdy, cdy) = (ay - dy, by - dy, cy - dy);
let (adz, bdz, cdz) = (az - dz, bz - dz, cz - dz);
let (bdxcdy, cdxbdy) = (bdx * cdy, cdx * bdy);
let (cdxady, adxcdy) = (cdx * ady, adx * cdy);
let (adxbdy, bdxady) = (adx * bdy, bdx * ady);
let det = adz * (bdxcdy - cdxbdy) + bdz * (cdxady - adxcdy) + cdz * (adxbdy - bdxady);
let permanent = (bdxcdy.abs() + cdxbdy.abs()) * adz.abs() + (cdxady.abs() + adxcdy.abs()) * bdz.abs() + (adxbdy.abs() + bdxady.abs()) * cdz.abs();
if certain(det, O3D_A * permanent) { Ok(Sign::of(det)) } else { Err(permanent) }
}
/// The orientation of `d` relative to the plane through `a, b, c`: [`Sign::Positive`] when `d`
/// lies below the plane, "above" being the side from which `a, b, c` appear counterclockwise;
/// [`Sign::Zero`] when the four points are coplanar. Exact. It is the sign of
/// `det[a - d; b - d; c - d] = -det[b - a; c - a; d - a]` (Shewchuk's convention), so it is
/// positive when the tetrahedron `a, b, c, d` is negatively oriented in the right-handed sense.
#[inline]
pub fn orient3d(a: &Point3, b: &Point3, c: &Point3, d: &Point3) -> Sign {
match orient3d_a(a, b, c, d) {
Ok(sign) => sign,
Err(permanent) => orient3d_adapt(a, b, c, d, permanent),
}
}
#[cold]
fn orient3d_adapt(a: &Point3, b: &Point3, c: &Point3, d: &Point3, permanent: f64) -> Sign {
let ([ax, ay, az], [bx, by, bz], [cx, cy, cz], [dx, dy, dz]) = (a.0, b.0, c.0, d.0);
let (adx, bdx, cdx) = (ax - dx, bx - dx, cx - dx);
let (ady, bdy, cdy) = (ay - dy, by - dy, cy - dy);
let (adz, bdz, cdz) = (az - dz, bz - dz, cz - dz);
// Stage B.
let bc = cross_diff(bdx, cdy, cdx, bdy);
let ca = cross_diff(cdx, ady, adx, cdy);
let ab = cross_diff(adx, bdy, bdx, ady);
let a_det = Exp::<8>::scale(&bc, adz);
let b_det = Exp::<8>::scale(&ca, bdz);
let c_det = Exp::<8>::scale(&ab, cdz);
let fin = Exp::<24>::sum(&Exp::<16>::sum(&a_det, &b_det), &c_det);
let det = fin.estimate();
if certain(det, O3D_B * permanent) {
return Sign::of(det);
}
let (adx_t, bdx_t, cdx_t) = (two_diff_tail(ax, dx, adx), two_diff_tail(bx, dx, bdx), two_diff_tail(cx, dx, cdx));
let (ady_t, bdy_t, cdy_t) = (two_diff_tail(ay, dy, ady), two_diff_tail(by, dy, bdy), two_diff_tail(cy, dy, cdy));
let (adz_t, bdz_t, cdz_t) = (two_diff_tail(az, dz, adz), two_diff_tail(bz, dz, bdz), two_diff_tail(cz, dz, cdz));
let tails = [adx_t, bdx_t, cdx_t, ady_t, bdy_t, cdy_t, adz_t, bdz_t, cdz_t];
if tails.iter().all(|&t| t == 0.0) {
return fin.sign();
}
// Stage C.
let errbound = O3D_C * permanent + RESULT_ERR * det.abs();
let det = det
+ (adz * ((bdx * cdy_t + cdy * bdx_t) - (bdy * cdx_t + cdx * bdy_t)) + adz_t * (bdx * cdy - bdy * cdx))
+ (bdz * ((cdx * ady_t + ady * cdx_t) - (cdy * adx_t + adx * cdy_t)) + bdz_t * (cdx * ady - cdy * adx))
+ (cdz * ((adx * bdy_t + bdy * adx_t) - (ady * bdx_t + bdx * ady_t)) + cdz_t * (adx * bdy - ady * bdx));
if certain(det, errbound) {
return Sign::of(det);
}
orient3d_exact(a, b, c, d)
}
/// `det[a - d; b - d; c - d]` exactly, from the input coordinates.
fn orient3d_exact(a: &Point3, b: &Point3, c: &Point3, d: &Point3) -> Sign {
det3(&[diffs3(a, d), diffs3(b, d), diffs3(c, d)]).sign()
}
/// The exact coordinate differences `p - o` as expansions.
fn diffs3(p: &Point3, o: &Point3) -> [Expansion; 3] {
let ([px, py, pz], [ox, oy, oz]) = (p.0, o.0);
[Expansion::diff(px, ox), Expansion::diff(py, oy), Expansion::diff(pz, oz)]
}
fn diffs2(p: &Point2, o: &Point2) -> [Expansion; 2] {
let ([px, py], [ox, oy]) = (p.0, o.0);
[Expansion::diff(px, ox), Expansion::diff(py, oy)]
}
// ---------------------------------------------------------------------------------------------
// incircle
// ---------------------------------------------------------------------------------------------
/// Stage A of [`incircle`].
#[inline]
pub fn incircle_filter(a: &Point2, b: &Point2, c: &Point2, d: &Point2) -> Option<Sign> {
incircle_a(a, b, c, d).ok()
}
#[inline(always)]
fn incircle_a(a: &Point2, b: &Point2, c: &Point2, d: &Point2) -> Result<Sign, f64> {
let ([ax, ay], [bx, by], [cx, cy], [dx, dy]) = (a.0, b.0, c.0, d.0);
let (adx, bdx, cdx) = (ax - dx, bx - dx, cx - dx);
let (ady, bdy, cdy) = (ay - dy, by - dy, cy - dy);
let (bdxcdy, cdxbdy) = (bdx * cdy, cdx * bdy);
let a_lift = adx * adx + ady * ady;
let (cdxady, adxcdy) = (cdx * ady, adx * cdy);
let b_lift = bdx * bdx + bdy * bdy;
let (adxbdy, bdxady) = (adx * bdy, bdx * ady);
let c_lift = cdx * cdx + cdy * cdy;
let det = a_lift * (bdxcdy - cdxbdy) + b_lift * (cdxady - adxcdy) + c_lift * (adxbdy - bdxady);
let permanent = (bdxcdy.abs() + cdxbdy.abs()) * a_lift + (cdxady.abs() + adxcdy.abs()) * b_lift + (adxbdy.abs() + bdxady.abs()) * c_lift;
if certain(det, ICC_A * permanent) { Ok(Sign::of(det)) } else { Err(permanent) }
}
/// Whether `d` lies inside the circle through `a, b, c`: [`Sign::Positive`] inside,
/// [`Sign::Negative`] outside, [`Sign::Zero`] on it, for `a, b, c` counterclockwise (the sign is
/// reversed when they are clockwise). Exact. It is the sign of
/// `det[a - d, |a - d|²; b - d, |b - d|²; c - d, |c - d|²]`.
#[inline]
pub fn incircle(a: &Point2, b: &Point2, c: &Point2, d: &Point2) -> Sign {
match incircle_a(a, b, c, d) {
Ok(sign) => sign,
Err(permanent) => incircle_adapt(a, b, c, d, permanent),
}
}
#[cold]
fn incircle_adapt(a: &Point2, b: &Point2, c: &Point2, d: &Point2, permanent: f64) -> Sign {
let ([ax, ay], [bx, by], [cx, cy], [dx, dy]) = (a.0, b.0, c.0, d.0);
let (adx, bdx, cdx) = (ax - dx, bx - dx, cx - dx);
let (ady, bdy, cdy) = (ay - dy, by - dy, cy - dy);
// Stage B.
let lifted = |minor: &Exp<4>, x: f64, y: f64| -> Exp<32> {
Exp::sum(&Exp::<16>::scale(&Exp::<8>::scale(minor, x), x), &Exp::<16>::scale(&Exp::<8>::scale(minor, y), y))
};
let a_det = lifted(&cross_diff(bdx, cdy, cdx, bdy), adx, ady);
let b_det = lifted(&cross_diff(cdx, ady, adx, cdy), bdx, bdy);
let c_det = lifted(&cross_diff(adx, bdy, bdx, ady), cdx, cdy);
let fin = Exp::<96>::sum(&Exp::<64>::sum(&a_det, &b_det), &c_det);
let det = fin.estimate();
if certain(det, ICC_B * permanent) {
return Sign::of(det);
}
let tails = [
two_diff_tail(ax, dx, adx),
two_diff_tail(bx, dx, bdx),
two_diff_tail(cx, dx, cdx),
two_diff_tail(ay, dy, ady),
two_diff_tail(by, dy, bdy),
two_diff_tail(cy, dy, cdy),
];
if tails.iter().all(|&t| t == 0.0) {
return fin.sign();
}
incircle_exact(a, b, c, d)
}
fn incircle_exact(a: &Point2, b: &Point2, c: &Point2, d: &Point2) -> Sign {
let row = |p: &Point2| {
let [x, y] = diffs2(p, d);
let lift = sq2(&x, &y);
[x, y, lift]
};
det3(&[row(a), row(b), row(c)]).sign()
}
// ---------------------------------------------------------------------------------------------
// insphere
// ---------------------------------------------------------------------------------------------
/// Stage A of [`insphere`].
#[inline]
pub fn insphere_filter(a: &Point3, b: &Point3, c: &Point3, d: &Point3, e: &Point3) -> Option<Sign> {
insphere_a(a, b, c, d, e).ok()
}
#[inline(always)]
fn insphere_a(a: &Point3, b: &Point3, c: &Point3, d: &Point3, e: &Point3) -> Result<Sign, f64> {
let ([ax, ay, az], [bx, by, bz], [cx, cy, cz], [dx, dy, dz], [ex, ey, ez]) = (a.0, b.0, c.0, d.0, e.0);
let (aex, bex, cex, dex) = (ax - ex, bx - ex, cx - ex, dx - ex);
let (aey, bey, cey, dey) = (ay - ey, by - ey, cy - ey, dy - ey);
let (aez, bez, cez, dez) = (az - ez, bz - ez, cz - ez, dz - ez);
let (aexbey, bexaey) = (aex * bey, bex * aey);
let ab = aexbey - bexaey;
let (bexcey, cexbey) = (bex * cey, cex * bey);
let bc = bexcey - cexbey;
let (cexdey, dexcey) = (cex * dey, dex * cey);
let cd = cexdey - dexcey;
let (dexaey, aexdey) = (dex * aey, aex * dey);
let da = dexaey - aexdey;
let (aexcey, cexaey) = (aex * cey, cex * aey);
let ac = aexcey - cexaey;
let (bexdey, dexbey) = (bex * dey, dex * bey);
let bd = bexdey - dexbey;
let abc = aez * bc - bez * ac + cez * ab;
let bcd = bez * cd - cez * bd + dez * bc;
let cda = cez * da + dez * ac + aez * cd;
let dab = dez * ab + aez * bd + bez * da;
let a_lift = aex * aex + aey * aey + aez * aez;
let b_lift = bex * bex + bey * bey + bez * bez;
let c_lift = cex * cex + cey * cey + cez * cez;
let d_lift = dex * dex + dey * dey + dez * dez;
let det = (d_lift * abc - c_lift * dab) + (b_lift * cda - a_lift * bcd);
let (aez_p, bez_p, cez_p, dez_p) = (aez.abs(), bez.abs(), cez.abs(), dez.abs());
let (ab_p, bc_p, cd_p) = (aexbey.abs() + bexaey.abs(), bexcey.abs() + cexbey.abs(), cexdey.abs() + dexcey.abs());
let (da_p, ac_p, bd_p) = (dexaey.abs() + aexdey.abs(), aexcey.abs() + cexaey.abs(), bexdey.abs() + dexbey.abs());
let permanent = (cd_p * bez_p + bd_p * cez_p + bc_p * dez_p) * a_lift
+ (da_p * cez_p + ac_p * dez_p + cd_p * aez_p) * b_lift
+ (ab_p * dez_p + bd_p * aez_p + da_p * bez_p) * c_lift
+ (bc_p * aez_p + ac_p * bez_p + ab_p * cez_p) * d_lift;
if certain(det, ISP_A * permanent) { Ok(Sign::of(det)) } else { Err(permanent) }
}
/// Whether `e` lies inside the sphere through `a, b, c, d`: [`Sign::Positive`] inside,
/// [`Sign::Negative`] outside, [`Sign::Zero`] on it, for `orient3d(a, b, c, d) > 0` (the sign is
/// reversed otherwise). Exact. It is the sign of the 4×4 determinant with rows
/// `[p - e, |p - e|²]` for `p = a, b, c, d`.
#[inline]
pub fn insphere(a: &Point3, b: &Point3, c: &Point3, d: &Point3, e: &Point3) -> Sign {
match insphere_a(a, b, c, d, e) {
Ok(sign) => sign,
Err(permanent) => insphere_adapt(a, b, c, d, e, permanent),
}
}
#[cold]
fn insphere_adapt(a: &Point3, b: &Point3, c: &Point3, d: &Point3, e: &Point3, permanent: f64) -> Sign {
let ([ax, ay, az], [bx, by, bz], [cx, cy, cz], [dx, dy, dz], [ex, ey, ez]) = (a.0, b.0, c.0, d.0, e.0);
let (aex, bex, cex, dex) = (ax - ex, bx - ex, cx - ex, dx - ex);
let (aey, bey, cey, dey) = (ay - ey, by - ey, cy - ey, dy - ey);
let (aez, bez, cez, dez) = (az - ez, bz - ez, cz - ez, dz - ez);
// Stage B: the 2×2 minors of the x, y columns, the 3×3 minors, then the lifted terms.
let ab = cross_diff(aex, bey, bex, aey);
let bc = cross_diff(bex, cey, cex, bey);
let cd = cross_diff(cex, dey, dex, cey);
let da = cross_diff(dex, aey, aex, dey);
let ac = cross_diff(aex, cey, cex, aey);
let bd = cross_diff(bex, dey, dex, bey);
let minor3 = |m1: &Exp<4>, z1: f64, m2: &Exp<4>, z2: f64, m3: &Exp<4>, z3: f64| -> Exp<24> {
Exp::sum(&Exp::<16>::sum(&Exp::<8>::scale(m1, z1), &Exp::<8>::scale(m2, z2)), &Exp::<8>::scale(m3, z3))
};
let abc = minor3(&bc, aez, &ac, -bez, &ab, cez);
let bcd = minor3(&cd, bez, &bd, -cez, &bc, dez);
let cda = minor3(&da, cez, &ac, dez, &cd, aez);
let dab = minor3(&ab, dez, &bd, aez, &da, bez);
let lifted = |m: &Exp<24>, x: f64, y: f64, z: f64| -> Exp<288> {
let xx = Exp::<96>::scale(&Exp::<48>::scale(m, x), x);
let yy = Exp::<96>::scale(&Exp::<48>::scale(m, y), y);
let zz = Exp::<96>::scale(&Exp::<48>::scale(m, z), z);
Exp::sum(&Exp::<192>::sum(&xx, &yy), &zz)
};
let d_term = lifted(&abc, dex, dey, dez);
let c_term = lifted(&dab, cex, cey, cez).negated();
let b_term = lifted(&cda, bex, bey, bez);
let a_term = lifted(&bcd, aex, aey, aez).negated();
let fin = Exp::<1152>::sum(&Exp::<576>::sum(&d_term, &c_term), &Exp::<576>::sum(&b_term, &a_term));
let det = fin.estimate();
if certain(det, ISP_B * permanent) {
return Sign::of(det);
}
let tails = [
two_diff_tail(ax, ex, aex),
two_diff_tail(bx, ex, bex),
two_diff_tail(cx, ex, cex),
two_diff_tail(dx, ex, dex),
two_diff_tail(ay, ey, aey),
two_diff_tail(by, ey, bey),
two_diff_tail(cy, ey, cey),
two_diff_tail(dy, ey, dey),
two_diff_tail(az, ez, aez),
two_diff_tail(bz, ez, bez),
two_diff_tail(cz, ez, cez),
two_diff_tail(dz, ez, dez),
];
if tails.iter().all(|&t| t == 0.0) {
return fin.sign();
}
insphere_exact(a, b, c, d, e)
}
/// The same expansion as stage B (shared 2×2 minors, then the 3×3 minors, then the lifts), on
/// the exact differences.
fn insphere_exact(a: &Point3, b: &Point3, c: &Point3, d: &Point3, e: &Point3) -> Sign {
let [ax, ay, az] = diffs3(a, e);
let [bx, by, bz] = diffs3(b, e);
let [cx, cy, cz] = diffs3(c, e);
let [dx, dy, dz] = diffs3(d, e);
let ab = det2(&ax, &ay, &bx, &by);
let bc = det2(&bx, &by, &cx, &cy);
let cd = det2(&cx, &cy, &dx, &dy);
let da = det2(&dx, &dy, &ax, &ay);
let ac = det2(&ax, &ay, &cx, &cy);
let bd = det2(&bx, &by, &dx, &dy);
let abc = az.clone() * bc.clone() - bz.clone() * ac.clone() + cz.clone() * ab.clone();
let bcd = bz.clone() * cd.clone() - cz.clone() * bd.clone() + dz.clone() * bc;
let cda = cz.clone() * da.clone() + dz.clone() * ac + az.clone() * cd;
let dab = dz.clone() * ab + az.clone() * bd + bz.clone() * da;
let lift = sq3(&dx, &dy, &dz) * abc - sq3(&cx, &cy, &cz) * dab + sq3(&bx, &by, &bz) * cda - sq3(&ax, &ay, &az) * bcd;
lift.sign()
}
/// The exact stages alone, for tests that exercise them directly.
#[cfg(test)]
pub(crate) mod exact {
use super::*;
pub(crate) fn orient2d(a: &Point2, b: &Point2, c: &Point2) -> Sign {
let [ax, ay] = diffs2(a, c);
let [bx, by] = diffs2(b, c);
det2(&ax, &ay, &bx, &by).sign()
}
pub(crate) fn orient3d(a: &Point3, b: &Point3, c: &Point3, d: &Point3) -> Sign {
orient3d_exact(a, b, c, d)
}
pub(crate) fn incircle(a: &Point2, b: &Point2, c: &Point2, d: &Point2) -> Sign {
incircle_exact(a, b, c, d)
}
pub(crate) fn insphere(a: &Point3, b: &Point3, c: &Point3, d: &Point3, e: &Point3) -> Sign {
insphere_exact(a, b, c, d, e)
}
}