use crate::primitives::{cross2d, segments_properly_intersect};
pub fn signed_area_2x(ring: &[[i64; 2]]) -> i128 {
let n = ring.len();
if n < 3 {
return 0;
}
let mut sum: i128 = 0;
for i in 0..n {
let j = (i + 1) % n;
let xi = ring[i][0] as i128;
let yi = ring[i][1] as i128;
let xj = ring[j][0] as i128;
let yj = ring[j][1] as i128;
sum += xi * yj - xj * yi;
}
sum
}
pub fn is_ccw(ring: &[[i64; 2]]) -> bool {
signed_area_2x(ring) > 0
}
pub fn ensure_ccw(ring: &mut Vec<[i64; 2]>) {
if !is_ccw(ring) {
ring.reverse();
}
}
pub fn remove_collinear(ring: &[[i64; 2]]) -> Vec<[i64; 2]> {
let n = ring.len();
if n < 3 {
return ring.to_vec();
}
let mut result: Vec<[i64; 2]> = Vec::with_capacity(n);
for i in 0..n {
let prev = ring[(i + n - 1) % n];
let curr = ring[i];
let next = ring[(i + 1) % n];
let cross = cross2d(prev[0], prev[1], curr[0], curr[1], next[0], next[1]);
if cross != 0 {
result.push(curr);
}
}
result
}
pub fn is_simple(ring: &[[i64; 2]]) -> bool {
let n = ring.len();
if n < 3 {
return false;
}
for i in 0..n {
let i1 = i;
let i2 = (i + 1) % n;
let a1 = ring[i1];
let a2 = ring[i2];
for j in (i + 2)..n {
if i == 0 && j == n - 1 {
continue;
}
let j1 = j;
let j2 = (j + 1) % n;
let b1 = ring[j1];
let b2 = ring[j2];
if segments_properly_intersect(a1[0], a1[1], a2[0], a2[1], b1[0], b1[1], b2[0], b2[1]) {
return false;
}
}
}
true
}
pub fn normalize_ring(ring: &[[i64; 2]]) -> Option<Vec<[i64; 2]>> {
let mut result = remove_collinear(ring);
if result.len() < 3 {
return None;
}
ensure_ccw(&mut result);
Some(result)
}
pub fn rotate_ring(ring: &[[i64; 2]], start: usize) -> Vec<[i64; 2]> {
let n = ring.len();
if n == 0 || start == 0 {
return ring.to_vec();
}
let start = start % n;
let mut result = Vec::with_capacity(n);
result.extend_from_slice(&ring[start..]);
result.extend_from_slice(&ring[..start]);
result
}
#[cfg(test)]
mod tests {
use super::*;
const M: i64 = 1_000_000;
fn square_ccw() -> Vec<[i64; 2]> {
vec![[0, 0], [M, 0], [M, M], [0, M]]
}
fn square_cw() -> Vec<[i64; 2]> {
vec![[0, 0], [0, M], [M, M], [M, 0]]
}
#[test]
fn signed_area_ccw_is_positive() {
let ring = square_ccw();
assert!(signed_area_2x(&ring) > 0);
}
#[test]
fn signed_area_cw_is_negative() {
let ring = square_cw();
assert!(signed_area_2x(&ring) < 0);
}
#[test]
fn signed_area_magnitude_is_twice_area() {
let ring = square_ccw();
let area_2x = signed_area_2x(&ring).unsigned_abs();
assert_eq!(area_2x, 2 * (M as u128) * (M as u128));
}
#[test]
fn is_ccw_detects_winding() {
assert!(is_ccw(&square_ccw()));
assert!(!is_ccw(&square_cw()));
}
#[test]
fn ensure_ccw_reverses_cw_ring() {
let mut ring = square_cw();
ensure_ccw(&mut ring);
assert!(is_ccw(&ring));
}
#[test]
fn ensure_ccw_leaves_ccw_ring_unchanged() {
let ccw = square_ccw();
let mut ring = ccw.clone();
ensure_ccw(&mut ring);
assert_eq!(ring, ccw);
}
#[test]
fn remove_collinear_removes_midpoint_on_edge() {
let ring = vec![[0, 0], [M / 2, 0], [M, 0], [M, M], [0, M]];
let result = remove_collinear(&ring);
assert_eq!(result.len(), 4);
assert!(!result.contains(&[M / 2, 0]));
}
#[test]
fn remove_collinear_preserves_non_collinear() {
let ring = square_ccw();
let result = remove_collinear(&ring);
assert_eq!(result, ring); }
#[test]
fn remove_collinear_exact_zero_check() {
let ring = vec![[0, 0], [M, 1], [2 * M, 0], [2 * M, M], [0, M]];
let result = remove_collinear(&ring);
assert_eq!(result.len(), 5);
}
#[test]
fn is_simple_convex_ring() {
assert!(is_simple(&square_ccw()));
}
#[test]
fn is_simple_self_intersecting() {
let ring = vec![[0, 0], [2 * M, 2 * M], [2 * M, 0], [0, 2 * M]];
assert!(!is_simple(&ring));
}
#[test]
fn normalize_ring_removes_collinear_and_ensures_ccw() {
let ring = vec![[0, 0], [0, M], [0, 2 * M], [M, 2 * M], [M, 0]]; let result = normalize_ring(&ring);
assert!(result.is_some());
let normalized = result.unwrap();
assert!(is_ccw(&normalized));
assert_eq!(normalized.len(), 4);
}
#[test]
fn rotate_ring_shifts_start() {
let ring = square_ccw();
let rotated = rotate_ring(&ring, 2);
assert_eq!(rotated[0], [M, M]);
assert_eq!(rotated.len(), 4);
}
#[test]
fn rotate_ring_zero_start_unchanged() {
let ring = square_ccw();
let rotated = rotate_ring(&ring, 0);
assert_eq!(rotated, ring);
}
}