#![deny(clippy::cast_precision_loss)]
#![deny(clippy::lossy_float_literal)]
#![doc(test(no_crate_inject))]
#![doc(html_playground_url = "https://rgeometry.org/playground.html")]
#![doc(test(no_crate_inject))]
use num_traits::*;
use std::cmp::Ordering;
use std::iter::Sum;
use std::ops::*;
pub mod algorithms;
pub mod data;
mod intersection;
mod matrix;
mod orientation;
mod transformation;
mod utils;
pub use orientation::{Orientation, SoS};
pub use intersection::Intersects;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Error {
InsufficientVertices,
SelfIntersections,
DuplicatePoints,
ConvexViolation,
ClockWiseViolation,
CoLinearViolation,
}
impl std::fmt::Display for Error {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
match self {
Error::InsufficientVertices => write!(f, "Insufficient vertices"),
Error::SelfIntersections => write!(f, "Self intersections"),
Error::DuplicatePoints => write!(f, "Duplicate points"),
Error::ConvexViolation => write!(f, "Convex violation"),
Error::ClockWiseViolation => write!(f, "Clockwise violation"),
Error::CoLinearViolation => write!(
f,
"Two or more points are colinear and no valid solution exists"
),
}
}
}
pub trait TotalOrd {
fn total_cmp(&self, other: &Self) -> Ordering;
fn total_min(self, other: Self) -> Self
where
Self: Sized,
{
std::cmp::min_by(self, other, TotalOrd::total_cmp)
}
fn total_max(self, other: Self) -> Self
where
Self: Sized,
{
std::cmp::max_by(self, other, TotalOrd::total_cmp)
}
}
impl TotalOrd for u32 {
fn total_cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}
impl<A: TotalOrd> TotalOrd for &A {
fn total_cmp(&self, other: &Self) -> Ordering {
(*self).total_cmp(*other)
}
}
impl<A: TotalOrd, B: TotalOrd> TotalOrd for (A, B) {
fn total_cmp(&self, other: &Self) -> Ordering {
self
.0
.total_cmp(&other.0)
.then_with(|| self.1.total_cmp(&other.1))
}
}
pub trait PolygonScalar:
std::fmt::Debug
+ Neg<Output = Self>
+ NumAssignOps
+ NumOps<Self, Self>
+ TotalOrd
+ PartialOrd
+ Sum
+ Clone
{
fn from_constant(val: i8) -> Self;
fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
fn cmp_vector_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
fn cmp_perp_vector_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
fn cmp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering;
fn cmp_perp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering;
fn cmp_edge_normals_cross(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering;
fn cmp_edge_normals_dot(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering;
fn cmp_edge_normal_cross_direction(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
direction: &[Self; 2],
) -> std::cmp::Ordering;
fn approx_intersection_point(
p1: [Self; 2],
p2: [Self; 2],
q1: [Self; 2],
q2: [Self; 2],
) -> Option<[Self; 2]> {
approx_intersection_point_basic(Self::from_constant(0), p1, p2, q1, q2)
}
fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering;
}
fn approx_intersection_point_basic<T: Clone + NumOps<T, T> + PartialEq + std::fmt::Debug>(
zero: T,
p1: [T; 2],
p2: [T; 2],
q1: [T; 2],
q2: [T; 2],
) -> Option<[T; 2]> {
let [x1, y1] = p1;
let [x2, y2] = p2;
let [x3, y3] = q1;
let [x4, y4] = q2;
let denom: T = (x1.clone() - x2.clone()) * (y3.clone() - y4.clone())
- (y1.clone() - y2.clone()) * (x3.clone() - x4.clone());
if denom == zero {
return None;
}
let part_a = x1.clone() * y2.clone() - y1.clone() * x2.clone();
let part_b = x3.clone() * y4.clone() - y3.clone() * x4.clone();
let x_num =
part_a.clone() * (x3.clone() - x4.clone()) - (x1.clone() - x2.clone()) * part_b.clone();
let y_num = part_a * (y3.clone() - y4.clone()) - (y1.clone() - y2.clone()) * part_b;
Some([x_num / denom.clone(), y_num / denom])
}
fn incircle_exact<T>(a: &[T; 2], b: &[T; 2], c: &[T; 2], d: &[T; 2]) -> Ordering
where
T: Clone + NumOps<T, T> + Zero + Ord,
{
let ax = a[0].clone() - d[0].clone();
let ay = a[1].clone() - d[1].clone();
let bx = b[0].clone() - d[0].clone();
let by = b[1].clone() - d[1].clone();
let cx = c[0].clone() - d[0].clone();
let cy = c[1].clone() - d[1].clone();
let a_len = ax.clone() * ax.clone() + ay.clone() * ay.clone();
let b_len = bx.clone() * bx.clone() + by.clone() * by.clone();
let c_len = cx.clone() * cx.clone() + cy.clone() * cy.clone();
let det = det3_generic(&ax, &ay, &a_len, &bx, &by, &b_len, &cx, &cy, &c_len);
det.cmp(&T::zero())
}
#[allow(clippy::too_many_arguments)]
fn det3_generic<T>(a1: &T, a2: &T, a3: &T, b1: &T, b2: &T, b3: &T, c1: &T, c2: &T, c3: &T) -> T
where
T: Clone + NumOps<T, T>,
{
let m1 = b2.clone() * c3.clone() - b3.clone() * c2.clone();
let m2 = b1.clone() * c3.clone() - b3.clone() * c1.clone();
let m3 = b1.clone() * c2.clone() - b2.clone() * c1.clone();
a1.clone() * m1 - a2.clone() * m2 + a3.clone() * m3
}
macro_rules! fixed_precision {
( $ty:ty, $uty:ty, $long:ty, $ulong: ty, $apfp_mod:path ) => {
impl TotalOrd for $ty {
fn total_cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}
impl PolygonScalar for $ty {
fn from_constant(val: i8) -> Self {
val as $ty
}
fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
use $apfp_mod as apfp_mod;
apfp_mod::cmp_dist(
&apfp_mod::Coord::new(p[0], p[1]),
&apfp_mod::Coord::new(q[0], q[1]),
&apfp_mod::Coord::new(r[0], r[1]),
)
}
fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::Orientation;
use $apfp_mod as apfp_mod;
let orient = apfp_mod::orient2d(
&apfp_mod::Coord::new(p[0], p[1]),
&apfp_mod::Coord::new(q[0], q[1]),
&apfp_mod::Coord::new(r[0], r[1]),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::Orientation;
use $apfp_mod as apfp_mod;
let orient = apfp_mod::orient2d_vec(
&apfp_mod::Coord::new(p[0], p[1]),
&apfp_mod::Coord::new(vector[0], vector[1]),
&apfp_mod::Coord::new(q[0], q[1]),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_perp_vector_slope(
vector: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
use apfp::geometry::Orientation;
use $apfp_mod as apfp_mod;
let orient = apfp_mod::orient2d_normal(
&apfp_mod::Coord::new(p[0], p[1]),
&apfp_mod::Coord::new(vector[0], vector[1]),
&apfp_mod::Coord::new(q[0], q[1]),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let e0 = edge_start[0];
let e1 = edge_start[1];
let f0 = edge_end[0];
let f1 = edge_end[1];
let p0 = p[0];
let p1 = p[1];
let q0 = q[0];
let q1 = q[1];
let sign = apfp::int_signum!((p0 - q0) * (e0 - f0) - (p1 - q1) * (f1 - e1));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_perp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let e0 = edge_start[0];
let e1 = edge_start[1];
let f0 = edge_end[0];
let f1 = edge_end[1];
let p0 = p[0];
let p1 = p[1];
let q0 = q[0];
let q1 = q[1];
let sign = apfp::int_signum!((p0 - q0) * (f1 - e1) + (p1 - q1) * (e0 - f0));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_edge_normals_cross(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let re0 = ref_edge_start[0];
let re1 = ref_edge_start[1];
let rf0 = ref_edge_end[0];
let rf1 = ref_edge_end[1];
let e0 = edge_start[0];
let e1 = edge_start[1];
let f0 = edge_end[0];
let f1 = edge_end[1];
let sign = apfp::int_signum!((rf1 - re1) * (e0 - f0) - (re0 - rf0) * (f1 - e1));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_edge_normals_dot(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let re0 = ref_edge_start[0];
let re1 = ref_edge_start[1];
let rf0 = ref_edge_end[0];
let rf1 = ref_edge_end[1];
let e0 = edge_start[0];
let e1 = edge_start[1];
let f0 = edge_end[0];
let f1 = edge_end[1];
let sign = apfp::int_signum!((rf0 - re0) * (f0 - e0) + (rf1 - re1) * (f1 - e1));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_edge_normal_cross_direction(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
direction: &[Self; 2],
) -> std::cmp::Ordering {
let e0 = edge_start[0];
let e1 = edge_start[1];
let f0 = edge_end[0];
let f1 = edge_end[1];
let d0 = direction[0];
let d1 = direction[1];
let sign = apfp::int_signum!((f1 - e1) * d1 + (f0 - e0) * d0);
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn approx_intersection_point(
p1: [Self; 2],
p2: [Self; 2],
q1: [Self; 2],
q2: [Self; 2],
) -> Option<[Self; 2]> {
let long_p1: [$long; 2] = [p1[0] as $long, p1[1] as $long];
let long_p2: [$long; 2] = [p2[0] as $long, p2[1] as $long];
let long_q1: [$long; 2] = [q1[0] as $long, q1[1] as $long];
let long_q2: [$long; 2] = [q2[0] as $long, q2[1] as $long];
let long_result = approx_intersection_point_basic(0, long_p1, long_p2, long_q1, long_q2);
long_result.and_then(|[x, y]| Some([Self::try_from(x).ok()?, Self::try_from(y).ok()?]))
}
fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering {
use apfp::geometry::Orientation;
use $apfp_mod as apfp_mod;
let result = apfp_mod::incircle(
&apfp_mod::Coord::new(a[0], a[1]),
&apfp_mod::Coord::new(b[0], b[1]),
&apfp_mod::Coord::new(c[0], c[1]),
&apfp_mod::Coord::new(d[0], d[1]),
);
match result {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
}
};
}
macro_rules! arbitrary_precision {
( $( $ty:ty ),* ) => {
$(
impl TotalOrd for $ty {
fn total_cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}
impl PolygonScalar for $ty {
fn from_constant(val: i8) -> Self {
<$ty>::from_i8(val).unwrap()
}
fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
let pq_x = &p[0] - &q[0];
let pq_y = &p[1] - &q[1];
let pq_dist_squared: Self = &pq_x*&pq_x + &pq_y*&pq_y;
let pr_x = &p[0] - &r[0];
let pr_y = &p[1] - &r[1];
let pr_dist_squared: Self = &pr_x*&pr_x + &pr_y*&pr_y;
pq_dist_squared.cmp(&pr_dist_squared)
}
fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
let slope1 = (&r[1] - &q[1]) * (&q[0] - &p[0]);
let slope2 = (&q[1] - &p[1]) * (&r[0] - &q[0]);
slope1.cmp(&slope2)
}
fn cmp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
PolygonScalar::cmp_slope(
p,
&[&p[0] + &vector[0], &p[1] + &vector[1]],
q
)
}
fn cmp_perp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
PolygonScalar::cmp_slope(
p,
&[&p[0] - &vector[1], &p[1] + &vector[0]],
q
)
}
fn cmp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let edge_normal = [
&edge_end[1] - &edge_start[1],
&edge_start[0] - &edge_end[0],
];
PolygonScalar::cmp_vector_slope(&edge_normal, p, q)
}
fn cmp_perp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let edge_normal = [
&edge_end[1] - &edge_start[1],
&edge_start[0] - &edge_end[0],
];
PolygonScalar::cmp_perp_vector_slope(&edge_normal, p, q)
}
fn cmp_edge_normals_cross(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let ref_nx = &ref_edge_end[1] - &ref_edge_start[1];
let ref_ny = &ref_edge_start[0] - &ref_edge_end[0];
let edge_nx = &edge_end[1] - &edge_start[1];
let edge_ny = &edge_start[0] - &edge_end[0];
let cross = &ref_nx * &edge_ny - &ref_ny * &edge_nx;
let zero = Self::from_constant(0);
cross.cmp(&zero)
}
fn cmp_edge_normals_dot(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let dot = (&ref_edge_end[0] - &ref_edge_start[0]) * (&edge_end[0] - &edge_start[0])
+ (&ref_edge_end[1] - &ref_edge_start[1]) * (&edge_end[1] - &edge_start[1]);
let zero = Self::from_constant(0);
dot.cmp(&zero)
}
fn cmp_edge_normal_cross_direction(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
direction: &[Self; 2],
) -> std::cmp::Ordering {
let dot = (&edge_end[0] - &edge_start[0]) * &direction[0]
+ (&edge_end[1] - &edge_start[1]) * &direction[1];
let zero = Self::from_constant(0);
dot.cmp(&zero)
}
fn incircle(
a: &[Self; 2],
b: &[Self; 2],
c: &[Self; 2],
d: &[Self; 2],
) -> Ordering {
incircle_exact(a, b, c, d)
}
})*
};
}
macro_rules! floating_precision {
( $( $ty:ty ),* ) => {
$(
impl TotalOrd for $ty {
fn total_cmp(&self, other: &Self) -> Ordering {
if *self == 0.0 && *other == 0.0 {
Ordering::Equal
} else {
<$ty>::total_cmp(self, other)
}
}
}
impl PolygonScalar for $ty {
fn from_constant(val: i8) -> Self {
<$ty>::from_i8(val).unwrap()
}
fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
PolygonScalar::cmp_dist(
&[float_to_rational(p[0]), float_to_rational(p[1])],
&[float_to_rational(q[0]), float_to_rational(q[1])],
&[float_to_rational(r[0]), float_to_rational(r[1])],
)
}
fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::{Orientation, f64::Coord};
let orient = apfp::geometry::f64::orient2d(
&Coord::new(p[0] as f64, p[1] as f64),
&Coord::new(q[0] as f64, q[1] as f64),
&Coord::new(r[0] as f64, r[1] as f64),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::{Orientation, f64::Coord};
let orient = apfp::geometry::f64::orient2d_vec(
&Coord::new(p[0] as f64, p[1] as f64),
&Coord::new(vector[0] as f64, vector[1] as f64),
&Coord::new(q[0] as f64, q[1] as f64),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_perp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::{Orientation, f64::Coord};
let orient = apfp::geometry::f64::orient2d_normal(
&Coord::new(p[0] as f64, p[1] as f64),
&Coord::new(vector[0] as f64, vector[1] as f64),
&Coord::new(q[0] as f64, q[1] as f64),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let px = p[0] as f64;
let py = p[1] as f64;
let qx = q[0] as f64;
let qy = q[1] as f64;
let es0 = edge_start[0] as f64;
let es1 = edge_start[1] as f64;
let ee0 = edge_end[0] as f64;
let ee1 = edge_end[1] as f64;
match apfp::apfp_signum!(
(px - qx) * (es0 - ee0) - (py - qy) * (ee1 - es1)
) {
1 => Ordering::Greater,
-1 => Ordering::Less,
_ => Ordering::Equal,
}
}
fn cmp_perp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let px = p[0] as f64;
let py = p[1] as f64;
let qx = q[0] as f64;
let qy = q[1] as f64;
let es0 = edge_start[0] as f64;
let es1 = edge_start[1] as f64;
let ee0 = edge_end[0] as f64;
let ee1 = edge_end[1] as f64;
match apfp::apfp_signum!(
(px - qx) * (ee1 - es1) + (py - qy) * (es0 - ee0)
) {
1 => Ordering::Greater,
-1 => Ordering::Less,
_ => Ordering::Equal,
}
}
fn cmp_edge_normals_cross(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let ref_sx = ref_edge_start[0] as f64;
let ref_sy = ref_edge_start[1] as f64;
let ref_ex = ref_edge_end[0] as f64;
let ref_ey = ref_edge_end[1] as f64;
let edge_sx = edge_start[0] as f64;
let edge_sy = edge_start[1] as f64;
let edge_ex = edge_end[0] as f64;
let edge_ey = edge_end[1] as f64;
match apfp::apfp_signum!(
(ref_ey - ref_sy) * (edge_sx - edge_ex) - (ref_sx - ref_ex) * (edge_ey - edge_sy)
) {
1 => Ordering::Greater,
-1 => Ordering::Less,
_ => Ordering::Equal,
}
}
fn cmp_edge_normals_dot(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let ref_sx = ref_edge_start[0] as f64;
let ref_sy = ref_edge_start[1] as f64;
let ref_ex = ref_edge_end[0] as f64;
let ref_ey = ref_edge_end[1] as f64;
let edge_sx = edge_start[0] as f64;
let edge_sy = edge_start[1] as f64;
let edge_ex = edge_end[0] as f64;
let edge_ey = edge_end[1] as f64;
match apfp::apfp_signum!(
(ref_ex - ref_sx) * (edge_ex - edge_sx) + (ref_ey - ref_sy) * (edge_ey - edge_sy)
) {
1 => Ordering::Greater,
-1 => Ordering::Less,
_ => Ordering::Equal,
}
}
fn cmp_edge_normal_cross_direction(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
direction: &[Self; 2],
) -> std::cmp::Ordering {
let edge_sx = edge_start[0] as f64;
let edge_sy = edge_start[1] as f64;
let edge_ex = edge_end[0] as f64;
let edge_ey = edge_end[1] as f64;
let dir_x = direction[0] as f64;
let dir_y = direction[1] as f64;
match apfp::apfp_signum!(
(edge_ex - edge_sx) * dir_x + (edge_ey - edge_sy) * dir_y
) {
1 => Ordering::Greater,
-1 => Ordering::Less,
_ => Ordering::Equal,
}
}
fn incircle(
a: &[Self; 2],
b: &[Self; 2],
c: &[Self; 2],
d: &[Self; 2],
) -> Ordering {
use apfp::geometry::{Orientation, f64::Coord};
let result = apfp::geometry::f64::incircle(
&Coord::new(a[0] as f64, a[1] as f64),
&Coord::new(b[0] as f64, b[1] as f64),
&Coord::new(c[0] as f64, c[1] as f64),
&Coord::new(d[0] as f64, d[1] as f64),
);
match result {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
})*
};
}
fixed_precision!(i8, u8, i16, u16, apfp::geometry::i8);
fixed_precision!(i16, u16, i32, u32, apfp::geometry::i16);
fixed_precision!(i32, u32, i64, u64, apfp::geometry::i32);
fixed_precision!(i64, u64, i128, u128, apfp::geometry::i64);
impl TotalOrd for isize {
fn total_cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}
impl PolygonScalar for isize {
fn from_constant(val: i8) -> Self {
val as isize
}
fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::i64 as apfp_mod;
apfp_mod::cmp_dist(
&apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
&apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
&apfp_mod::Coord::new(r[0] as i64, r[1] as i64),
)
}
fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::{Orientation, i64 as apfp_mod};
let orient = apfp_mod::orient2d(
&apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
&apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
&apfp_mod::Coord::new(r[0] as i64, r[1] as i64),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::{Orientation, i64 as apfp_mod};
let orient = apfp_mod::orient2d_vec(
&apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
&apfp_mod::Coord::new(vector[0] as i64, vector[1] as i64),
&apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_perp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
use apfp::geometry::{Orientation, i64 as apfp_mod};
let orient = apfp_mod::orient2d_normal(
&apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
&apfp_mod::Coord::new(vector[0] as i64, vector[1] as i64),
&apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
);
match orient {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
fn cmp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
let (p0, p1) = (p[0] as i64, p[1] as i64);
let (q0, q1) = (q[0] as i64, q[1] as i64);
let sign = apfp::int_signum!((p0 - q0) * (e0 - f0) - (p1 - q1) * (f1 - e1));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_perp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
let (p0, p1) = (p[0] as i64, p[1] as i64);
let (q0, q1) = (q[0] as i64, q[1] as i64);
let sign = apfp::int_signum!((p0 - q0) * (f1 - e1) + (p1 - q1) * (e0 - f0));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_edge_normals_cross(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let (re0, re1) = (ref_edge_start[0] as i64, ref_edge_start[1] as i64);
let (rf0, rf1) = (ref_edge_end[0] as i64, ref_edge_end[1] as i64);
let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
let sign = apfp::int_signum!((rf1 - re1) * (e0 - f0) - (re0 - rf0) * (f1 - e1));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_edge_normals_dot(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let (re0, re1) = (ref_edge_start[0] as i64, ref_edge_start[1] as i64);
let (rf0, rf1) = (ref_edge_end[0] as i64, ref_edge_end[1] as i64);
let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
let sign = apfp::int_signum!((rf0 - re0) * (f0 - e0) + (rf1 - re1) * (f1 - e1));
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn cmp_edge_normal_cross_direction(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
direction: &[Self; 2],
) -> std::cmp::Ordering {
let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
let (d0, d1) = (direction[0] as i64, direction[1] as i64);
let sign = apfp::int_signum!((f1 - e1) * d1 + (f0 - e0) * d0);
match sign {
1 => Ordering::Greater,
-1 => Ordering::Less,
0 => Ordering::Equal,
_ => unreachable!(),
}
}
fn approx_intersection_point(
p1: [Self; 2],
p2: [Self; 2],
q1: [Self; 2],
q2: [Self; 2],
) -> Option<[Self; 2]> {
let long_p1: [i128; 2] = [p1[0] as i128, p1[1] as i128];
let long_p2: [i128; 2] = [p2[0] as i128, p2[1] as i128];
let long_q1: [i128; 2] = [q1[0] as i128, q1[1] as i128];
let long_q2: [i128; 2] = [q2[0] as i128, q2[1] as i128];
let long_result = approx_intersection_point_basic(0, long_p1, long_p2, long_q1, long_q2);
long_result.and_then(|[x, y]| Some([Self::try_from(x).ok()?, Self::try_from(y).ok()?]))
}
fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering {
use apfp::geometry::{Orientation, i64 as apfp_mod};
let result = apfp_mod::incircle(
&apfp_mod::Coord::new(a[0] as i64, a[1] as i64),
&apfp_mod::Coord::new(b[0] as i64, b[1] as i64),
&apfp_mod::Coord::new(c[0] as i64, c[1] as i64),
&apfp_mod::Coord::new(d[0] as i64, d[1] as i64),
);
match result {
Orientation::CounterClockwise => Ordering::Greater,
Orientation::Clockwise => Ordering::Less,
Orientation::CoLinear => Ordering::Equal,
}
}
}
arbitrary_precision!(num_bigint::BigInt);
arbitrary_precision!(num_rational::BigRational);
floating_precision!(f32);
floating_precision!(f64);
#[cfg(feature = "rug")]
impl TotalOrd for rug::Integer {
fn total_cmp(&self, other: &Self) -> Ordering {
self.cmp(other)
}
}
#[cfg(feature = "rug")]
impl PolygonScalar for rug::Integer {
fn from_constant(val: i8) -> Self {
rug::Integer::from(val)
}
fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
let [qx, qy] = q.clone();
let [px, py] = p.clone();
let pq_x = px - qx;
let pq_y = py - qy;
let pq_dist_squared: Self = pq_x.square() + pq_y.square();
let [rx, ry] = r.clone();
let [px, py] = p.clone();
let pr_x = px - rx;
let pr_y = py - ry;
let pr_dist_squared: Self = pr_x.square() + pr_y.square();
pq_dist_squared.cmp(&pr_dist_squared)
}
fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
let [qx, qy] = q.clone();
let [rx, ry] = r.clone();
let ry_qy = ry - &q[1];
let qx_px = qx - &p[0];
let slope1 = ry_qy * qx_px;
let qy_py = qy - &p[1];
let rx_qx = rx - &q[0];
let slope2 = qy_py * rx_qx;
slope1.cmp(&slope2)
}
fn cmp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
let new_x = rug::Integer::from(&p[0] + &vector[0]);
let new_y = rug::Integer::from(&p[1] + &vector[1]);
PolygonScalar::cmp_slope(p, &[new_x, new_y], q)
}
fn cmp_perp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
let new_x = rug::Integer::from(&p[0] - &vector[1]);
let new_y = rug::Integer::from(&p[1] + &vector[0]);
PolygonScalar::cmp_slope(p, &[new_x, new_y], q)
}
fn cmp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let edge_normal = [
rug::Integer::from(&edge_end[1] - &edge_start[1]),
rug::Integer::from(&edge_start[0] - &edge_end[0]),
];
PolygonScalar::cmp_vector_slope(&edge_normal, p, q)
}
fn cmp_perp_edge_normal_slope(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
p: &[Self; 2],
q: &[Self; 2],
) -> std::cmp::Ordering {
let edge_normal = [
rug::Integer::from(&edge_end[1] - &edge_start[1]),
rug::Integer::from(&edge_start[0] - &edge_end[0]),
];
PolygonScalar::cmp_perp_vector_slope(&edge_normal, p, q)
}
fn cmp_edge_normals_cross(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let ref_nx = rug::Integer::from(&ref_edge_end[1] - &ref_edge_start[1]);
let ref_ny = rug::Integer::from(&ref_edge_start[0] - &ref_edge_end[0]);
let edge_nx = rug::Integer::from(&edge_end[1] - &edge_start[1]);
let edge_ny = rug::Integer::from(&edge_start[0] - &edge_end[0]);
let cross = ref_nx * edge_ny - ref_ny * edge_nx;
cross.cmp(&rug::Integer::new())
}
fn cmp_edge_normals_dot(
ref_edge_start: &[Self; 2],
ref_edge_end: &[Self; 2],
edge_start: &[Self; 2],
edge_end: &[Self; 2],
) -> std::cmp::Ordering {
let dot = rug::Integer::from(&ref_edge_end[0] - &ref_edge_start[0])
* rug::Integer::from(&edge_end[0] - &edge_start[0])
+ rug::Integer::from(&ref_edge_end[1] - &ref_edge_start[1])
* rug::Integer::from(&edge_end[1] - &edge_start[1]);
dot.cmp(&rug::Integer::new())
}
fn cmp_edge_normal_cross_direction(
edge_start: &[Self; 2],
edge_end: &[Self; 2],
direction: &[Self; 2],
) -> std::cmp::Ordering {
let dot = rug::Integer::from(&edge_end[0] - &edge_start[0]) * &direction[0]
+ rug::Integer::from(&edge_end[1] - &edge_start[1]) * &direction[1];
dot.cmp(&rug::Integer::new())
}
fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering {
let mut ax = a[0].clone();
ax -= &d[0];
let mut ay = a[1].clone();
ay -= &d[1];
let mut bx = b[0].clone();
bx -= &d[0];
let mut by = b[1].clone();
by -= &d[1];
let mut cx = c[0].clone();
cx -= &d[0];
let mut cy = c[1].clone();
cy -= &d[1];
let a_len = ax.clone() * ax.clone() + ay.clone() * ay.clone();
let b_len = bx.clone() * bx.clone() + by.clone() * by.clone();
let c_len = cx.clone() * cx.clone() + cy.clone() * cy.clone();
let det = det3_generic(&ax, &ay, &a_len, &bx, &by, &b_len, &cx, &cy, &c_len);
det.cmp(&rug::Integer::new())
}
}
fn float_to_rational(f: impl num::traits::float::FloatCore) -> num::BigRational {
num::BigRational::from_float(f).expect("cannot convert NaN or infinite to exact precision number")
}
#[cfg(test)]
pub mod testing;
#[cfg(test)]
mod floating_robustness_tests {
use super::*;
fn to_big(f: f64) -> num_rational::BigRational {
num_rational::BigRational::from_float(f).unwrap()
}
#[test]
fn cmp_edge_normals_cross_f64_robustness() {
let big = 1e15f64;
let ref_start = [0.0f64, 0.0];
let ref_end = [big, big + 1.0];
let edge_start = [0.0f64, 0.0];
let edge_end = [big + 2.0, big + 3.0];
let ref_start_big = [to_big(ref_start[0]), to_big(ref_start[1])];
let ref_end_big = [to_big(ref_end[0]), to_big(ref_end[1])];
let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
let expected = num_rational::BigRational::cmp_edge_normals_cross(
&ref_start_big,
&ref_end_big,
&edge_start_big,
&edge_end_big,
);
assert_eq!(
expected,
Ordering::Less,
"BigRational should give Less (cross = -2)"
);
let result = f64::cmp_edge_normals_cross(&ref_start, &ref_end, &edge_start, &edge_end);
assert_eq!(
result, expected,
"f64 cmp_edge_normals_cross gave {:?} but BigRational gave {:?}. \
This demonstrates precision loss: true cross is -2, but naive f64 gives 0.",
result, expected
);
}
#[test]
fn cmp_edge_normals_dot_f64_robustness() {
let big = 1e15f64;
let ref_start = [0.0f64, 0.0];
let ref_end = [big, 1.0];
let edge_start = [0.0f64, 0.0];
let edge_end = [-1.0, big + 0.5];
let ref_start_big = [to_big(ref_start[0]), to_big(ref_start[1])];
let ref_end_big = [to_big(ref_end[0]), to_big(ref_end[1])];
let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
let expected = num_rational::BigRational::cmp_edge_normals_dot(
&ref_start_big,
&ref_end_big,
&edge_start_big,
&edge_end_big,
);
assert_eq!(expected, Ordering::Greater);
let result = f64::cmp_edge_normals_dot(&ref_start, &ref_end, &edge_start, &edge_end);
assert_eq!(result, expected);
}
#[test]
fn cmp_edge_normal_cross_direction_f64_robustness() {
let big = 1e15f64;
let edge_start = [0.0f64, 0.0];
let edge_end = [big, 1.0];
let direction = [-1.0f64, big + 0.5];
let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
let direction_big = [to_big(direction[0]), to_big(direction[1])];
let expected = num_rational::BigRational::cmp_edge_normal_cross_direction(
&edge_start_big,
&edge_end_big,
&direction_big,
);
assert_eq!(
expected,
Ordering::Greater,
"BigRational should give Greater (result = 0.5)"
);
let result = f64::cmp_edge_normal_cross_direction(&edge_start, &edge_end, &direction);
assert_eq!(
result, expected,
"f64 cmp_edge_normal_cross_direction gave {:?} but BigRational gave {:?}",
result, expected
);
}
#[test]
fn cmp_edge_normal_slope_f64_mismatch() {
let edge_start = [0.1f64, 0.2];
let edge_end = [0.5f64, 0.1];
let p = [1125899906842624.0f64, 4503599627370496.0];
let q = [0.0f64, 0.0];
let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
let p_big = [to_big(p[0]), to_big(p[1])];
let q_big = [to_big(q[0]), to_big(q[1])];
let expected = num_rational::BigRational::cmp_edge_normal_slope(
&edge_start_big,
&edge_end_big,
&p_big,
&q_big,
);
assert_eq!(
expected,
Ordering::Greater,
"BigRational should give Greater for this configuration"
);
let result = f64::cmp_edge_normal_slope(&edge_start, &edge_end, &p, &q);
assert_eq!(
result, expected,
"f64 cmp_edge_normal_slope gave {:?} but BigRational gave {:?}",
result, expected
);
}
#[test]
fn cmp_perp_edge_normal_slope_f64_mismatch() {
let edge_start = [0.1f64, 0.1];
let edge_end = [0.2f64, 0.5];
let p = [1125899906842624.0f64, 4503599627370496.0];
let q = [0.0f64, 0.0];
let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
let p_big = [to_big(p[0]), to_big(p[1])];
let q_big = [to_big(q[0]), to_big(q[1])];
let expected = num_rational::BigRational::cmp_perp_edge_normal_slope(
&edge_start_big,
&edge_end_big,
&p_big,
&q_big,
);
assert_eq!(
expected,
Ordering::Less,
"BigRational should give Less for this configuration"
);
let result = f64::cmp_perp_edge_normal_slope(&edge_start, &edge_end, &p, &q);
assert_eq!(
result, expected,
"f64 cmp_perp_edge_normal_slope gave {:?} but BigRational gave {:?}",
result, expected
);
}
}