rgeometry/
lib.rs

1// #![deny(warnings)]
2#![deny(clippy::cast_precision_loss)]
3#![deny(clippy::lossy_float_literal)]
4#![doc(test(no_crate_inject))]
5#![doc(html_playground_url = "https://rgeometry.org/playground.html")]
6#![doc(test(no_crate_inject))]
7//! # Computational Geometry
8//!
9//! RGeometry provides data types (points, polygons, lines, segments) and algorithms for
10//! computational geometry. Computational geometry is the study of algorithms for solving
11//! geometric problems, such as computing convex hulls, triangulating polygons, finding
12//! intersections, and determining visibility.
13//!
14//! This crate includes algorithms for:
15//! - **Convex hulls**: Graham scan, gift wrapping, and Melkman's algorithm
16//! - **Triangulation**: Ear clipping for simple polygons and constrained Delaunay triangulation
17//! - **Polygonization**: Converting point sets to polygons (monotone, star-shaped, two-opt)
18//! - **Line segment intersection**: Naive and Bentley-Ottmann sweep line algorithms
19//! - **Visibility**: Computing visibility polygons
20//! - **Spatial indexing**: Z-order curve hashing
21//!
22//! # Demos
23//!
24//! Interactive visualizations are available:
25//!
26//! - [Convex Hull](../../../convex_hull.html) - Graham scan algorithm
27//! - [Melkman's Algorithm](../../../melkman.html) - Online convex hull for simple polylines
28//! - [Ear Clipping](../../../earclip.html) - Polygon triangulation
29//! - [Constrained Delaunay](../../../delaunay.html) - Ear-clipping plus Lawson flips
30//! - [Two-Opt](../../../two_opt.html) - Polygonization heuristic
31//! - [Random Convex](../../../random_convex.html) - Convex polygon generation
32//! - [Random Monotone](../../../random_monotone.html) - Monotone polygon generation
33//!
34//! # Caveats
35//!
36//! RGeometry supports multiple numeric types, each with different trade-offs:
37//!
38//! - **Arbitrary precision** (e.g., `num::BigRational`): Always produces mathematically correct
39//!   results but can be significantly slower due to dynamic memory allocation and arbitrary
40//!   precision arithmetic.
41//!
42//! - **Fixed precision integers** (e.g., `i32`, `i64`): RGeometry guarantees that intermediate
43//!   calculations will not overflow or underflow, but final outputs may not be representable in
44//!   the chosen type. For example, testing if two line segments overlap is always safe, but
45//!   computing where they intersect may produce a point whose coordinates cannot be represented.
46//!
47//! - **Floating point** (e.g., `f32`, `f64`): RGeometry uses robust geometric predicates to
48//!   ensure accumulated rounding errors do not affect algorithmic results. However, individual
49//!   coordinate values are still subject to floating-point precision limits.
50
51use num_traits::*;
52use std::cmp::Ordering;
53use std::iter::Sum;
54use std::ops::*;
55
56pub mod algorithms;
57pub mod data;
58mod intersection;
59mod matrix;
60mod orientation;
61mod transformation;
62mod utils;
63
64pub use orientation::{Orientation, SoS};
65
66pub use intersection::Intersects;
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq)]
69pub enum Error {
70  InsufficientVertices,
71  SelfIntersections,
72  DuplicatePoints,
73  /// Two consecutive line segments are either colinear or oriented clockwise.
74  ConvexViolation,
75  ClockWiseViolation,
76  CoLinearViolation,
77}
78
79impl std::fmt::Display for Error {
80  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
81    match self {
82      Error::InsufficientVertices => write!(f, "Insufficient vertices"),
83      Error::SelfIntersections => write!(f, "Self intersections"),
84      Error::DuplicatePoints => write!(f, "Duplicate points"),
85      Error::ConvexViolation => write!(f, "Convex violation"),
86      Error::ClockWiseViolation => write!(f, "Clockwise violation"),
87      Error::CoLinearViolation => write!(
88        f,
89        "Two or more points are colinear and no valid solution exists"
90      ),
91    }
92  }
93}
94
95pub trait TotalOrd {
96  fn total_cmp(&self, other: &Self) -> Ordering;
97
98  fn total_min(self, other: Self) -> Self
99  where
100    Self: Sized,
101  {
102    std::cmp::min_by(self, other, TotalOrd::total_cmp)
103  }
104
105  fn total_max(self, other: Self) -> Self
106  where
107    Self: Sized,
108  {
109    std::cmp::max_by(self, other, TotalOrd::total_cmp)
110  }
111}
112
113impl TotalOrd for u32 {
114  fn total_cmp(&self, other: &Self) -> Ordering {
115    self.cmp(other)
116  }
117}
118
119impl<A: TotalOrd> TotalOrd for &A {
120  fn total_cmp(&self, other: &Self) -> Ordering {
121    (*self).total_cmp(*other)
122  }
123}
124
125impl<A: TotalOrd, B: TotalOrd> TotalOrd for (A, B) {
126  fn total_cmp(&self, other: &Self) -> Ordering {
127    self
128      .0
129      .total_cmp(&other.0)
130      .then_with(|| self.1.total_cmp(&other.1))
131  }
132}
133
134// FIXME: Should include ZHashable.
135pub trait PolygonScalar:
136  std::fmt::Debug
137  + Neg<Output = Self>
138  + NumAssignOps
139  + NumOps<Self, Self>
140  + TotalOrd
141  + PartialOrd
142  + Sum
143  + Clone
144{
145  fn from_constant(val: i8) -> Self;
146  fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
147  fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
148  fn cmp_vector_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
149  fn cmp_perp_vector_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering;
150
151  /// Compute orientation using an edge's outward normal as the direction vector.
152  ///
153  /// This is equivalent to `cmp_vector_slope(&[edge_end[1] - edge_start[1], edge_start[0] - edge_end[0]], p, q)`
154  /// but avoids overflow that would occur from computing the normal explicitly.
155  ///
156  /// The outward normal of edge (edge_start → edge_end) for a CCW polygon is the edge vector
157  /// rotated 90° clockwise: (dy, -dx) where (dx, dy) = edge_end - edge_start.
158  fn cmp_edge_normal_slope(
159    edge_start: &[Self; 2],
160    edge_end: &[Self; 2],
161    p: &[Self; 2],
162    q: &[Self; 2],
163  ) -> std::cmp::Ordering;
164
165  /// Compute orientation using the perpendicular of an edge's outward normal.
166  ///
167  /// This is equivalent to `cmp_perp_vector_slope(&[edge_end[1] - edge_start[1], edge_start[0] - edge_end[0]], p, q)`
168  /// but avoids overflow.
169  fn cmp_perp_edge_normal_slope(
170    edge_start: &[Self; 2],
171    edge_end: &[Self; 2],
172    p: &[Self; 2],
173    q: &[Self; 2],
174  ) -> std::cmp::Ordering;
175
176  /// Compare the cross product of two edge normals.
177  ///
178  /// Returns the sign of: normal1 × normal2
179  /// where normal1 = outward normal of (ref_edge_start → ref_edge_end)
180  /// and normal2 = outward normal of (edge_start → edge_end)
181  ///
182  /// This is mathematically equivalent to the cross product of the edge vectors,
183  /// avoiding the need to compute the normals explicitly.
184  fn cmp_edge_normals_cross(
185    ref_edge_start: &[Self; 2],
186    ref_edge_end: &[Self; 2],
187    edge_start: &[Self; 2],
188    edge_end: &[Self; 2],
189  ) -> std::cmp::Ordering;
190
191  /// Compare the dot product of two edge normals against zero.
192  ///
193  /// Returns the sign of: normal1 · normal2
194  /// where normal1 = outward normal of (ref_edge_start → ref_edge_end)
195  /// and normal2 = outward normal of (edge_start → edge_end)
196  ///
197  /// This is mathematically equivalent to the dot product of the edge vectors,
198  /// avoiding the need to compute the normals explicitly.
199  fn cmp_edge_normals_dot(
200    ref_edge_start: &[Self; 2],
201    ref_edge_end: &[Self; 2],
202    edge_start: &[Self; 2],
203    edge_end: &[Self; 2],
204  ) -> std::cmp::Ordering;
205
206  /// Compare the cross product of an edge's outward normal with a direction vector.
207  ///
208  /// Returns the sign of: edge_normal × direction
209  /// This equals the dot product of (edge_end - edge_start) · direction.
210  fn cmp_edge_normal_cross_direction(
211    edge_start: &[Self; 2],
212    edge_end: &[Self; 2],
213    direction: &[Self; 2],
214  ) -> std::cmp::Ordering;
215
216  fn approx_intersection_point(
217    p1: [Self; 2],
218    p2: [Self; 2],
219    q1: [Self; 2],
220    q2: [Self; 2],
221  ) -> Option<[Self; 2]> {
222    approx_intersection_point_basic(Self::from_constant(0), p1, p2, q1, q2)
223  }
224  fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering;
225}
226
227fn approx_intersection_point_basic<T: Clone + NumOps<T, T> + PartialEq + std::fmt::Debug>(
228  zero: T,
229  p1: [T; 2],
230  p2: [T; 2],
231  q1: [T; 2],
232  q2: [T; 2],
233) -> Option<[T; 2]> {
234  let [x1, y1] = p1;
235  let [x2, y2] = p2;
236  let [x3, y3] = q1;
237  let [x4, y4] = q2;
238  let denom: T = (x1.clone() - x2.clone()) * (y3.clone() - y4.clone())
239    - (y1.clone() - y2.clone()) * (x3.clone() - x4.clone());
240  if denom == zero {
241    return None;
242  }
243  let part_a = x1.clone() * y2.clone() - y1.clone() * x2.clone();
244  let part_b = x3.clone() * y4.clone() - y3.clone() * x4.clone();
245  let x_num =
246    part_a.clone() * (x3.clone() - x4.clone()) - (x1.clone() - x2.clone()) * part_b.clone();
247  let y_num = part_a * (y3.clone() - y4.clone()) - (y1.clone() - y2.clone()) * part_b;
248  Some([x_num / denom.clone(), y_num / denom])
249}
250
251fn incircle_exact<T>(a: &[T; 2], b: &[T; 2], c: &[T; 2], d: &[T; 2]) -> Ordering
252where
253  T: Clone + NumOps<T, T> + Zero + Ord,
254{
255  let ax = a[0].clone() - d[0].clone();
256  let ay = a[1].clone() - d[1].clone();
257  let bx = b[0].clone() - d[0].clone();
258  let by = b[1].clone() - d[1].clone();
259  let cx = c[0].clone() - d[0].clone();
260  let cy = c[1].clone() - d[1].clone();
261
262  let a_len = ax.clone() * ax.clone() + ay.clone() * ay.clone();
263  let b_len = bx.clone() * bx.clone() + by.clone() * by.clone();
264  let c_len = cx.clone() * cx.clone() + cy.clone() * cy.clone();
265
266  let det = det3_generic(&ax, &ay, &a_len, &bx, &by, &b_len, &cx, &cy, &c_len);
267  det.cmp(&T::zero())
268}
269
270#[allow(clippy::too_many_arguments)]
271fn det3_generic<T>(a1: &T, a2: &T, a3: &T, b1: &T, b2: &T, b3: &T, c1: &T, c2: &T, c3: &T) -> T
272where
273  T: Clone + NumOps<T, T>,
274{
275  let m1 = b2.clone() * c3.clone() - b3.clone() * c2.clone();
276  let m2 = b1.clone() * c3.clone() - b3.clone() * c1.clone();
277  let m3 = b1.clone() * c2.clone() - b2.clone() * c1.clone();
278  a1.clone() * m1 - a2.clone() * m2 + a3.clone() * m3
279}
280
281macro_rules! fixed_precision {
282  ( $ty:ty, $uty:ty, $long:ty, $ulong: ty, $apfp_mod:path ) => {
283    impl TotalOrd for $ty {
284      fn total_cmp(&self, other: &Self) -> Ordering {
285        self.cmp(other)
286      }
287    }
288
289    impl PolygonScalar for $ty {
290      fn from_constant(val: i8) -> Self {
291        val as $ty
292      }
293      fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
294        use $apfp_mod as apfp_mod;
295        apfp_mod::cmp_dist(
296          &apfp_mod::Coord::new(p[0], p[1]),
297          &apfp_mod::Coord::new(q[0], q[1]),
298          &apfp_mod::Coord::new(r[0], r[1]),
299        )
300      }
301
302      fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
303        use apfp::geometry::Orientation;
304        use $apfp_mod as apfp_mod;
305        let orient = apfp_mod::orient2d(
306          &apfp_mod::Coord::new(p[0], p[1]),
307          &apfp_mod::Coord::new(q[0], q[1]),
308          &apfp_mod::Coord::new(r[0], r[1]),
309        );
310        match orient {
311          Orientation::CounterClockwise => Ordering::Greater,
312          Orientation::Clockwise => Ordering::Less,
313          Orientation::CoLinear => Ordering::Equal,
314        }
315      }
316
317      fn cmp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
318        use apfp::geometry::Orientation;
319        use $apfp_mod as apfp_mod;
320        let orient = apfp_mod::orient2d_vec(
321          &apfp_mod::Coord::new(p[0], p[1]),
322          &apfp_mod::Coord::new(vector[0], vector[1]),
323          &apfp_mod::Coord::new(q[0], q[1]),
324        );
325        match orient {
326          Orientation::CounterClockwise => Ordering::Greater,
327          Orientation::Clockwise => Ordering::Less,
328          Orientation::CoLinear => Ordering::Equal,
329        }
330      }
331
332      fn cmp_perp_vector_slope(
333        vector: &[Self; 2],
334        p: &[Self; 2],
335        q: &[Self; 2],
336      ) -> std::cmp::Ordering {
337        use apfp::geometry::Orientation;
338        use $apfp_mod as apfp_mod;
339        let orient = apfp_mod::orient2d_normal(
340          &apfp_mod::Coord::new(p[0], p[1]),
341          &apfp_mod::Coord::new(vector[0], vector[1]),
342          &apfp_mod::Coord::new(q[0], q[1]),
343        );
344        match orient {
345          Orientation::CounterClockwise => Ordering::Greater,
346          Orientation::Clockwise => Ordering::Less,
347          Orientation::CoLinear => Ordering::Equal,
348        }
349      }
350
351      fn cmp_edge_normal_slope(
352        edge_start: &[Self; 2],
353        edge_end: &[Self; 2],
354        p: &[Self; 2],
355        q: &[Self; 2],
356      ) -> std::cmp::Ordering {
357        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
358        // We want: cmp_vector_slope(edge_normal, p, q)
359        // Which computes orient2d_vec(p, edge_normal, q):
360        //   sign((p[0] - q[0]) * edge_normal[1] - (p[1] - q[1]) * edge_normal[0])
361        //   = sign((p[0] - q[0]) * (edge_start[0] - edge_end[0]) - (p[1] - q[1]) * (edge_end[1] - edge_start[1]))
362        let e0 = edge_start[0];
363        let e1 = edge_start[1];
364        let f0 = edge_end[0];
365        let f1 = edge_end[1];
366        let p0 = p[0];
367        let p1 = p[1];
368        let q0 = q[0];
369        let q1 = q[1];
370        let sign = apfp::int_signum!((p0 - q0) * (e0 - f0) - (p1 - q1) * (f1 - e1));
371        match sign {
372          1 => Ordering::Greater,
373          -1 => Ordering::Less,
374          0 => Ordering::Equal,
375          _ => unreachable!(),
376        }
377      }
378
379      fn cmp_perp_edge_normal_slope(
380        edge_start: &[Self; 2],
381        edge_end: &[Self; 2],
382        p: &[Self; 2],
383        q: &[Self; 2],
384      ) -> std::cmp::Ordering {
385        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
386        // We want: cmp_perp_vector_slope(edge_normal, p, q)
387        // Which computes orient2d_normal(p, edge_normal, q):
388        //   sign((p[0] - q[0]) * edge_normal[0] + (p[1] - q[1]) * edge_normal[1])
389        //   = sign((p[0] - q[0]) * (edge_end[1] - edge_start[1]) + (p[1] - q[1]) * (edge_start[0] - edge_end[0]))
390        let e0 = edge_start[0];
391        let e1 = edge_start[1];
392        let f0 = edge_end[0];
393        let f1 = edge_end[1];
394        let p0 = p[0];
395        let p1 = p[1];
396        let q0 = q[0];
397        let q1 = q[1];
398        let sign = apfp::int_signum!((p0 - q0) * (f1 - e1) + (p1 - q1) * (e0 - f0));
399        match sign {
400          1 => Ordering::Greater,
401          -1 => Ordering::Less,
402          0 => Ordering::Equal,
403          _ => unreachable!(),
404        }
405      }
406
407      fn cmp_edge_normals_cross(
408        ref_edge_start: &[Self; 2],
409        ref_edge_end: &[Self; 2],
410        edge_start: &[Self; 2],
411        edge_end: &[Self; 2],
412      ) -> std::cmp::Ordering {
413        // ref_normal = (ref_edge_end[1] - ref_edge_start[1], ref_edge_start[0] - ref_edge_end[0])
414        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
415        // ref_normal × edge_normal = ref_normal.x * edge_normal.y - ref_normal.y * edge_normal.x
416        // = (ref_edge_end[1] - ref_edge_start[1]) * (edge_start[0] - edge_end[0])
417        //   - (ref_edge_start[0] - ref_edge_end[0]) * (edge_end[1] - edge_start[1])
418        let re0 = ref_edge_start[0];
419        let re1 = ref_edge_start[1];
420        let rf0 = ref_edge_end[0];
421        let rf1 = ref_edge_end[1];
422        let e0 = edge_start[0];
423        let e1 = edge_start[1];
424        let f0 = edge_end[0];
425        let f1 = edge_end[1];
426        let sign = apfp::int_signum!((rf1 - re1) * (e0 - f0) - (re0 - rf0) * (f1 - e1));
427        match sign {
428          1 => Ordering::Greater,
429          -1 => Ordering::Less,
430          0 => Ordering::Equal,
431          _ => unreachable!(),
432        }
433      }
434
435      fn cmp_edge_normals_dot(
436        ref_edge_start: &[Self; 2],
437        ref_edge_end: &[Self; 2],
438        edge_start: &[Self; 2],
439        edge_end: &[Self; 2],
440      ) -> std::cmp::Ordering {
441        // ref_normal = (ref_edge_end[1] - ref_edge_start[1], ref_edge_start[0] - ref_edge_end[0])
442        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
443        // ref_normal · edge_normal = ref_normal.x * edge_normal.x + ref_normal.y * edge_normal.y
444        // = (rf1 - re1) * (f1 - e1) + (re0 - rf0) * (e0 - f0)
445        // This equals the dot product of the edge vectors: (rf0 - re0) * (f0 - e0) + (rf1 - re1) * (f1 - e1)
446        let re0 = ref_edge_start[0];
447        let re1 = ref_edge_start[1];
448        let rf0 = ref_edge_end[0];
449        let rf1 = ref_edge_end[1];
450        let e0 = edge_start[0];
451        let e1 = edge_start[1];
452        let f0 = edge_end[0];
453        let f1 = edge_end[1];
454        let sign = apfp::int_signum!((rf0 - re0) * (f0 - e0) + (rf1 - re1) * (f1 - e1));
455        match sign {
456          1 => Ordering::Greater,
457          -1 => Ordering::Less,
458          0 => Ordering::Equal,
459          _ => unreachable!(),
460        }
461      }
462
463      fn cmp_edge_normal_cross_direction(
464        edge_start: &[Self; 2],
465        edge_end: &[Self; 2],
466        direction: &[Self; 2],
467      ) -> std::cmp::Ordering {
468        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
469        // edge_normal × direction = edge_normal.x * direction[1] - edge_normal.y * direction[0]
470        // = (edge_end[1] - edge_start[1]) * direction[1] - (edge_start[0] - edge_end[0]) * direction[0]
471        // = (edge_end[1] - edge_start[1]) * direction[1] + (edge_end[0] - edge_start[0]) * direction[0]
472        let e0 = edge_start[0];
473        let e1 = edge_start[1];
474        let f0 = edge_end[0];
475        let f1 = edge_end[1];
476        let d0 = direction[0];
477        let d1 = direction[1];
478        let sign = apfp::int_signum!((f1 - e1) * d1 + (f0 - e0) * d0);
479        match sign {
480          1 => Ordering::Greater,
481          -1 => Ordering::Less,
482          0 => Ordering::Equal,
483          _ => unreachable!(),
484        }
485      }
486      fn approx_intersection_point(
487        p1: [Self; 2],
488        p2: [Self; 2],
489        q1: [Self; 2],
490        q2: [Self; 2],
491      ) -> Option<[Self; 2]> {
492        let long_p1: [$long; 2] = [p1[0] as $long, p1[1] as $long];
493        let long_p2: [$long; 2] = [p2[0] as $long, p2[1] as $long];
494        let long_q1: [$long; 2] = [q1[0] as $long, q1[1] as $long];
495        let long_q2: [$long; 2] = [q2[0] as $long, q2[1] as $long];
496        let long_result = approx_intersection_point_basic(0, long_p1, long_p2, long_q1, long_q2);
497        long_result.and_then(|[x, y]| Some([Self::try_from(x).ok()?, Self::try_from(y).ok()?]))
498      }
499      fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering {
500        use apfp::geometry::Orientation;
501        use $apfp_mod as apfp_mod;
502        let result = apfp_mod::incircle(
503          &apfp_mod::Coord::new(a[0], a[1]),
504          &apfp_mod::Coord::new(b[0], b[1]),
505          &apfp_mod::Coord::new(c[0], c[1]),
506          &apfp_mod::Coord::new(d[0], d[1]),
507        );
508        match result {
509          Orientation::CounterClockwise => Ordering::Greater,
510          Orientation::Clockwise => Ordering::Less,
511          Orientation::CoLinear => Ordering::Equal,
512        }
513      }
514    }
515  };
516}
517
518macro_rules! arbitrary_precision {
519  ( $( $ty:ty ),* ) => {
520    $(
521      impl TotalOrd for $ty {
522        fn total_cmp(&self, other: &Self) -> Ordering {
523          self.cmp(other)
524        }
525      }
526
527      impl PolygonScalar for $ty {
528      fn from_constant(val: i8) -> Self {
529        <$ty>::from_i8(val).unwrap()
530      }
531      fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
532        let pq_x = &p[0] - &q[0];
533        let pq_y = &p[1] - &q[1];
534        let pq_dist_squared: Self = &pq_x*&pq_x + &pq_y*&pq_y;
535        let pr_x = &p[0] - &r[0];
536        let pr_y = &p[1] - &r[1];
537        let pr_dist_squared: Self = &pr_x*&pr_x + &pr_y*&pr_y;
538        pq_dist_squared.cmp(&pr_dist_squared)
539      }
540
541      fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
542        let slope1 = (&r[1] - &q[1]) * (&q[0] - &p[0]);
543        let slope2 = (&q[1] - &p[1]) * (&r[0] - &q[0]);
544        slope1.cmp(&slope2)
545      }
546      fn cmp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
547        PolygonScalar::cmp_slope(
548          p,
549          &[&p[0] + &vector[0], &p[1] + &vector[1]],
550          q
551        )
552      }
553      fn cmp_perp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
554        PolygonScalar::cmp_slope(
555          p,
556          &[&p[0] - &vector[1], &p[1] + &vector[0]],
557          q
558        )
559      }
560      fn cmp_edge_normal_slope(
561        edge_start: &[Self; 2],
562        edge_end: &[Self; 2],
563        p: &[Self; 2],
564        q: &[Self; 2],
565      ) -> std::cmp::Ordering {
566        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
567        let edge_normal = [
568          &edge_end[1] - &edge_start[1],
569          &edge_start[0] - &edge_end[0],
570        ];
571        PolygonScalar::cmp_vector_slope(&edge_normal, p, q)
572      }
573      fn cmp_perp_edge_normal_slope(
574        edge_start: &[Self; 2],
575        edge_end: &[Self; 2],
576        p: &[Self; 2],
577        q: &[Self; 2],
578      ) -> std::cmp::Ordering {
579        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
580        let edge_normal = [
581          &edge_end[1] - &edge_start[1],
582          &edge_start[0] - &edge_end[0],
583        ];
584        PolygonScalar::cmp_perp_vector_slope(&edge_normal, p, q)
585      }
586      fn cmp_edge_normals_cross(
587        ref_edge_start: &[Self; 2],
588        ref_edge_end: &[Self; 2],
589        edge_start: &[Self; 2],
590        edge_end: &[Self; 2],
591      ) -> std::cmp::Ordering {
592        // ref_normal × edge_normal
593        let ref_nx = &ref_edge_end[1] - &ref_edge_start[1];
594        let ref_ny = &ref_edge_start[0] - &ref_edge_end[0];
595        let edge_nx = &edge_end[1] - &edge_start[1];
596        let edge_ny = &edge_start[0] - &edge_end[0];
597        let cross = &ref_nx * &edge_ny - &ref_ny * &edge_nx;
598        let zero = Self::from_constant(0);
599        cross.cmp(&zero)
600      }
601      fn cmp_edge_normals_dot(
602        ref_edge_start: &[Self; 2],
603        ref_edge_end: &[Self; 2],
604        edge_start: &[Self; 2],
605        edge_end: &[Self; 2],
606      ) -> std::cmp::Ordering {
607        // ref_normal · edge_normal = ref_edge_vector · edge_vector
608        let dot = (&ref_edge_end[0] - &ref_edge_start[0]) * (&edge_end[0] - &edge_start[0])
609          + (&ref_edge_end[1] - &ref_edge_start[1]) * (&edge_end[1] - &edge_start[1]);
610        let zero = Self::from_constant(0);
611        dot.cmp(&zero)
612      }
613      fn cmp_edge_normal_cross_direction(
614        edge_start: &[Self; 2],
615        edge_end: &[Self; 2],
616        direction: &[Self; 2],
617      ) -> std::cmp::Ordering {
618        // edge_normal × direction = (edge_end - edge_start) · direction
619        let dot = (&edge_end[0] - &edge_start[0]) * &direction[0]
620          + (&edge_end[1] - &edge_start[1]) * &direction[1];
621        let zero = Self::from_constant(0);
622        dot.cmp(&zero)
623      }
624      fn incircle(
625        a: &[Self; 2],
626        b: &[Self; 2],
627        c: &[Self; 2],
628        d: &[Self; 2],
629      ) -> Ordering {
630        incircle_exact(a, b, c, d)
631      }
632    })*
633  };
634}
635
636macro_rules! floating_precision {
637  ( $( $ty:ty ),* ) => {
638    $(
639      impl TotalOrd for $ty {
640        fn total_cmp(&self, other: &Self) -> Ordering {
641          // The ordering established by `<$ty>::total_cmp` does not always
642          // agree with the PartialOrd and PartialEq implementations of <$ty>. For
643          // example, they consider negative and positive zero equal, while
644          // total_cmp doesn’t.
645          if *self == 0.0 && *other == 0.0 {
646            Ordering::Equal
647          } else {
648            <$ty>::total_cmp(self, other)
649          }
650        }
651      }
652
653      impl PolygonScalar for $ty {
654      fn from_constant(val: i8) -> Self {
655        <$ty>::from_i8(val).unwrap()
656      }
657      // FIXME: Use `geometry_predicates` to speed up calculation. Right now we're
658      // roughly 100x slower than necessary.
659      fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
660        PolygonScalar::cmp_dist(
661          &[float_to_rational(p[0]), float_to_rational(p[1])],
662          &[float_to_rational(q[0]), float_to_rational(q[1])],
663          &[float_to_rational(r[0]), float_to_rational(r[1])],
664        )
665      }
666
667      // This function uses the arbitrary precision machinery of `apfp` to
668      // quickly compute the orientation of three 2D points.
669      fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
670        use apfp::geometry::{Orientation, f64::Coord};
671        let orient = apfp::geometry::f64::orient2d(
672          &Coord::new(p[0] as f64, p[1] as f64),
673          &Coord::new(q[0] as f64, q[1] as f64),
674          &Coord::new(r[0] as f64, r[1] as f64),
675        );
676        match orient {
677          Orientation::CounterClockwise => Ordering::Greater,
678          Orientation::Clockwise => Ordering::Less,
679          Orientation::CoLinear => Ordering::Equal,
680        }
681      }
682      // Uses apfp's orient2d_vec for fast arbitrary precision calculation.
683      fn cmp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
684        use apfp::geometry::{Orientation, f64::Coord};
685        let orient = apfp::geometry::f64::orient2d_vec(
686          &Coord::new(p[0] as f64, p[1] as f64),
687          &Coord::new(vector[0] as f64, vector[1] as f64),
688          &Coord::new(q[0] as f64, q[1] as f64),
689        );
690        match orient {
691          Orientation::CounterClockwise => Ordering::Greater,
692          Orientation::Clockwise => Ordering::Less,
693          Orientation::CoLinear => Ordering::Equal,
694        }
695      }
696      // Uses apfp's orient2d_normal for fast arbitrary precision calculation.
697      fn cmp_perp_vector_slope(vector: &[Self;2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
698        use apfp::geometry::{Orientation, f64::Coord};
699        let orient = apfp::geometry::f64::orient2d_normal(
700          &Coord::new(p[0] as f64, p[1] as f64),
701          &Coord::new(vector[0] as f64, vector[1] as f64),
702          &Coord::new(q[0] as f64, q[1] as f64),
703        );
704        match orient {
705          Orientation::CounterClockwise => Ordering::Greater,
706          Orientation::Clockwise => Ordering::Less,
707          Orientation::CoLinear => Ordering::Equal,
708        }
709      }
710      fn cmp_edge_normal_slope(
711        edge_start: &[Self; 2],
712        edge_end: &[Self; 2],
713        p: &[Self; 2],
714        q: &[Self; 2],
715      ) -> std::cmp::Ordering {
716        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
717        // We need to compute orient2d_vec(p, edge_normal, q), which is:
718        //   (p.x - q.x) * normal.y - (p.y - q.y) * normal.x
719        // = (p.x - q.x) * (edge_start[0] - edge_end[0]) - (p.y - q.y) * (edge_end[1] - edge_start[1])
720        //
721        // Use apfp_signum! with all original coordinates to avoid precision loss
722        // from pre-computing the normal.
723        let px = p[0] as f64;
724        let py = p[1] as f64;
725        let qx = q[0] as f64;
726        let qy = q[1] as f64;
727        let es0 = edge_start[0] as f64;
728        let es1 = edge_start[1] as f64;
729        let ee0 = edge_end[0] as f64;
730        let ee1 = edge_end[1] as f64;
731        // (p.x - q.x) * (es0 - ee0) - (p.y - q.y) * (ee1 - es1)
732        // = (px - qx) * (es0 - ee0) - (py - qy) * (ee1 - es1)
733        // = px*es0 - px*ee0 - qx*es0 + qx*ee0 - py*ee1 + py*es1 + qy*ee1 - qy*es1
734        match apfp::apfp_signum!(
735          (px - qx) * (es0 - ee0) - (py - qy) * (ee1 - es1)
736        ) {
737          1 => Ordering::Greater,
738          -1 => Ordering::Less,
739          _ => Ordering::Equal,
740        }
741      }
742      fn cmp_perp_edge_normal_slope(
743        edge_start: &[Self; 2],
744        edge_end: &[Self; 2],
745        p: &[Self; 2],
746        q: &[Self; 2],
747      ) -> std::cmp::Ordering {
748        // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
749        // We need to compute orient2d_normal(p, edge_normal, q), which is:
750        //   (p.x - q.x) * normal.x + (p.y - q.y) * normal.y
751        // = (p.x - q.x) * (edge_end[1] - edge_start[1]) + (p.y - q.y) * (edge_start[0] - edge_end[0])
752        //
753        // Use apfp_signum! with all original coordinates to avoid precision loss.
754        let px = p[0] as f64;
755        let py = p[1] as f64;
756        let qx = q[0] as f64;
757        let qy = q[1] as f64;
758        let es0 = edge_start[0] as f64;
759        let es1 = edge_start[1] as f64;
760        let ee0 = edge_end[0] as f64;
761        let ee1 = edge_end[1] as f64;
762        // (px - qx) * (ee1 - es1) + (py - qy) * (es0 - ee0)
763        match apfp::apfp_signum!(
764          (px - qx) * (ee1 - es1) + (py - qy) * (es0 - ee0)
765        ) {
766          1 => Ordering::Greater,
767          -1 => Ordering::Less,
768          _ => Ordering::Equal,
769        }
770      }
771      fn cmp_edge_normals_cross(
772        ref_edge_start: &[Self; 2],
773        ref_edge_end: &[Self; 2],
774        edge_start: &[Self; 2],
775        edge_end: &[Self; 2],
776      ) -> std::cmp::Ordering {
777        // Use apfp's adaptive precision for robust computation and keep the
778        // subtraction inside the expression to avoid rounding the edge normals.
779        let ref_sx = ref_edge_start[0] as f64;
780        let ref_sy = ref_edge_start[1] as f64;
781        let ref_ex = ref_edge_end[0] as f64;
782        let ref_ey = ref_edge_end[1] as f64;
783        let edge_sx = edge_start[0] as f64;
784        let edge_sy = edge_start[1] as f64;
785        let edge_ex = edge_end[0] as f64;
786        let edge_ey = edge_end[1] as f64;
787        match apfp::apfp_signum!(
788          (ref_ey - ref_sy) * (edge_sx - edge_ex) - (ref_sx - ref_ex) * (edge_ey - edge_sy)
789        ) {
790          1 => Ordering::Greater,
791          -1 => Ordering::Less,
792          _ => Ordering::Equal,
793        }
794      }
795      fn cmp_edge_normals_dot(
796        ref_edge_start: &[Self; 2],
797        ref_edge_end: &[Self; 2],
798        edge_start: &[Self; 2],
799        edge_end: &[Self; 2],
800      ) -> std::cmp::Ordering {
801        // Use apfp's adaptive precision for robust computation and avoid
802        // rounding the edge vectors before evaluation.
803        let ref_sx = ref_edge_start[0] as f64;
804        let ref_sy = ref_edge_start[1] as f64;
805        let ref_ex = ref_edge_end[0] as f64;
806        let ref_ey = ref_edge_end[1] as f64;
807        let edge_sx = edge_start[0] as f64;
808        let edge_sy = edge_start[1] as f64;
809        let edge_ex = edge_end[0] as f64;
810        let edge_ey = edge_end[1] as f64;
811        match apfp::apfp_signum!(
812          (ref_ex - ref_sx) * (edge_ex - edge_sx) + (ref_ey - ref_sy) * (edge_ey - edge_sy)
813        ) {
814          1 => Ordering::Greater,
815          -1 => Ordering::Less,
816          _ => Ordering::Equal,
817        }
818      }
819      fn cmp_edge_normal_cross_direction(
820        edge_start: &[Self; 2],
821        edge_end: &[Self; 2],
822        direction: &[Self; 2],
823      ) -> std::cmp::Ordering {
824        // Use apfp's adaptive precision for robust computation and avoid
825        // rounding the edge vector before evaluation.
826        let edge_sx = edge_start[0] as f64;
827        let edge_sy = edge_start[1] as f64;
828        let edge_ex = edge_end[0] as f64;
829        let edge_ey = edge_end[1] as f64;
830        let dir_x = direction[0] as f64;
831        let dir_y = direction[1] as f64;
832        match apfp::apfp_signum!(
833          (edge_ex - edge_sx) * dir_x + (edge_ey - edge_sy) * dir_y
834        ) {
835          1 => Ordering::Greater,
836          -1 => Ordering::Less,
837          _ => Ordering::Equal,
838        }
839      }
840      fn incircle(
841        a: &[Self; 2],
842        b: &[Self; 2],
843        c: &[Self; 2],
844        d: &[Self; 2],
845      ) -> Ordering {
846        use apfp::geometry::{Orientation, f64::Coord};
847        let result = apfp::geometry::f64::incircle(
848          &Coord::new(a[0] as f64, a[1] as f64),
849          &Coord::new(b[0] as f64, b[1] as f64),
850          &Coord::new(c[0] as f64, c[1] as f64),
851          &Coord::new(d[0] as f64, d[1] as f64),
852        );
853        match result {
854          Orientation::CounterClockwise => Ordering::Greater,
855          Orientation::Clockwise => Ordering::Less,
856          Orientation::CoLinear => Ordering::Equal,
857        }
858      }
859    })*
860  };
861}
862
863fixed_precision!(i8, u8, i16, u16, apfp::geometry::i8);
864fixed_precision!(i16, u16, i32, u32, apfp::geometry::i16);
865fixed_precision!(i32, u32, i64, u64, apfp::geometry::i32);
866fixed_precision!(i64, u64, i128, u128, apfp::geometry::i64);
867
868// isize needs special handling since apfp doesn't have an isize module
869// We cast to i64 which works on all platforms (isize is at most 64-bit)
870impl TotalOrd for isize {
871  fn total_cmp(&self, other: &Self) -> Ordering {
872    self.cmp(other)
873  }
874}
875
876impl PolygonScalar for isize {
877  fn from_constant(val: i8) -> Self {
878    val as isize
879  }
880  fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
881    use apfp::geometry::i64 as apfp_mod;
882    apfp_mod::cmp_dist(
883      &apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
884      &apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
885      &apfp_mod::Coord::new(r[0] as i64, r[1] as i64),
886    )
887  }
888
889  fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
890    use apfp::geometry::{Orientation, i64 as apfp_mod};
891    let orient = apfp_mod::orient2d(
892      &apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
893      &apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
894      &apfp_mod::Coord::new(r[0] as i64, r[1] as i64),
895    );
896    match orient {
897      Orientation::CounterClockwise => Ordering::Greater,
898      Orientation::Clockwise => Ordering::Less,
899      Orientation::CoLinear => Ordering::Equal,
900    }
901  }
902
903  fn cmp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
904    use apfp::geometry::{Orientation, i64 as apfp_mod};
905    let orient = apfp_mod::orient2d_vec(
906      &apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
907      &apfp_mod::Coord::new(vector[0] as i64, vector[1] as i64),
908      &apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
909    );
910    match orient {
911      Orientation::CounterClockwise => Ordering::Greater,
912      Orientation::Clockwise => Ordering::Less,
913      Orientation::CoLinear => Ordering::Equal,
914    }
915  }
916
917  fn cmp_perp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
918    use apfp::geometry::{Orientation, i64 as apfp_mod};
919    let orient = apfp_mod::orient2d_normal(
920      &apfp_mod::Coord::new(p[0] as i64, p[1] as i64),
921      &apfp_mod::Coord::new(vector[0] as i64, vector[1] as i64),
922      &apfp_mod::Coord::new(q[0] as i64, q[1] as i64),
923    );
924    match orient {
925      Orientation::CounterClockwise => Ordering::Greater,
926      Orientation::Clockwise => Ordering::Less,
927      Orientation::CoLinear => Ordering::Equal,
928    }
929  }
930
931  fn cmp_edge_normal_slope(
932    edge_start: &[Self; 2],
933    edge_end: &[Self; 2],
934    p: &[Self; 2],
935    q: &[Self; 2],
936  ) -> std::cmp::Ordering {
937    // Cast to i64 and use int_signum for overflow-safe computation
938    let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
939    let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
940    let (p0, p1) = (p[0] as i64, p[1] as i64);
941    let (q0, q1) = (q[0] as i64, q[1] as i64);
942    let sign = apfp::int_signum!((p0 - q0) * (e0 - f0) - (p1 - q1) * (f1 - e1));
943    match sign {
944      1 => Ordering::Greater,
945      -1 => Ordering::Less,
946      0 => Ordering::Equal,
947      _ => unreachable!(),
948    }
949  }
950
951  fn cmp_perp_edge_normal_slope(
952    edge_start: &[Self; 2],
953    edge_end: &[Self; 2],
954    p: &[Self; 2],
955    q: &[Self; 2],
956  ) -> std::cmp::Ordering {
957    // Cast to i64 and use int_signum for overflow-safe computation
958    let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
959    let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
960    let (p0, p1) = (p[0] as i64, p[1] as i64);
961    let (q0, q1) = (q[0] as i64, q[1] as i64);
962    let sign = apfp::int_signum!((p0 - q0) * (f1 - e1) + (p1 - q1) * (e0 - f0));
963    match sign {
964      1 => Ordering::Greater,
965      -1 => Ordering::Less,
966      0 => Ordering::Equal,
967      _ => unreachable!(),
968    }
969  }
970
971  fn cmp_edge_normals_cross(
972    ref_edge_start: &[Self; 2],
973    ref_edge_end: &[Self; 2],
974    edge_start: &[Self; 2],
975    edge_end: &[Self; 2],
976  ) -> std::cmp::Ordering {
977    let (re0, re1) = (ref_edge_start[0] as i64, ref_edge_start[1] as i64);
978    let (rf0, rf1) = (ref_edge_end[0] as i64, ref_edge_end[1] as i64);
979    let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
980    let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
981    let sign = apfp::int_signum!((rf1 - re1) * (e0 - f0) - (re0 - rf0) * (f1 - e1));
982    match sign {
983      1 => Ordering::Greater,
984      -1 => Ordering::Less,
985      0 => Ordering::Equal,
986      _ => unreachable!(),
987    }
988  }
989
990  fn cmp_edge_normals_dot(
991    ref_edge_start: &[Self; 2],
992    ref_edge_end: &[Self; 2],
993    edge_start: &[Self; 2],
994    edge_end: &[Self; 2],
995  ) -> std::cmp::Ordering {
996    let (re0, re1) = (ref_edge_start[0] as i64, ref_edge_start[1] as i64);
997    let (rf0, rf1) = (ref_edge_end[0] as i64, ref_edge_end[1] as i64);
998    let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
999    let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
1000    let sign = apfp::int_signum!((rf0 - re0) * (f0 - e0) + (rf1 - re1) * (f1 - e1));
1001    match sign {
1002      1 => Ordering::Greater,
1003      -1 => Ordering::Less,
1004      0 => Ordering::Equal,
1005      _ => unreachable!(),
1006    }
1007  }
1008
1009  fn cmp_edge_normal_cross_direction(
1010    edge_start: &[Self; 2],
1011    edge_end: &[Self; 2],
1012    direction: &[Self; 2],
1013  ) -> std::cmp::Ordering {
1014    let (e0, e1) = (edge_start[0] as i64, edge_start[1] as i64);
1015    let (f0, f1) = (edge_end[0] as i64, edge_end[1] as i64);
1016    let (d0, d1) = (direction[0] as i64, direction[1] as i64);
1017    let sign = apfp::int_signum!((f1 - e1) * d1 + (f0 - e0) * d0);
1018    match sign {
1019      1 => Ordering::Greater,
1020      -1 => Ordering::Less,
1021      0 => Ordering::Equal,
1022      _ => unreachable!(),
1023    }
1024  }
1025  fn approx_intersection_point(
1026    p1: [Self; 2],
1027    p2: [Self; 2],
1028    q1: [Self; 2],
1029    q2: [Self; 2],
1030  ) -> Option<[Self; 2]> {
1031    let long_p1: [i128; 2] = [p1[0] as i128, p1[1] as i128];
1032    let long_p2: [i128; 2] = [p2[0] as i128, p2[1] as i128];
1033    let long_q1: [i128; 2] = [q1[0] as i128, q1[1] as i128];
1034    let long_q2: [i128; 2] = [q2[0] as i128, q2[1] as i128];
1035    let long_result = approx_intersection_point_basic(0, long_p1, long_p2, long_q1, long_q2);
1036    long_result.and_then(|[x, y]| Some([Self::try_from(x).ok()?, Self::try_from(y).ok()?]))
1037  }
1038  fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering {
1039    use apfp::geometry::{Orientation, i64 as apfp_mod};
1040    let result = apfp_mod::incircle(
1041      &apfp_mod::Coord::new(a[0] as i64, a[1] as i64),
1042      &apfp_mod::Coord::new(b[0] as i64, b[1] as i64),
1043      &apfp_mod::Coord::new(c[0] as i64, c[1] as i64),
1044      &apfp_mod::Coord::new(d[0] as i64, d[1] as i64),
1045    );
1046    match result {
1047      Orientation::CounterClockwise => Ordering::Greater,
1048      Orientation::Clockwise => Ordering::Less,
1049      Orientation::CoLinear => Ordering::Equal,
1050    }
1051  }
1052}
1053arbitrary_precision!(num_bigint::BigInt);
1054arbitrary_precision!(num_rational::BigRational);
1055floating_precision!(f32);
1056floating_precision!(f64);
1057
1058#[cfg(feature = "rug")]
1059impl TotalOrd for rug::Integer {
1060  fn total_cmp(&self, other: &Self) -> Ordering {
1061    self.cmp(other)
1062  }
1063}
1064
1065#[cfg(feature = "rug")]
1066impl PolygonScalar for rug::Integer {
1067  fn from_constant(val: i8) -> Self {
1068    rug::Integer::from(val)
1069  }
1070  fn cmp_dist(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
1071    let [qx, qy] = q.clone();
1072    let [px, py] = p.clone();
1073    let pq_x = px - qx;
1074    let pq_y = py - qy;
1075    let pq_dist_squared: Self = pq_x.square() + pq_y.square();
1076    let [rx, ry] = r.clone();
1077    let [px, py] = p.clone();
1078    let pr_x = px - rx;
1079    let pr_y = py - ry;
1080    let pr_dist_squared: Self = pr_x.square() + pr_y.square();
1081    pq_dist_squared.cmp(&pr_dist_squared)
1082  }
1083
1084  fn cmp_slope(p: &[Self; 2], q: &[Self; 2], r: &[Self; 2]) -> std::cmp::Ordering {
1085    let [qx, qy] = q.clone();
1086    let [rx, ry] = r.clone();
1087    let ry_qy = ry - &q[1];
1088    let qx_px = qx - &p[0];
1089    let slope1 = ry_qy * qx_px;
1090    let qy_py = qy - &p[1];
1091    let rx_qx = rx - &q[0];
1092    let slope2 = qy_py * rx_qx;
1093    slope1.cmp(&slope2)
1094  }
1095  fn cmp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
1096    let new_x = rug::Integer::from(&p[0] + &vector[0]);
1097    let new_y = rug::Integer::from(&p[1] + &vector[1]);
1098    PolygonScalar::cmp_slope(p, &[new_x, new_y], q)
1099  }
1100  fn cmp_perp_vector_slope(vector: &[Self; 2], p: &[Self; 2], q: &[Self; 2]) -> std::cmp::Ordering {
1101    // The perpendicular (normal) of vector (vx, vy) rotated 90° counterclockwise is (-vy, vx)
1102    // So the new point is: (p[0] - vector[1], p[1] + vector[0])
1103    let new_x = rug::Integer::from(&p[0] - &vector[1]);
1104    let new_y = rug::Integer::from(&p[1] + &vector[0]);
1105    PolygonScalar::cmp_slope(p, &[new_x, new_y], q)
1106  }
1107  fn cmp_edge_normal_slope(
1108    edge_start: &[Self; 2],
1109    edge_end: &[Self; 2],
1110    p: &[Self; 2],
1111    q: &[Self; 2],
1112  ) -> std::cmp::Ordering {
1113    // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
1114    let edge_normal = [
1115      rug::Integer::from(&edge_end[1] - &edge_start[1]),
1116      rug::Integer::from(&edge_start[0] - &edge_end[0]),
1117    ];
1118    PolygonScalar::cmp_vector_slope(&edge_normal, p, q)
1119  }
1120  fn cmp_perp_edge_normal_slope(
1121    edge_start: &[Self; 2],
1122    edge_end: &[Self; 2],
1123    p: &[Self; 2],
1124    q: &[Self; 2],
1125  ) -> std::cmp::Ordering {
1126    // edge_normal = (edge_end[1] - edge_start[1], edge_start[0] - edge_end[0])
1127    let edge_normal = [
1128      rug::Integer::from(&edge_end[1] - &edge_start[1]),
1129      rug::Integer::from(&edge_start[0] - &edge_end[0]),
1130    ];
1131    PolygonScalar::cmp_perp_vector_slope(&edge_normal, p, q)
1132  }
1133  fn cmp_edge_normals_cross(
1134    ref_edge_start: &[Self; 2],
1135    ref_edge_end: &[Self; 2],
1136    edge_start: &[Self; 2],
1137    edge_end: &[Self; 2],
1138  ) -> std::cmp::Ordering {
1139    // ref_normal × edge_normal
1140    let ref_nx = rug::Integer::from(&ref_edge_end[1] - &ref_edge_start[1]);
1141    let ref_ny = rug::Integer::from(&ref_edge_start[0] - &ref_edge_end[0]);
1142    let edge_nx = rug::Integer::from(&edge_end[1] - &edge_start[1]);
1143    let edge_ny = rug::Integer::from(&edge_start[0] - &edge_end[0]);
1144    let cross = ref_nx * edge_ny - ref_ny * edge_nx;
1145    cross.cmp(&rug::Integer::new())
1146  }
1147  fn cmp_edge_normals_dot(
1148    ref_edge_start: &[Self; 2],
1149    ref_edge_end: &[Self; 2],
1150    edge_start: &[Self; 2],
1151    edge_end: &[Self; 2],
1152  ) -> std::cmp::Ordering {
1153    // ref_normal · edge_normal = ref_edge_vector · edge_vector
1154    let dot = rug::Integer::from(&ref_edge_end[0] - &ref_edge_start[0])
1155      * rug::Integer::from(&edge_end[0] - &edge_start[0])
1156      + rug::Integer::from(&ref_edge_end[1] - &ref_edge_start[1])
1157        * rug::Integer::from(&edge_end[1] - &edge_start[1]);
1158    dot.cmp(&rug::Integer::new())
1159  }
1160  fn cmp_edge_normal_cross_direction(
1161    edge_start: &[Self; 2],
1162    edge_end: &[Self; 2],
1163    direction: &[Self; 2],
1164  ) -> std::cmp::Ordering {
1165    // edge_normal × direction = (edge_end - edge_start) · direction
1166    let dot = rug::Integer::from(&edge_end[0] - &edge_start[0]) * &direction[0]
1167      + rug::Integer::from(&edge_end[1] - &edge_start[1]) * &direction[1];
1168    dot.cmp(&rug::Integer::new())
1169  }
1170  fn incircle(a: &[Self; 2], b: &[Self; 2], c: &[Self; 2], d: &[Self; 2]) -> Ordering {
1171    let mut ax = a[0].clone();
1172    ax -= &d[0];
1173    let mut ay = a[1].clone();
1174    ay -= &d[1];
1175    let mut bx = b[0].clone();
1176    bx -= &d[0];
1177    let mut by = b[1].clone();
1178    by -= &d[1];
1179    let mut cx = c[0].clone();
1180    cx -= &d[0];
1181    let mut cy = c[1].clone();
1182    cy -= &d[1];
1183
1184    let a_len = ax.clone() * ax.clone() + ay.clone() * ay.clone();
1185    let b_len = bx.clone() * bx.clone() + by.clone() * by.clone();
1186    let c_len = cx.clone() * cx.clone() + cy.clone() * cy.clone();
1187
1188    let det = det3_generic(&ax, &ay, &a_len, &bx, &by, &b_len, &cx, &cy, &c_len);
1189    det.cmp(&rug::Integer::new())
1190  }
1191}
1192
1193fn float_to_rational(f: impl num::traits::float::FloatCore) -> num::BigRational {
1194  num::BigRational::from_float(f).expect("cannot convert NaN or infinite to exact precision number")
1195}
1196
1197#[cfg(test)]
1198pub mod testing;
1199
1200#[cfg(test)]
1201mod floating_robustness_tests {
1202  use super::*;
1203
1204  // Helper to convert f64 to BigRational for ground truth comparison.
1205  fn to_big(f: f64) -> num_rational::BigRational {
1206    num_rational::BigRational::from_float(f).unwrap()
1207  }
1208
1209  // Test that cmp_edge_normals_cross is robust for nearly-parallel edges.
1210  //
1211  // The naive floating-point implementation computes:
1212  //   cross = ref_nx * edge_ny - ref_ny * edge_nx
1213  //
1214  // For edges from origin, this simplifies to:
1215  //   cross = ref_end[0] * edge_end[1] - ref_end[1] * edge_end[0]
1216  //
1217  // At 1e15 scale, the products are at 1e30 scale where the ULP is ~1.4e14.
1218  // This means differences smaller than 1.4e14 get rounded away!
1219  //
1220  // This test uses coordinates where:
1221  // - All inputs are exactly representable f64 values
1222  // - The true cross product is -2 (mathematically exact, computable by BigRational)
1223  // - The naive f64 computation gives 0 due to precision loss in the products
1224  #[test]
1225  fn cmp_edge_normals_cross_f64_robustness() {
1226    // Edges from origin for simplicity:
1227    // ref_edge: (0,0) -> (1e15, 1e15+1)
1228    // edge: (0,0) -> (1e15+2, 1e15+3)
1229    //
1230    // cross = ref[0]*edge[1] - ref[1]*edge[0]
1231    //       = 1e15 * (1e15+3) - (1e15+1) * (1e15+2)
1232    //       = 1e30 + 3e15 - (1e30 + 2e15 + 1e15 + 2)
1233    //       = 1e30 + 3e15 - 1e30 - 3e15 - 2
1234    //       = -2
1235    //
1236    // But in f64:
1237    // - 1e15 * (1e15+3) = 1e30 + 3e15, rounded to nearest at 1e30 scale
1238    // - (1e15+1) * (1e15+2) = 1e30 + 3e15 + 2, rounded to same value
1239    // - Both products round to the same f64 value, so cross = 0
1240    //
1241    // ULP at 1e30 scale is ~1.4e14, so the difference of 2 is completely lost.
1242    let big = 1e15f64;
1243
1244    let ref_start = [0.0f64, 0.0];
1245    let ref_end = [big, big + 1.0];
1246    let edge_start = [0.0f64, 0.0];
1247    let edge_end = [big + 2.0, big + 3.0];
1248
1249    // Ground truth using BigRational
1250    let ref_start_big = [to_big(ref_start[0]), to_big(ref_start[1])];
1251    let ref_end_big = [to_big(ref_end[0]), to_big(ref_end[1])];
1252    let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
1253    let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
1254    let expected = num_rational::BigRational::cmp_edge_normals_cross(
1255      &ref_start_big,
1256      &ref_end_big,
1257      &edge_start_big,
1258      &edge_end_big,
1259    );
1260
1261    // The mathematically correct answer is Less (cross = -2)
1262    assert_eq!(
1263      expected,
1264      Ordering::Less,
1265      "BigRational should give Less (cross = -2)"
1266    );
1267
1268    // The f64 implementation should match BigRational
1269    let result = f64::cmp_edge_normals_cross(&ref_start, &ref_end, &edge_start, &edge_end);
1270    assert_eq!(
1271      result, expected,
1272      "f64 cmp_edge_normals_cross gave {:?} but BigRational gave {:?}. \
1273       This demonstrates precision loss: true cross is -2, but naive f64 gives 0.",
1274      result, expected
1275    );
1276  }
1277
1278  // Test that cmp_edge_normals_dot is robust for nearly-perpendicular edges.
1279  //
1280  // The dot product of edge vectors suffers similar precision loss.
1281  // dot = (ref_end[0] - ref_start[0]) * (edge_end[0] - edge_start[0])
1282  //     + (ref_end[1] - ref_start[1]) * (edge_end[1] - edge_start[1])
1283  //
1284  // For edges from origin:
1285  #[test]
1286  fn cmp_edge_normals_dot_f64_robustness() {
1287    let big = 1e15f64;
1288
1289    let ref_start = [0.0f64, 0.0];
1290    let ref_end = [big, 1.0];
1291    let edge_start = [0.0f64, 0.0];
1292    let edge_end = [-1.0, big + 0.5];
1293
1294    let ref_start_big = [to_big(ref_start[0]), to_big(ref_start[1])];
1295    let ref_end_big = [to_big(ref_end[0]), to_big(ref_end[1])];
1296    let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
1297    let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
1298    let expected = num_rational::BigRational::cmp_edge_normals_dot(
1299      &ref_start_big,
1300      &ref_end_big,
1301      &edge_start_big,
1302      &edge_end_big,
1303    );
1304
1305    assert_eq!(expected, Ordering::Greater);
1306
1307    let result = f64::cmp_edge_normals_dot(&ref_start, &ref_end, &edge_start, &edge_end);
1308    assert_eq!(result, expected);
1309  }
1310
1311  // Test that cmp_edge_normal_cross_direction is robust.
1312  //
1313  // This computes edge_vec · direction, which has the same structure as dot product.
1314  #[test]
1315  fn cmp_edge_normal_cross_direction_f64_robustness() {
1316    // edge: (0, 0) -> (1e15, 1)
1317    // direction: (-1, 1e15 + 0.5)
1318    //
1319    // edge_vec · direction = 1e15 * (-1) + 1 * (1e15 + 0.5) = 0.5
1320    let big = 1e15f64;
1321
1322    let edge_start = [0.0f64, 0.0];
1323    let edge_end = [big, 1.0];
1324    let direction = [-1.0f64, big + 0.5];
1325
1326    let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
1327    let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
1328    let direction_big = [to_big(direction[0]), to_big(direction[1])];
1329    let expected = num_rational::BigRational::cmp_edge_normal_cross_direction(
1330      &edge_start_big,
1331      &edge_end_big,
1332      &direction_big,
1333    );
1334
1335    assert_eq!(
1336      expected,
1337      Ordering::Greater,
1338      "BigRational should give Greater (result = 0.5)"
1339    );
1340
1341    let result = f64::cmp_edge_normal_cross_direction(&edge_start, &edge_end, &direction);
1342    assert_eq!(
1343      result, expected,
1344      "f64 cmp_edge_normal_cross_direction gave {:?} but BigRational gave {:?}",
1345      result, expected
1346    );
1347  }
1348
1349  // Regression: cmp_edge_normal_slope should still agree with BigRational even
1350  // when a naive f64 implementation would lose precision while computing the
1351  // edge normal.
1352  #[test]
1353  fn cmp_edge_normal_slope_f64_mismatch() {
1354    // edge_start/end use decimal literals that are not exactly representable.
1355    // The edge normal is computed via subtraction in f64, which rounds.
1356    // With large p/q deltas, the rounding error changes the sign.
1357    let edge_start = [0.1f64, 0.2];
1358    let edge_end = [0.5f64, 0.1];
1359    let p = [1125899906842624.0f64, 4503599627370496.0];
1360    let q = [0.0f64, 0.0];
1361
1362    let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
1363    let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
1364    let p_big = [to_big(p[0]), to_big(p[1])];
1365    let q_big = [to_big(q[0]), to_big(q[1])];
1366
1367    let expected = num_rational::BigRational::cmp_edge_normal_slope(
1368      &edge_start_big,
1369      &edge_end_big,
1370      &p_big,
1371      &q_big,
1372    );
1373    assert_eq!(
1374      expected,
1375      Ordering::Greater,
1376      "BigRational should give Greater for this configuration"
1377    );
1378
1379    let result = f64::cmp_edge_normal_slope(&edge_start, &edge_end, &p, &q);
1380    assert_eq!(
1381      result, expected,
1382      "f64 cmp_edge_normal_slope gave {:?} but BigRational gave {:?}",
1383      result, expected
1384    );
1385  }
1386
1387  // Regression: cmp_perp_edge_normal_slope should still agree with BigRational
1388  // even when a naive f64 implementation would lose precision while computing
1389  // the edge normal.
1390  #[test]
1391  fn cmp_perp_edge_normal_slope_f64_mismatch() {
1392    let edge_start = [0.1f64, 0.1];
1393    let edge_end = [0.2f64, 0.5];
1394    let p = [1125899906842624.0f64, 4503599627370496.0];
1395    let q = [0.0f64, 0.0];
1396
1397    let edge_start_big = [to_big(edge_start[0]), to_big(edge_start[1])];
1398    let edge_end_big = [to_big(edge_end[0]), to_big(edge_end[1])];
1399    let p_big = [to_big(p[0]), to_big(p[1])];
1400    let q_big = [to_big(q[0]), to_big(q[1])];
1401
1402    let expected = num_rational::BigRational::cmp_perp_edge_normal_slope(
1403      &edge_start_big,
1404      &edge_end_big,
1405      &p_big,
1406      &q_big,
1407    );
1408    assert_eq!(
1409      expected,
1410      Ordering::Less,
1411      "BigRational should give Less for this configuration"
1412    );
1413
1414    let result = f64::cmp_perp_edge_normal_slope(&edge_start, &edge_end, &p, &q);
1415    assert_eq!(
1416      result, expected,
1417      "f64 cmp_perp_edge_normal_slope gave {:?} but BigRational gave {:?}",
1418      result, expected
1419    );
1420  }
1421}