use axiolid_contracts::{Certified, Precision, Sign};
use axiolid_core::Point2;
use crate::expansion::{two_diff, two_product, two_sum};
const EPSILON: f64 = f64::EPSILON / 2.0;
const ORIENT2D_ERROR_FACTOR: f64 = (3.0 + 16.0 * EPSILON) * EPSILON;
#[must_use]
pub fn orient2d(a: Point2, b: Point2, c: Point2) -> Certified {
match orient2d_filter(a, b, c) {
Certified::Certain { sign, .. } => Certified::exact_sign(sign),
Certified::Uncertain { .. } => Certified::exact_sign(orient2d_exact(a, b, c)),
_ => Certified::exact_sign(orient2d_exact(a, b, c)),
}
}
#[must_use]
pub fn orient2d_filter(a: Point2, b: Point2, c: Point2) -> Certified {
let left = (a.x - c.x) * (b.y - c.y);
let right = (a.y - c.y) * (b.x - c.x);
let determinant = left - right;
let magnitude = left.abs() + right.abs();
let error_bound = ORIENT2D_ERROR_FACTOR * magnitude;
Certified::from_filter(determinant, error_bound, Precision::F64)
}
#[must_use]
fn orient2d_exact(a: Point2, b: Point2, c: Point2) -> Sign {
let (acx, acx_err) = two_diff(a.x, c.x);
let (bcy, bcy_err) = two_diff(b.y, c.y);
let (acy, acy_err) = two_diff(a.y, c.y);
let (bcx, bcx_err) = two_diff(b.x, c.x);
let (left, left_err) = two_product(acx, bcy);
let (right, right_err) = two_product(acy, bcx);
let left_correction = acx * bcy_err + acx_err * bcy + acx_err * bcy_err;
let right_correction = acy * bcx_err + acy_err * bcx + acy_err * bcx_err;
let (head, head_err) = two_diff(left, right);
debug_assert_eq!(head_err, 0.0, "filtered inputs make this subtraction exact");
let tail = (left_err - right_err) + (left_correction - right_correction);
let (total, total_err) = two_sum(head, head_err + tail);
let value = if total != 0.0 { total } else { total_err };
if value > 0.0 {
Sign::Positive
} else if value < 0.0 {
Sign::Negative
} else {
Sign::Zero
}
}