use core::cmp::Ordering;
use geo_types::Coord;
use crate::vertex::Vertex;
#[inline]
pub(crate) fn orient(a: Coord<i32>, b: Coord<i32>, c: Coord<i32>) -> Ordering {
let ex1 = i64::from(b.x) - i64::from(a.x);
let ey1 = i64::from(b.y) - i64::from(a.y);
let ex2 = i64::from(c.x) - i64::from(a.x);
let ey2 = i64::from(c.y) - i64::from(a.y);
if fits_i32(ex1) && fits_i32(ey1) && fits_i32(ex2) && fits_i32(ey2) {
(ex1 * ey2).cmp(&(ey1 * ex2))
} else {
(i128::from(ex1) * i128::from(ey2)).cmp(&(i128::from(ey1) * i128::from(ex2)))
}
}
#[inline]
fn fits_i32(v: i64) -> bool {
i32::try_from(v).is_ok()
}
#[expect(
clippy::cast_possible_truncation,
reason = "the offset is dx·t/dy with |t| ≤ |dy|, so it fits i64"
)]
pub(crate) fn crossing_x_ceil(a: Coord<i32>, b: Coord<i32>, y: i32) -> i64 {
let dx = i64::from(b.x) - i64::from(a.x);
let mut dy = i64::from(b.y) - i64::from(a.y);
let mut t = i64::from(y) - i64::from(a.y);
if dy < 0 {
dy = -dy;
t = -t;
}
let offset = if fits_i32(dx) && fits_i32(t) {
-(-(dx * t)).div_euclid(dy)
} else {
(-(-(i128::from(dx) * i128::from(t))).div_euclid(i128::from(dy))) as i64
};
i64::from(a.x) + offset
}
#[must_use]
pub fn signed_area_2x<V: Vertex>(ring: &[V]) -> i128 {
let Some(v0) = ring.first().map(Vertex::position) else {
return 0;
};
let mut area2: i128 = 0;
for w in ring.windows(2) {
let (p, q) = (w[0].position(), w[1].position());
let px = i128::from(p.x) - i128::from(v0.x);
let py = i128::from(p.y) - i128::from(v0.y);
let qx = i128::from(q.x) - i128::from(v0.x);
let qy = i128::from(q.y) - i128::from(v0.y);
area2 += px * qy - py * qx;
}
area2
}
pub(crate) fn ring_orientation<V: Vertex>(ring: &[V]) -> Ordering {
signed_area_2x(ring).cmp(&0)
}
pub(crate) fn point_in_ring(p: Coord<i32>, ring: &[Coord<i32>]) -> bool {
let n = ring.len();
if n < 3 {
return false;
}
let m = if ring[n - 1] == ring[0] { n - 1 } else { n };
if m < 3 {
return false;
}
let mut inside = false;
let mut prev = m - 1;
for i in 0..m {
let tail = ring[prev];
let head = ring[i];
if orient(tail, head, p) == Ordering::Equal
&& p.x >= tail.x.min(head.x)
&& p.x <= tail.x.max(head.x)
&& p.y >= tail.y.min(head.y)
&& p.y <= tail.y.max(head.y)
{
return true;
}
if (tail.y > p.y) != (head.y > p.y) {
let left = orient(tail, head, p) == Ordering::Greater;
if left == (head.y > tail.y) {
inside = !inside;
}
}
prev = i;
}
inside
}
#[cfg(test)]
mod tests {
use geo_types::Coord;
use super::*;
fn c(x: i32, y: i32) -> Coord<i32> {
Coord { x, y }
}
#[test]
fn degenerate_ring_contains_nothing() {
assert!(!point_in_ring(c(0, 0), &[c(0, 0), c(5, 5)]));
assert!(!point_in_ring(c(0, 0), &[c(0, 0), c(5, 5), c(0, 0)]));
}
#[test]
fn orient_basic_and_extremes() {
assert_eq!(orient(c(0, 0), c(1, 0), c(0, 1)), Ordering::Greater); assert_eq!(orient(c(0, 0), c(1, 0), c(0, -1)), Ordering::Less); assert_eq!(orient(c(0, 0), c(2, 2), c(1, 1)), Ordering::Equal); assert_eq!(
orient(
c(i32::MIN, i32::MIN),
c(i32::MAX, i32::MIN),
c(i32::MIN, i32::MAX)
),
Ordering::Greater
);
assert_eq!(
orient(
c(i32::MIN, i32::MIN),
c(i32::MAX, i32::MAX),
c(i32::MIN + 1, i32::MIN + 1)
),
Ordering::Equal
);
}
#[test]
fn crossing_x_ceil_is_exact() {
assert_eq!(crossing_x_ceil(c(0, 0), c(10, 3), 1), 4);
assert_eq!(crossing_x_ceil(c(10, 3), c(0, 0), 1), 4);
assert_eq!(crossing_x_ceil(c(0, 0), c(10, 2), 1), 5);
assert_eq!(crossing_x_ceil(c(0, 0), c(-10, 3), 1), -3);
let x = crossing_x_ceil(c(i32::MIN, i32::MIN), c(i32::MAX, i32::MAX), 0);
assert_eq!(x, 0);
let x = crossing_x_ceil(c(i32::MIN, i32::MAX), c(i32::MAX, i32::MIN), 1);
assert_eq!(x, -2);
let x = crossing_x_ceil(c(i32::MIN, i32::MAX), c(i32::MAX - 1, i32::MIN), 1);
assert_eq!(x, -2); }
#[test]
fn ring_orientation_ccw_cw() {
let ccw = [c(0, 0), c(4, 0), c(4, 4), c(0, 4), c(0, 0)];
let cw = [c(0, 0), c(0, 4), c(4, 4), c(4, 0), c(0, 0)];
assert_eq!(ring_orientation(&ccw), Ordering::Greater);
assert_eq!(ring_orientation(&cw), Ordering::Less);
assert_eq!(ring_orientation(&ccw[..2]), Ordering::Equal); assert_eq!(signed_area_2x(&ccw), 32);
assert_eq!(signed_area_2x(&ccw[..4]), 32);
assert_eq!(signed_area_2x(&cw), -32);
assert_eq!(signed_area_2x::<Coord<i32>>(&[]), 0);
let big = [
c(i32::MIN, i32::MIN),
c(i32::MAX, i32::MIN),
c(i32::MAX, i32::MAX),
];
let side = i128::from(i32::MAX) - i128::from(i32::MIN);
assert_eq!(signed_area_2x(&big), side * side);
}
#[test]
fn point_in_ring_square() {
let sq = [c(0, 0), c(10, 0), c(10, 10), c(0, 10), c(0, 0)];
assert!(point_in_ring(c(5, 5), &sq)); assert!(!point_in_ring(c(15, 5), &sq)); assert!(point_in_ring(c(0, 5), &sq)); assert!(point_in_ring(c(10, 10), &sq)); assert!(!point_in_ring(c(-1, 5), &sq));
}
#[test]
fn point_in_ring_concave() {
let c_shape = [
c(0, 0),
c(10, 0),
c(10, 3),
c(3, 3),
c(3, 7),
c(10, 7),
c(10, 10),
c(0, 10),
c(0, 0),
];
assert!(point_in_ring(c(1, 5), &c_shape)); assert!(!point_in_ring(c(6, 5), &c_shape)); assert!(point_in_ring(c(6, 1), &c_shape)); }
}