pub use u_geometry::robust::{
is_ccw, is_convex, orient2d, orient2d_filtered, orient2d_raw, point_in_triangle,
point_in_triangle_inclusive, Orientation,
};
#[inline]
pub fn point_in_triangle_robust(
p: (f64, f64),
a: (f64, f64),
b: (f64, f64),
c: (f64, f64),
) -> bool {
point_in_triangle(p, a, b, c)
}
#[inline]
pub fn point_in_triangle_inclusive_robust(
p: (f64, f64),
a: (f64, f64),
b: (f64, f64),
c: (f64, f64),
) -> bool {
point_in_triangle_inclusive(p, a, b, c)
}
#[inline]
pub fn is_convex_robust(polygon: &[(f64, f64)]) -> bool {
is_convex(polygon)
}
#[inline]
pub fn is_ccw_robust(polygon: &[(f64, f64)]) -> bool {
is_ccw(polygon)
}
pub fn signed_area_robust(polygon: &[(f64, f64)]) -> f64 {
let n = polygon.len();
if n < 3 {
return 0.0;
}
let mut sum = 0.0;
let mut c = 0.0;
for i in 0..n {
let j = (i + 1) % n;
let term = polygon[i].0 * polygon[j].1 - polygon[j].0 * polygon[i].1;
let y = term - c;
let t = sum + y;
c = (t - sum) - y;
sum = t;
}
sum / 2.0
}
#[derive(Debug, Clone, Copy)]
pub struct ScalingConfig {
pub scale: f64,
pub inv_scale: f64,
}
impl ScalingConfig {
pub fn new(precision: u32) -> Self {
let scale = 10.0_f64.powi(precision as i32);
Self {
scale,
inv_scale: 1.0 / scale,
}
}
#[inline]
pub fn scale_coord(&self, x: f64) -> f64 {
(x * self.scale).round()
}
#[inline]
pub fn scale_point(&self, p: (f64, f64)) -> (f64, f64) {
(self.scale_coord(p.0), self.scale_coord(p.1))
}
#[inline]
pub fn unscale_coord(&self, x: f64) -> f64 {
x * self.inv_scale
}
#[inline]
pub fn unscale_point(&self, p: (f64, f64)) -> (f64, f64) {
(self.unscale_coord(p.0), self.unscale_coord(p.1))
}
pub fn scale_polygon(&self, polygon: &[(f64, f64)]) -> Vec<(f64, f64)> {
polygon.iter().map(|&p| self.scale_point(p)).collect()
}
pub fn unscale_polygon(&self, polygon: &[(f64, f64)]) -> Vec<(f64, f64)> {
polygon.iter().map(|&p| self.unscale_point(p)).collect()
}
}
impl Default for ScalingConfig {
fn default() -> Self {
Self::new(6)
}
}
#[inline]
pub fn snap_to_grid(point: (f64, f64), resolution: f64) -> (f64, f64) {
(
(point.0 / resolution).round() * resolution,
(point.1 / resolution).round() * resolution,
)
}
pub fn snap_polygon_to_grid(polygon: &[(f64, f64)], resolution: f64) -> Vec<(f64, f64)> {
polygon
.iter()
.map(|&p| snap_to_grid(p, resolution))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_orient2d_basic() {
let a = (0.0, 0.0);
let b = (1.0, 0.0);
let c = (0.5, 1.0);
assert_eq!(orient2d(a, b, c), Orientation::CounterClockwise);
assert_eq!(orient2d(a, c, b), Orientation::Clockwise);
}
#[test]
fn test_orient2d_collinear() {
let a = (0.0, 0.0);
let b = (1.0, 1.0);
let c = (2.0, 2.0);
assert_eq!(orient2d(a, b, c), Orientation::Collinear);
}
#[test]
fn test_orient2d_near_collinear() {
let a = (0.0, 0.0);
let b = (1.0, 1.0);
let c = (2.0, 2.0 + 1e-15);
let result = orient2d(a, b, c);
assert!(
result == Orientation::Collinear || result == Orientation::CounterClockwise,
"Expected collinear or CCW, got {:?}",
result
);
}
#[test]
fn test_orient2d_filtered_fast_path() {
let a = (0.0, 0.0);
let b = (10.0, 0.0);
let c = (5.0, 10.0);
assert_eq!(orient2d_filtered(a, b, c), Orientation::CounterClockwise);
}
#[test]
fn test_point_in_triangle_robust() {
let a = (0.0, 0.0);
let b = (10.0, 0.0);
let c = (5.0, 10.0);
assert!(point_in_triangle_robust((5.0, 3.0), a, b, c));
assert!(!point_in_triangle_robust((20.0, 5.0), a, b, c));
assert!(!point_in_triangle_robust((5.0, 0.0), a, b, c));
}
#[test]
fn test_point_in_triangle_inclusive() {
let a = (0.0, 0.0);
let b = (10.0, 0.0);
let c = (5.0, 10.0);
assert!(point_in_triangle_inclusive_robust((5.0, 3.0), a, b, c));
assert!(point_in_triangle_inclusive_robust((5.0, 0.0), a, b, c));
assert!(point_in_triangle_inclusive_robust((0.0, 0.0), a, b, c));
assert!(!point_in_triangle_inclusive_robust((20.0, 5.0), a, b, c));
}
#[test]
fn test_is_convex_robust() {
let square = vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
assert!(is_convex_robust(&square));
let triangle = vec![(0.0, 0.0), (10.0, 0.0), (5.0, 10.0)];
assert!(is_convex_robust(&triangle));
let l_shape = vec![
(0.0, 0.0),
(10.0, 0.0),
(10.0, 5.0),
(5.0, 5.0),
(5.0, 10.0),
(0.0, 10.0),
];
assert!(!is_convex_robust(&l_shape));
}
#[test]
fn test_is_ccw_robust() {
let ccw_square = vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
assert!(is_ccw_robust(&ccw_square));
let cw_square = vec![(0.0, 0.0), (0.0, 10.0), (10.0, 10.0), (10.0, 0.0)];
assert!(!is_ccw_robust(&cw_square));
}
#[test]
fn test_signed_area_robust() {
let ccw_square = vec![(0.0, 0.0), (10.0, 0.0), (10.0, 10.0), (0.0, 10.0)];
let area = signed_area_robust(&ccw_square);
assert!((area - 100.0).abs() < 1e-10);
let cw_square = vec![(0.0, 0.0), (0.0, 10.0), (10.0, 10.0), (10.0, 0.0)];
let area = signed_area_robust(&cw_square);
assert!((area + 100.0).abs() < 1e-10);
}
#[test]
fn test_scaling_config() {
let config = ScalingConfig::new(3);
let p = (1.234, 5.678);
let scaled = config.scale_point(p);
assert_eq!(scaled, (1234.0, 5678.0));
let unscaled = config.unscale_point(scaled);
assert!((unscaled.0 - p.0).abs() < 1e-10);
assert!((unscaled.1 - p.1).abs() < 1e-10);
}
#[test]
fn test_snap_to_grid() {
let p = (1.23, 4.56);
let snapped = snap_to_grid(p, 0.5);
assert_eq!(snapped, (1.0, 4.5));
let snapped = snap_to_grid(p, 1.0);
assert_eq!(snapped, (1.0, 5.0));
}
#[test]
fn test_orientation_methods() {
assert!(Orientation::CounterClockwise.is_ccw());
assert!(!Orientation::CounterClockwise.is_cw());
assert!(!Orientation::CounterClockwise.is_collinear());
assert!(!Orientation::Clockwise.is_ccw());
assert!(Orientation::Clockwise.is_cw());
assert!(!Orientation::Clockwise.is_collinear());
assert!(!Orientation::Collinear.is_ccw());
assert!(!Orientation::Collinear.is_cw());
assert!(Orientation::Collinear.is_collinear());
}
#[test]
fn test_degenerate_triangle() {
let a = (0.0, 0.0);
let b = (5.0, 0.0);
let c = (10.0, 0.0);
assert!(!point_in_triangle_robust((5.0, 0.0), a, b, c));
}
#[test]
fn test_extreme_coordinates() {
let a = (1e10, 1e10);
let b = (1e10 + 1.0, 1e10);
let c = (1e10 + 0.5, 1e10 + 1.0);
assert_eq!(orient2d(a, b, c), Orientation::CounterClockwise);
let a = (1e-10, 1e-10);
let b = (1e-10 + 1e-12, 1e-10);
let c = (1e-10 + 5e-13, 1e-10 + 1e-12);
let result = orient2d(a, b, c);
assert!(
result == Orientation::CounterClockwise || result == Orientation::Collinear,
"Unexpected orientation: {:?}",
result
);
}
}