use crate::distance::{
Point, along as at, dot, fraction_on_segment, has_area, on_triangle, squared, sub,
};
const SMALL: f64 = f64::MIN_POSITIVE * 9_007_199_254_740_992.0;
pub(crate) fn edge(a: f64, b: f64, c: f64, d: f64) -> f64 {
let (p, q) = (a * b, c * d);
if p.to_bits() != q.to_bits() {
return p - q;
}
if p.abs() < SMALL {
return 0.0;
}
a.mul_add(b, -p) - c.mul_add(d, -q)
}
pub fn crossing(a: Point, b: Point, t: [Point; 3]) -> Option<f64> {
if !has_area(t) {
return None;
}
let d = sub(b, a);
let mut kz = 0;
for k in 1..3 {
if d[k].abs() > d[kz].abs() {
kz = k;
}
}
if d[kz] == 0.0 {
return None;
}
let (kx, ky) = ((kz + 1) % 3, (kz + 2) % 3);
let (sx, sy) = (d[kx] / d[kz], d[ky] / d[kz]);
let [pa, pb, pc] = t.map(|p| sub(p, a));
let sheared = |p: Point| [p[kx] - sx * p[kz], p[ky] - sy * p[kz], p[kz]];
let ([ax, ay, az], [bx, by, bz], [cx, cy, cz]) = (sheared(pa), sheared(pb), sheared(pc));
let u = edge(cx, by, cy, bx);
let v = edge(ax, cy, ay, cx);
let w = edge(bx, ay, by, ax);
if (u < 0.0 || v < 0.0 || w < 0.0) && (u > 0.0 || v > 0.0 || w > 0.0) {
return None;
}
let det = u + v + w;
if det == 0.0 {
return None;
}
let (along, limit) = (u * az + v * bz + w * cz, det * d[kz]);
let (along, limit) = if limit < 0.0 { (-along, -limit) } else { (along, limit) };
if along < 0.0 || along > limit {
return None;
}
Some(along / limit)
}
fn segments(a: Point, b: Point, p: Point, q: Point) -> (f64, f64) {
let (d1, d2, r) = (sub(b, a), sub(q, p), sub(a, p));
let (aa, ee, bb) = (dot(d1, d1), dot(d2, d2), dot(d1, d2));
let (c, f) = (dot(d1, r), dot(d2, r));
let mut candidates = Vec::with_capacity(5);
let denom = aa * ee - bb * bb;
if denom > 0.0 {
let (s, u) = ((bb * f - c * ee) / denom, (aa * f - bb * c) / denom);
if (0.0..=1.0).contains(&s) && (0.0..=1.0).contains(&u) {
candidates.push((s, u));
}
}
for s in [0.0, 1.0] {
candidates.push((s, fraction_on_segment(at(a, b, s), p, q)));
}
for u in [0.0, 1.0] {
candidates.push((fraction_on_segment(at(p, q, u), a, b), u));
}
let gap = |(s, u): (f64, f64)| squared(at(a, b, s), at(p, q, u));
candidates.into_iter().fold((0.0, 0.0), |best, c| {
let (g, h) = (gap(c), gap(best));
if g.total_cmp(&h).then(c.0.total_cmp(&best.0)).is_lt() { c } else { best }
})
}
pub fn nearest(a: Point, b: Point, t: [Point; 3]) -> (f64, f64) {
if let Some(s) = crossing(a, b, t) {
return (s, 0.0);
}
let mut best = (0.0, f64::INFINITY);
let mut offer = |s: f64, d: f64| {
if d.total_cmp(&best.1).then(s.total_cmp(&best.0)).is_lt() {
best = (s, d);
}
};
for s in [0.0, 1.0] {
let p = at(a, b, s);
offer(s, squared(p, on_triangle(p, t)));
}
let [x, y, z] = t;
for (p, q) in [(x, y), (y, z), (z, x)] {
let (s, u) = segments(a, b, p, q);
offer(s, squared(at(a, b, s), at(p, q, u)));
}
best
}
#[cfg(test)]
mod tests {
use super::*;
fn pow2(e: i64) -> f64 {
f64::from_bits(u64::try_from(e + 1023).unwrap_or(0) << 52)
}
#[test]
fn ties_are_decided_by_rounding_errors_down_to_small() {
let (up, down) = (1.0 + f64::EPSILON, 1.0 - f64::EPSILON);
assert!(edge(up, down, 1.0, 1.0) < 0.0);
assert_eq!(SMALL.to_bits(), pow2(-969).to_bits());
let h = pow2(-485);
assert_eq!(edge(up * h, down * h, h, h).to_bits(), 0.0_f64.to_bits());
assert!(edge(SMALL, 1.0, up, SMALL * down) > 0.0);
}
}