use super::{fixed, interval, rational};
use super::{DropAxis, ImplicitPoint, Sign};
pub fn orient3d(a: &ImplicitPoint, b: &ImplicitPoint, c: &ImplicitPoint, d: &ImplicitPoint) -> Sign {
use ImplicitPoint::{Explicit, Lpi, Tpi};
match (a, b, c, d) {
(Explicit(a), Explicit(b), Explicit(c), Explicit(d)) => {
Sign::from_f64(geometry_predicates::orient3d(*a, *b, *c, *d))
}
(Lpi(l), Explicit(b), Explicit(c), Explicit(d)) => interval::lpi_orient3d(l, *b, *c, *d)
.or_else(|| fixed::indirect_orient3d(a, *b, *c, *d))
.unwrap_or_else(|| rational::lpi_orient3d(l, *b, *c, *d)),
(Tpi(t), Explicit(b), Explicit(c), Explicit(d)) => interval::tpi_orient3d(t, *b, *c, *d)
.or_else(|| fixed::indirect_orient3d(a, *b, *c, *d))
.unwrap_or_else(|| rational::tpi_orient3d(t, *b, *c, *d)),
_ => unimplemented!(
"kernel::orient3d: implicit-point configuration never produced by the arrangement pipeline"
),
}
}
pub fn orient2d(a: &ImplicitPoint, b: &ImplicitPoint, c: &ImplicitPoint, axis: DropAxis) -> Sign {
use ImplicitPoint::{Explicit, Lpi, Tpi};
let (i, j) = match axis {
DropAxis::X => (1, 2),
DropAxis::Y => (0, 2),
DropAxis::Z => (0, 1),
};
match (a, b, c) {
(Explicit(a), Explicit(b), Explicit(c)) => {
Sign::from_f64(geometry_predicates::orient2d([a[i], a[j]], [b[i], b[j]], [c[i], c[j]]))
}
(Lpi(l), Explicit(b), Explicit(c)) => interval::lpi_orient2d(l, *b, *c, axis)
.or_else(|| fixed::indirect_orient2d(a, *b, *c, axis))
.unwrap_or_else(|| rational::lpi_orient2d(l, *b, *c, axis)),
(Tpi(t), Explicit(b), Explicit(c)) => interval::tpi_orient2d(t, *b, *c, axis)
.or_else(|| fixed::indirect_orient2d(a, *b, *c, axis))
.unwrap_or_else(|| rational::tpi_orient2d(t, *b, *c, axis)),
_ => unimplemented!(
"kernel::orient2d: implicit-point configuration never produced by the arrangement pipeline"
),
}
}
pub fn orient2d_2i(a: &ImplicitPoint, b: &ImplicitPoint, c: [f64; 3], axis: DropAxis) -> Sign {
interval::orient2d_2i(a, b, c, axis)
.or_else(|| fixed::orient2d_2i(a, b, c, axis))
.unwrap_or_else(|| rational::orient2d_2i(a, b, c, axis))
}
pub fn orient2d_3i(a: &ImplicitPoint, b: &ImplicitPoint, c: &ImplicitPoint, axis: DropAxis) -> Sign {
interval::orient2d_3i(a, b, c, axis)
.or_else(|| fixed::orient2d_3i(a, b, c, axis))
.unwrap_or_else(|| rational::orient2d_3i(a, b, c, axis))
}
pub fn cmp_lex(a: &ImplicitPoint, b: &ImplicitPoint) -> Sign {
interval::cmp_lex(a, b).or_else(|| fixed::cmp_lex(a, b)).unwrap_or_else(|| rational::cmp_lex(a, b))
}
#[inline]
fn explicit_coord(p: &ImplicitPoint) -> [f64; 3] {
match p {
ImplicitPoint::Explicit(c) => *c,
_ => unreachable!("explicit_coord on an implicit point"),
}
}
pub fn orient2d_any(a: &ImplicitPoint, b: &ImplicitPoint, c: &ImplicitPoint, axis: DropAxis) -> Sign {
let pts = [a, b, c];
let key = |p: &ImplicitPoint| u8::from(matches!(p, ImplicitPoint::Explicit(_))); let keys = [key(a), key(b), key(c)];
let mut perm = [0usize, 1, 2];
perm.sort_by_key(|&i| keys[i]); let inversions = u8::from(perm[0] > perm[1]) + u8::from(perm[0] > perm[2]) + u8::from(perm[1] > perm[2]);
let rp = [pts[perm[0]], pts[perm[1]], pts[perm[2]]];
let n_implicit = 3 - (keys[0] + keys[1] + keys[2]) as usize;
let canonical = match n_implicit {
0 | 1 => orient2d(rp[0], rp[1], rp[2], axis),
2 => orient2d_2i(rp[0], rp[1], explicit_coord(rp[2]), axis),
_ => orient2d_3i(rp[0], rp[1], rp[2], axis),
};
if inversions % 2 == 1 {
canonical.flip()
} else {
canonical
}
}
#[cfg(test)]
mod tests {
use super::super::{rational, DropAxis, Lpi, Tpi};
use super::{
cmp_lex, orient2d, orient2d_2i, orient2d_3i, orient2d_any, orient3d, ImplicitPoint, Sign,
};
fn e(p: [f64; 3]) -> ImplicitPoint {
ImplicitPoint::Explicit(p)
}
fn battery() -> Vec<[[f64; 3]; 4]> {
vec![
[[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [1., 1., 0.]], [[0., 0., 12.3456789], [10., 0., 12.3456789], [0., 7., 12.3456789], [3.3, 2.1, 12.3456789 + 1e-9]],
[[0., 0., 12.3456789], [10., 0., 12.3456789], [0., 7., 12.3456789], [3.3, 2.1, 12.3456789 - 1e-9]],
[[1e7, 1e7, 0.], [1e7 + 1., 1e7, 0.], [1e7, 1e7 + 1., 0.], [1e7 + 0.5, 1e7 + 0.5, 1e-7]],
[[0., 0., 0.], [1., 2., 3.], [-2., 1., 0.5], [0.5, 0.5, 0.5]],
[[0., 0., 0.], [1., 1., 1.], [2., 2., 2.], [5., 1., 9.]], [[0., 0., 0.], [1e-4, 0., 0.], [0., 1e-4, 0.], [0., 0., 1e-4]],
[[0., 0., 0.], [0., 1e-4, 0.], [1e-4, 0., 0.], [0., 0., 1e-4]],
[[-3., 2., 5.], [7., -1., 2.], [4., 4., -6.], [1.5, 0.0, 0.25]],
]
}
fn lpi_cases() -> Vec<(Lpi, [f64; 3], [f64; 3], [f64; 3])> {
vec![
(
Lpi { p: [0.3, 0.3, -1.], q: [0.3, 0.3, 1.], r: [0., 0., 0.], s: [2., 0., 0.], t: [0., 2., 0.] },
[0., 0., 1.], [1., 0., 1.], [0., 1., 1.],
),
(
Lpi { p: [0.3, 0.3, -1.], q: [0.3, 0.3, 1.], r: [0., 0., 0.], s: [2., 0., 0.], t: [0., 2., 0.] },
[0., 0., -1.], [1., 0., -1.], [0., 1., -1.],
),
(
Lpi { p: [1., 1., 0.], q: [2., 3., 4.], r: [0., 0., 1.], s: [3., 0., 2.], t: [0., 3., 2.] },
[5., -2., 0.], [-1., 4., 3.], [2., 2., -3.],
),
(
Lpi { p: [12.3, 4.5, -2.], q: [12.3, 4.5, 6.], r: [0., 0., 3.1], s: [20., 0., 3.1], t: [0., 9., 3.1] },
[10., 10., 10.], [-5., 0., 0.], [0., -5., 8.],
),
]
}
#[test]
fn explicit_orient3d_matches_rational_oracle() {
for cfg in battery() {
let [a, b, c, d] = cfg;
let fast = orient3d(&e(a), &e(b), &e(c), &e(d));
let oracle = rational::orient3d_exact(a, b, c, d);
assert_eq!(fast, oracle, "explicit orient3d != rational oracle on {cfg:?}");
}
}
#[test]
fn lpi_orient3d_matches_materialised_point() {
for (l, p2, p3, p4) in lpi_cases() {
let homog = rational::lpi_orient3d(&l, p2, p3, p4);
let direct = rational::orient3d_exact_pt(&rational::lpi_point(&l), p2, p3, p4);
assert_eq!(homog, direct, "LPI homogenisation/flip wrong for {l:?}");
assert_ne!(homog, Sign::Zero, "test LPI case should be off-plane: {l:?}");
}
}
#[test]
fn lpi_orient3d_sign_invariant_to_plane_winding() {
for (l, p2, p3, p4) in lpi_cases() {
let l_rewound = Lpi { s: l.t, t: l.s, ..l };
assert_eq!(
rational::lpi_orient3d(&l, p2, p3, p4),
rational::lpi_orient3d(&l_rewound, p2, p3, p4),
"LPI-orient3d sign changed under plane re-winding — the sign(d) flip is wrong/missing"
);
}
}
#[test]
fn assemble_sign_per_config_flip() {
use super::super::assemble_sign;
assert_eq!(assemble_sign(Sign::Positive, &[Sign::Negative]), Sign::Negative);
assert_eq!(assemble_sign(Sign::Positive, &[Sign::Negative, Sign::Negative]), Sign::Positive);
assert_eq!(assemble_sign(Sign::Negative, &[Sign::Positive]), Sign::Negative);
assert_eq!(assemble_sign(Sign::Positive, &[Sign::Zero]), Sign::Zero);
assert_eq!(assemble_sign(Sign::Positive, &[]), Sign::Positive);
}
#[test]
fn next_up_down_are_adjacent() {
use super::super::interval::{next_down, next_up};
for &x in &[1.0, -1.0, 0.0, 1e7, -1e-9, 12.3456789, f64::MIN_POSITIVE] {
assert!(next_up(x) > x, "next_up({x}) not strictly greater");
assert!(next_down(x) < x, "next_down({x}) not strictly less");
assert_eq!(next_down(next_up(x)), x, "next_up/next_down not adjacent at {x}");
}
}
struct Lcg(u64);
impl Lcg {
fn u(&mut self) -> u64 {
self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1442695040888963407);
self.0
}
fn f(&mut self, lo: f64, hi: f64) -> f64 {
let unit = (self.u() >> 11) as f64 / (1u64 << 53) as f64; lo + (hi - lo) * unit
}
fn p(&mut self) -> [f64; 3] {
[self.f(-10., 10.), self.f(-10., 10.), self.f(-10., 10.)]
}
}
#[test]
fn interval_tier_is_sound_and_the_cascade_equals_exact() {
use super::super::{interval, rational, Lpi};
let mut rng = Lcg(0x1234_5678_9abc_def0);
let (mut definite, mut escalated) = (0u32, 0u32);
for _ in 0..3000 {
let l = Lpi { p: rng.p(), q: rng.p(), r: rng.p(), s: rng.p(), t: rng.p() };
let (p2, p3, p4) = (rng.p(), rng.p(), rng.p());
let exact = rational::lpi_orient3d(&l, p2, p3, p4);
match interval::lpi_orient3d(&l, p2, p3, p4) {
Some(s) => {
assert_eq!(s, exact, "interval returned a WRONG definite sign for {l:?}");
definite += 1;
}
None => escalated += 1,
}
let cascade = orient3d(&ImplicitPoint::Lpi(l), &e(p2), &e(p3), &e(p4));
assert_eq!(cascade, exact, "cascade != exact for {l:?}");
}
assert!(
definite as f64 / (definite + escalated) as f64 > 0.95,
"interval resolved only {definite}/{} — fast path too cold",
definite + escalated
);
eprintln!("interval tier: {definite} definite, {escalated} escalated to exact");
}
fn tpi_cases() -> Vec<(Tpi, [f64; 3], [f64; 3], [f64; 3])> {
let axis_aligned = Tpi {
planes: [
[[0., 0., 0.], [1., 0., 0.], [0., 1., 0.]], [[0.3, 0., 0.], [0.3, 1., 0.], [0.3, 0., 1.]], [[0., 0.4, 0.], [1., 0.4, 0.], [0., 0.4, 1.]], ],
};
let tilted = Tpi {
planes: [
[[0., 0., 1.], [3., 0., 2.], [0., 3., 2.]],
[[1., 0., 0.], [1., 2., 1.], [2., 0., 3.]],
[[-1., -1., 0.], [2., -1., 1.], [-1., 2., 2.]],
],
};
vec![
(axis_aligned, [0., 0., 1.], [1., 0., 1.], [0., 1., 1.]), (axis_aligned, [0., 0., -1.], [1., 0., -1.], [0., 1., -1.]), (tilted, [5., -2., 0.], [-1., 4., 3.], [2., 2., -3.]),
(tilted, [10., 10., 10.], [-5., 0., 0.], [0., -5., 8.]),
]
}
#[test]
fn tpi_orient3d_matches_materialised_point() {
for (t, p2, p3, p4) in tpi_cases() {
let homog = rational::tpi_orient3d(&t, p2, p3, p4);
let direct = rational::orient3d_exact_pt(&rational::tpi_point(&t), p2, p3, p4);
assert_eq!(homog, direct, "TPI homogenisation/flip wrong for {t:?}");
assert_ne!(homog, Sign::Zero, "test TPI case should be off-plane: {t:?}");
}
}
#[test]
fn tpi_orient3d_sign_invariant_to_plane_winding() {
for (t, p2, p3, p4) in tpi_cases() {
let mut rewound = t;
rewound.planes[0].swap(1, 2);
assert_eq!(
rational::tpi_orient3d(&t, p2, p3, p4),
rational::tpi_orient3d(&rewound, p2, p3, p4),
"TPI-orient3d sign changed under plane re-winding — the sign(d) flip is wrong/missing"
);
}
}
#[test]
fn tpi_interval_is_sound_and_the_cascade_equals_exact() {
use super::super::interval;
let mut rng = Lcg(0xfeed_face_cafe_d00d);
let (mut definite, mut escalated) = (0u32, 0u32);
for _ in 0..2000 {
let plane = |rng: &mut Lcg| [rng.p(), rng.p(), rng.p()];
let t = Tpi { planes: [plane(&mut rng), plane(&mut rng), plane(&mut rng)] };
let (p2, p3, p4) = (rng.p(), rng.p(), rng.p());
let exact = rational::tpi_orient3d(&t, p2, p3, p4);
match interval::tpi_orient3d(&t, p2, p3, p4) {
Some(s) => {
assert_eq!(s, exact, "TPI interval returned a WRONG definite sign for {t:?}");
definite += 1;
}
None => escalated += 1,
}
let cascade = orient3d(&ImplicitPoint::Tpi(t), &e(p2), &e(p3), &e(p4));
assert_eq!(cascade, exact, "TPI cascade != exact for {t:?}");
}
assert!(
definite as f64 / (definite + escalated) as f64 > 0.80,
"TPI interval resolved only {definite}/{}",
definite + escalated
);
eprintln!("TPI interval tier: {definite} definite, {escalated} escalated");
}
const AXES: [DropAxis; 3] = [DropAxis::X, DropAxis::Y, DropAxis::Z];
#[test]
fn explicit_orient2d_matches_oracle() {
for axis in AXES {
for cfg in battery() {
let [a, b, c, _d] = cfg;
let fast = orient2d(&e(a), &e(b), &e(c), axis);
let oracle = rational::orient2d_exact(a, b, c, axis);
assert_eq!(fast, oracle, "explicit orient2d != oracle on {cfg:?} axis {axis:?}");
}
}
}
#[test]
fn indirect_orient2d_matches_materialised_point() {
for axis in AXES {
for (l, p2, p3, _p4) in lpi_cases() {
let homog = rational::lpi_orient2d(&l, p2, p3, axis);
let direct = rational::orient2d_exact_pt(&rational::lpi_point(&l), p2, p3, axis);
assert_eq!(homog, direct, "LPI orient2d homog/flip wrong, axis {axis:?}");
let cascade = orient2d(&ImplicitPoint::Lpi(l), &e(p2), &e(p3), axis);
assert_eq!(cascade, direct, "LPI orient2d cascade != exact, axis {axis:?}");
}
for (t, p2, p3, _p4) in tpi_cases() {
let homog = rational::tpi_orient2d(&t, p2, p3, axis);
let direct = rational::orient2d_exact_pt(&rational::tpi_point(&t), p2, p3, axis);
assert_eq!(homog, direct, "TPI orient2d homog/flip wrong, axis {axis:?}");
let cascade = orient2d(&ImplicitPoint::Tpi(t), &e(p2), &e(p3), axis);
assert_eq!(cascade, direct, "TPI orient2d cascade != exact, axis {axis:?}");
}
}
}
#[test]
fn orient2d_interval_is_sound_vs_oracle() {
use super::super::interval;
let mut rng = Lcg(0xabcd_1234_5678_9999);
for _ in 0..2000 {
let l = Lpi { p: rng.p(), q: rng.p(), r: rng.p(), s: rng.p(), t: rng.p() };
let (b, c) = (rng.p(), rng.p());
let axis = AXES[(rng.u() % 3) as usize];
let exact = rational::lpi_orient2d(&l, b, c, axis);
if let Some(s) = interval::lpi_orient2d(&l, b, c, axis) {
assert_eq!(s, exact, "orient2d interval returned a WRONG definite sign for {l:?}");
}
assert_eq!(orient2d(&ImplicitPoint::Lpi(l), &e(b), &e(c), axis), exact);
}
}
#[test]
fn lpi_point_lies_on_its_defining_plane() {
for (l, _, _, _) in lpi_cases() {
assert_eq!(
orient3d(&ImplicitPoint::Lpi(l), &e(l.r), &e(l.s), &e(l.t)),
Sign::Zero,
"LPI point is not on its defining plane R,S,T: {l:?}"
);
}
}
#[test]
fn tpi_point_lies_on_all_three_defining_planes() {
for (t, _, _, _) in tpi_cases() {
for plane in &t.planes {
assert_eq!(
orient3d(&ImplicitPoint::Tpi(t), &e(plane[0]), &e(plane[1]), &e(plane[2])),
Sign::Zero,
"TPI point is not on one of its defining planes: {t:?}"
);
}
}
}
#[test]
fn orient2d_1i_sign_invariant_to_plane_winding() {
for axis in AXES {
for (l, p2, p3, _p4) in lpi_cases() {
let rewound = Lpi { s: l.t, t: l.s, ..l };
assert_eq!(
rational::lpi_orient2d(&l, p2, p3, axis),
rational::lpi_orient2d(&rewound, p2, p3, axis),
"orient2d 1I sign changed under plane re-winding (axis {axis:?})"
);
}
}
}
#[test]
fn multi_implicit_orient2d_matches_materialised_oracle() {
let mut pts: Vec<ImplicitPoint> =
lpi_cases().into_iter().map(|(l, ..)| ImplicitPoint::Lpi(l)).collect();
pts.extend(tpi_cases().into_iter().map(|(t, ..)| ImplicitPoint::Tpi(t)));
let c = [1.3, -0.7, 2.1];
let cpt = rational::point_of(&ImplicitPoint::Explicit(c));
for axis in AXES {
for a in &pts {
for b in &pts {
let oracle = rational::orient2d_pts(
&rational::point_of(a), &rational::point_of(b), &cpt, axis);
assert_eq!(rational::orient2d_2i(a, b, c, axis), oracle, "orient2d_2i (axis {axis:?})");
for cc in &pts {
let oracle3 = rational::orient2d_pts(
&rational::point_of(a), &rational::point_of(b), &rational::point_of(cc), axis);
assert_eq!(rational::orient2d_3i(a, b, cc, axis), oracle3, "orient2d_3i (axis {axis:?})");
}
}
}
}
}
#[test]
fn multi_implicit_orient2d_winding_invariant() {
let l = lpi_cases()[2].0;
let a = ImplicitPoint::Lpi(l);
let a_rw = ImplicitPoint::Lpi(Lpi { s: l.t, t: l.s, ..l });
let b = ImplicitPoint::Tpi(tpi_cases()[0].0);
let cc = ImplicitPoint::Lpi(lpi_cases()[0].0);
let c = [0.4, 1.1, -0.3];
for axis in AXES {
assert_eq!(
rational::orient2d_2i(&a, &b, c, axis),
rational::orient2d_2i(&a_rw, &b, c, axis),
"2I sign changed under plane re-winding"
);
assert_eq!(
rational::orient2d_3i(&a, &b, &cc, axis),
rational::orient2d_3i(&a_rw, &b, &cc, axis),
"3I sign changed under plane re-winding"
);
}
}
#[test]
fn orient2d_any_matches_oracle_for_every_permutation_and_mix() {
let mut pts: Vec<ImplicitPoint> = vec![e([0.0, 0.0, 0.0]), e([3.0, 1.0, -2.0])];
pts.extend(lpi_cases().into_iter().take(2).map(|(l, ..)| ImplicitPoint::Lpi(l)));
pts.extend(tpi_cases().into_iter().take(2).map(|(t, ..)| ImplicitPoint::Tpi(t)));
for axis in AXES {
for a in &pts {
for b in &pts {
for c in &pts {
let got = orient2d_any(a, b, c, axis);
let want = rational::orient2d_pts(
&rational::point_of(a),
&rational::point_of(b),
&rational::point_of(c),
axis,
);
assert_eq!(got, want, "orient2d_any mismatch (axis {axis:?})");
}
}
}
}
}
#[test]
fn cmp_lex_matches_materialised_order_and_is_a_total_order() {
use std::cmp::Ordering;
let mut pts: Vec<ImplicitPoint> =
lpi_cases().into_iter().map(|(l, ..)| ImplicitPoint::Lpi(l)).collect();
pts.extend(tpi_cases().into_iter().map(|(t, ..)| ImplicitPoint::Tpi(t)));
pts.push(e([1.5, -2.0, 0.25]));
pts.push(e([0.0, 0.0, 0.0]));
let oracle = |a: &ImplicitPoint, b: &ImplicitPoint| -> Sign {
let (pa, pb) = (rational::point_of(a), rational::point_of(b));
for k in 0..3 {
match pa[k].cmp(&pb[k]) {
Ordering::Less => return Sign::Negative,
Ordering::Greater => return Sign::Positive,
Ordering::Equal => {}
}
}
Sign::Zero
};
for a in &pts {
assert_eq!(rational::cmp_lex(a, a), Sign::Zero, "cmp_lex not reflexive-zero");
for b in &pts {
assert_eq!(rational::cmp_lex(a, b), oracle(a, b), "cmp_lex != materialised lex");
assert_eq!(
rational::cmp_lex(a, b),
rational::cmp_lex(b, a).flip(),
"cmp_lex not antisymmetric"
);
}
}
for a in &pts {
for b in &pts {
for c in &pts {
if rational::cmp_lex(a, b) == Sign::Negative
&& rational::cmp_lex(b, c) == Sign::Negative
{
assert_eq!(rational::cmp_lex(a, c), Sign::Negative, "cmp_lex not transitive");
}
}
}
}
}
#[test]
fn new_tier_interval_is_sound_and_cascade_equals_exact() {
use super::super::interval;
let mut rng = Lcg(0x0bad_c0de_1234_5678);
for _ in 0..500 {
let mk = |rng: &mut Lcg| Lpi { p: rng.p(), q: rng.p(), r: rng.p(), s: rng.p(), t: rng.p() };
let a = ImplicitPoint::Lpi(mk(&mut rng));
let b = ImplicitPoint::Lpi(mk(&mut rng));
let cc = ImplicitPoint::Lpi(mk(&mut rng));
let c = rng.p();
for axis in AXES {
let ex2 = rational::orient2d_2i(&a, &b, c, axis);
if let Some(s) = interval::orient2d_2i(&a, &b, c, axis) {
assert_eq!(s, ex2, "orient2d_2i interval wrong sign");
}
assert_eq!(orient2d_2i(&a, &b, c, axis), ex2, "2i cascade != exact");
let ex3 = rational::orient2d_3i(&a, &b, &cc, axis);
if let Some(s) = interval::orient2d_3i(&a, &b, &cc, axis) {
assert_eq!(s, ex3, "orient2d_3i interval wrong sign");
}
assert_eq!(orient2d_3i(&a, &b, &cc, axis), ex3, "3i cascade != exact");
}
let exl = rational::cmp_lex(&a, &b);
if let Some(s) = interval::cmp_lex(&a, &b) {
assert_eq!(s, exl, "cmp_lex interval wrong sign");
}
assert_eq!(cmp_lex(&a, &b), exl, "cmp_lex cascade != exact");
}
}
#[test]
fn cmp_lex_welds_coincident_lpi_and_tpi() {
let lpi = ImplicitPoint::Lpi(Lpi {
p: [0.3, 0.4, -1.],
q: [0.3, 0.4, 1.],
r: [0., 0., 0.],
s: [1., 0., 0.],
t: [0., 1., 0.],
});
let tpi = ImplicitPoint::Tpi(Tpi {
planes: [
[[0., 0., 0.], [1., 0., 0.], [0., 1., 0.]], [[0.3, 0., 0.], [0.3, 1., 0.], [0.3, 0., 1.]], [[0., 0.4, 0.], [1., 0.4, 0.], [0., 0.4, 1.]], ],
});
assert_eq!(rational::point_of(&lpi), rational::point_of(&tpi), "test points not coincident");
assert_eq!(
rational::cmp_lex(&lpi, &tpi),
Sign::Zero,
"coincident LPI/TPI not welded by cmp_lex"
);
}
#[test]
fn cmp_along_matches_materialised_oracle() {
use num_rational::BigRational;
use num_traits::Signed;
let mut pts: Vec<ImplicitPoint> =
vec![e([0., 0., 0.]), e([3., 1., -2.]), e([1.5, -2., 0.25])];
pts.extend(lpi_cases().into_iter().take(2).map(|(l, ..)| ImplicitPoint::Lpi(l)));
pts.extend(tpi_cases().into_iter().take(2).map(|(t, ..)| ImplicitPoint::Tpi(t)));
let dirs = [[1., 0., 0.], [0., 1., 0.], [0., 0., 1.], [1., 2., -1.], [-0.3, 1.7, 0.5]];
for u in dirs {
let ur = [
BigRational::from_float(u[0]).unwrap(),
BigRational::from_float(u[1]).unwrap(),
BigRational::from_float(u[2]).unwrap(),
];
for a in &pts {
for b in &pts {
let pa = rational::point_of(a);
let pb = rational::point_of(b);
let dot = (&pa[0] - &pb[0]) * &ur[0]
+ (&pa[1] - &pb[1]) * &ur[1]
+ (&pa[2] - &pb[2]) * &ur[2];
let want = if dot.is_negative() {
Sign::Negative
} else if dot.is_positive() {
Sign::Positive
} else {
Sign::Zero
};
assert_eq!(rational::cmp_along(a, b, u), want, "cmp_along != oracle (u={u:?})");
}
}
}
}
}