use axiolid_core::Point3;
use axiolid_exact::{Arith, Dyadic};
use axiolid_guarantees::Sign;
pub(crate) type ExactPoint = [Dyadic; 3];
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum RingPosition {
Inside,
OnEdge,
Outside,
}
fn dy(v: f64) -> Option<Dyadic> {
Dyadic::try_from_f64(v)
}
pub(crate) fn exact(p: Point3) -> Option<ExactPoint> {
Some([dy(p.x)?, dy(p.y)?, dy(p.z)?])
}
fn minus(p: &ExactPoint, q: &ExactPoint) -> ExactPoint {
[p[0].sub(&q[0]), p[1].sub(&q[1]), p[2].sub(&q[2])]
}
fn cross(u: &ExactPoint, v: &ExactPoint) -> ExactPoint {
[
u[1].mul(&v[2]).sub(&u[2].mul(&v[1])),
u[2].mul(&v[0]).sub(&u[0].mul(&v[2])),
u[0].mul(&v[1]).sub(&u[1].mul(&v[0])),
]
}
fn dot(u: &ExactPoint, v: &ExactPoint) -> Dyadic {
u[0].mul(&v[0]).add(&u[1].mul(&v[1])).add(&u[2].mul(&v[2]))
}
fn sign(value: &Dyadic) -> Sign {
value
.sign()
.expect("a dyadic value always has a decided sign")
}
fn less(a: &Dyadic, b: &Dyadic) -> bool {
sign(&a.sub(b)) == Sign::Negative
}
fn normal(face: &[Point3]) -> Option<ExactPoint> {
let a = exact(face[0])?;
Some(cross(
&minus(&exact(face[1])?, &a),
&minus(&exact(face[2])?, &a),
))
}
fn orient3(a: &ExactPoint, b: &ExactPoint, c: &ExactPoint, d: &ExactPoint) -> Sign {
sign(&dot(&minus(a, d), &cross(&minus(b, d), &minus(c, d))))
}
pub(crate) fn side(face: &[Point3], point: &ExactPoint) -> Option<Sign> {
Some(orient3(
&exact(face[0])?,
&exact(face[1])?,
&exact(face[2])?,
point,
))
}
pub(crate) fn turn(origin: &ExactPoint, far: Point3, a: Point3, b: Point3) -> Option<Sign> {
Some(orient3(origin, &exact(far)?, &exact(a)?, &exact(b)?))
}
pub(crate) fn ring_position(face: &[Point3], point: &ExactPoint) -> Option<RingPosition> {
let n = normal(face)?;
let approx = [0, 1, 2].map(|k| n[k].to_f64().abs());
let mut axes = [0usize, 1, 2];
axes.sort_by(|&i, &j| approx[j].total_cmp(&approx[i]));
let drop = *axes.iter().find(|&&k| sign(&n[k]) != Sign::Zero)?;
let (u, v) = ((drop + 1) % 3, (drop + 2) % 3);
let ring: Vec<ExactPoint> = face.iter().map(|&p| exact(p)).collect::<Option<_>>()?;
let (qu, qv) = (&point[u], &point[v]);
let orient2 = |a: &ExactPoint, b: &ExactPoint| {
sign(
&b[u]
.sub(&a[u])
.mul(&qv.sub(&a[v]))
.sub(&b[v].sub(&a[v]).mul(&qu.sub(&a[u]))),
)
};
let within = |q: &Dyadic, a: &Dyadic, b: &Dyadic| {
let (lo, hi) = if less(b, a) { (b, a) } else { (a, b) };
!less(q, lo) && !less(hi, q)
};
for i in 0..ring.len() {
let (a, b) = (&ring[i], &ring[(i + 1) % ring.len()]);
if orient2(a, b) == Sign::Zero && within(qu, &a[u], &b[u]) && within(qv, &a[v], &b[v]) {
return Some(RingPosition::OnEdge);
}
}
let mut inside = false;
for i in 0..ring.len() {
let (a, b) = (&ring[i], &ring[(i + 1) % ring.len()]);
if less(qv, &a[v]) != less(qv, &b[v]) {
let s = orient2(a, b);
let upward = less(&a[v], &b[v]);
if (upward && s == Sign::Negative) || (!upward && s == Sign::Positive) {
inside = !inside;
}
}
}
Some(if inside {
RingPosition::Inside
} else {
RingPosition::Outside
})
}
pub(crate) fn normals_agree(first: &[Point3], second: &[Point3]) -> Option<bool> {
Some(sign(&dot(&normal(first)?, &normal(second)?)) == Sign::Positive)
}
pub(crate) fn interior_point(ring: &[Point3]) -> Option<ExactPoint> {
let quarter = dy(0.25)?;
let half = dy(0.5)?;
let first = exact(ring[0])?;
for i in 1..ring.len() - 1 {
let (b, c) = (exact(ring[i])?, exact(ring[i + 1])?);
let area = cross(&minus(&b, &first), &minus(&c, &first));
if area.iter().all(|a| sign(a) == Sign::Zero) {
continue;
}
let point: ExactPoint =
[0, 1, 2].map(|k| first[k].add(&b[k]).mul(&quarter).add(&c[k].mul(&half)));
if ring_position(ring, &point)? == RingPosition::Inside {
return Some(point);
}
}
None
}
pub(crate) fn approx(point: &ExactPoint) -> Point3 {
Point3::new(point[0].to_f64(), point[1].to_f64(), point[2].to_f64())
}
pub(crate) fn plane_crossing(
plane: &[Point3],
a: Point3,
b: Point3,
guess: Point3,
) -> Option<Point3> {
let normal = normal(plane)?;
let (ea, eb) = (exact(a)?, exact(b)?);
let along = minus(&eb, &ea);
let mut span = dot(&normal, &along);
let mut reach = dot(&normal, &minus(&exact(plane[0])?, &ea));
match sign(&span) {
Sign::Zero => return None,
Sign::Negative => {
span = span.neg();
reach = reach.neg();
}
_ => {}
}
let mut out = [0.0; 3];
for k in 0..3 {
let (from, to) = ([a.x, a.y, a.z][k], [b.x, b.y, b.z][k]);
out[k] = if sign(&along[k]) == Sign::Zero {
from
} else {
let numerator = ea[k].mul(&span).add(&along[k].mul(&reach));
let (lo, hi) = if from < to { (from, to) } else { (to, from) };
nearest_ratio(&numerator, &span, [guess.x, guess.y, guess.z][k], lo, hi)?
};
}
Some(Point3::new(out[0], out[1], out[2]))
}
fn key(x: f64) -> u64 {
let bits = (x + 0.0).to_bits();
if bits >> 63 == 1 {
!bits
} else {
bits | (1 << 63)
}
}
fn unkey(k: u64) -> f64 {
f64::from_bits(if k >> 63 == 1 { k & !(1 << 63) } else { !k })
}
fn nearest_ratio(
numerator: &Dyadic,
denominator: &Dyadic,
guess: f64,
lo: f64,
hi: f64,
) -> Option<f64> {
let half = dy(0.5)?;
let beyond = |x: f64, y: f64| -> Option<Sign> {
let mid = dy(x)?.add(&dy(y)?).mul(&half);
Some(sign(&numerator.sub(&mid.mul(denominator))))
};
let versus = |x: f64| -> Option<Sign> { Some(sign(&numerator.sub(&dy(x)?.mul(denominator)))) };
let even = |r: f64| r.to_bits() & 1 == 0;
const WALK: usize = 8;
let mut r = if guess.is_finite() {
guess.clamp(lo, hi)
} else {
lo
};
let mut settled = false;
for _ in 0..WALK {
let up = r.next_up();
match beyond(r, up)? {
Sign::Positive => r = up,
Sign::Zero if !even(r) => r = up,
_ => {
settled = true;
break;
}
}
}
if settled {
for _ in 0..WALK {
let down = r.next_down();
match beyond(down, r)? {
Sign::Negative => r = down,
Sign::Zero if !even(r) => r = down,
_ => return Some(r),
}
}
}
let (mut low, mut high) = (key(lo), key(hi));
if versus(lo)? == Sign::Negative || versus(hi)? == Sign::Positive {
return None;
}
while high - low > 1 {
let mid = low + (high - low) / 2;
if versus(unkey(mid))? == Sign::Negative {
high = mid;
} else {
low = mid;
}
}
let floor = if versus(unkey(high))? == Sign::Negative {
unkey(low)
} else {
unkey(high)
};
let next = floor.next_up();
Some(match beyond(floor, next)? {
Sign::Positive => next,
Sign::Zero if !even(floor) => next,
_ => floor,
})
}
#[cfg(test)]
mod tests {
use super::*;
fn quotient(n: f64, d: f64) -> (Dyadic, Dyadic) {
(dy(n).unwrap(), dy(d).unwrap())
}
#[test]
fn nearest_ratio_is_the_ieee_quotient_from_any_seed() {
for &(n, d) in &[(1.0, 3.0), (2.0, 3.0), (1.0, 7.0), (5.0, 9.0), (0.1, 0.3)] {
let (num, den) = quotient(n, d);
let want = n / d;
for seed in [want, want.next_up(), 0.0, 1.0, f64::NAN, 0.5] {
assert_eq!(
nearest_ratio(&num, &den, seed, 0.0, 1.0),
Some(want),
"{n}/{d} from seed {seed}"
);
}
}
}
#[test]
fn a_crossing_that_is_a_double_comes_back_exactly() {
let t = 1.0 / 3.0;
let plane = [
Point3::new(0.0, 0.0, t),
Point3::new(1.0, 0.0, t),
Point3::new(0.0, 1.0, t),
];
let (a, b) = (Point3::new(0.5, 0.5, 0.0), Point3::new(0.5, 0.5, 1.0));
let cut = plane_crossing(&plane, a, b, Point3::new(0.5, 0.5, 0.9)).unwrap();
assert_eq!(cut, Point3::new(0.5, 0.5, t));
}
#[test]
fn a_ring_enclosing_no_area_has_no_interior_point() {
let z = 1.0 / 3.0;
let collapsed = [
Point3::new(1.0, 1.0, z),
Point3::new(2.0 / 3.0, 1.0, z),
Point3::new(1.0, 1.0, z.next_down()),
Point3::new(2.0 / 3.0, 1.0, z),
];
assert_eq!(interior_point(&collapsed), None);
let sliver = [
Point3::new(0.0, 1.0, z.next_down()),
Point3::new(0.0, 1.0, z),
Point3::new(1.0, 1.0, z),
Point3::new(1.0, 1.0, z.next_down()),
];
let inside = interior_point(&sliver).expect("a sliver has area");
assert_eq!(ring_position(&sliver, &inside), Some(RingPosition::Inside));
}
#[test]
fn ties_round_to_even_from_the_walk_and_from_bisection() {
let one = dy(1.0).unwrap();
let ulp = dy(f64::EPSILON).unwrap();
let half_ulp = ulp.mul(&dy(0.5).unwrap());
let low_tie = one.add(&half_ulp);
let high_tie = one.add(&ulp).add(&half_ulp);
let (even_low, even_high) = (1.0, 1.0 + 2.0 * f64::EPSILON);
let seeds = |want: f64| [want, want.next_up(), want.next_down(), 1.0 + f64::EPSILON];
for (tie, want) in [(&low_tie, even_low), (&high_tie, even_high)] {
for seed in seeds(want) {
assert_eq!(
nearest_ratio(tie, &one, seed, 0.0, 2.0),
Some(want),
"seed {seed}"
);
}
assert_eq!(nearest_ratio(tie, &one, f64::NAN, 0.0, 2.0), Some(want));
}
}
#[test]
fn an_interior_point_is_never_taken_on_the_ring() {
let p = |x: f64, y: f64| Point3::new(x, y, 0.0);
let ring = [
p(0.0, 4.0),
p(0.0, 0.0),
p(4.0, 0.0),
p(4.0, 1.0),
p(1.0, 1.0),
p(1.0, 4.0),
];
let point = interior_point(&ring).expect("the ring has area");
assert_eq!(approx(&point), p(0.75, 3.25));
}
}