1use crate::primitives::{cross2d, segments_properly_intersect};
12
13pub fn signed_area_2x(ring: &[[i64; 2]]) -> i128 {
17 let n = ring.len();
18 if n < 3 {
19 return 0;
20 }
21 let mut sum: i128 = 0;
22 for i in 0..n {
23 let j = (i + 1) % n;
24 let xi = ring[i][0] as i128;
25 let yi = ring[i][1] as i128;
26 let xj = ring[j][0] as i128;
27 let yj = ring[j][1] as i128;
28 sum += xi * yj - xj * yi;
29 }
30 sum
31}
32
33pub fn is_ccw(ring: &[[i64; 2]]) -> bool {
35 signed_area_2x(ring) > 0
36}
37
38pub fn ensure_ccw(ring: &mut Vec<[i64; 2]>) {
40 if !is_ccw(ring) {
41 ring.reverse();
42 }
43}
44
45pub fn remove_collinear(ring: &[[i64; 2]]) -> Vec<[i64; 2]> {
50 let n = ring.len();
51 if n < 3 {
52 return ring.to_vec();
53 }
54
55 let mut result: Vec<[i64; 2]> = Vec::with_capacity(n);
56
57 for i in 0..n {
58 let prev = ring[(i + n - 1) % n];
59 let curr = ring[i];
60 let next = ring[(i + 1) % n];
61
62 let cross = cross2d(prev[0], prev[1], curr[0], curr[1], next[0], next[1]);
63
64 if cross != 0 {
65 result.push(curr);
66 }
67 }
68
69 result
70}
71
72pub fn is_simple(ring: &[[i64; 2]]) -> bool {
76 let n = ring.len();
77 if n < 3 {
78 return false;
79 }
80
81 for i in 0..n {
82 let i1 = i;
83 let i2 = (i + 1) % n;
84 let a1 = ring[i1];
85 let a2 = ring[i2];
86
87 for j in (i + 2)..n {
88 if i == 0 && j == n - 1 {
90 continue;
91 }
92
93 let j1 = j;
94 let j2 = (j + 1) % n;
95 let b1 = ring[j1];
96 let b2 = ring[j2];
97
98 if segments_properly_intersect(a1[0], a1[1], a2[0], a2[1], b1[0], b1[1], b2[0], b2[1]) {
99 return false;
100 }
101 }
102 }
103
104 true
105}
106
107pub fn normalize_ring(ring: &[[i64; 2]]) -> Option<Vec<[i64; 2]>> {
111 let mut result = remove_collinear(ring);
112 if result.len() < 3 {
113 return None;
114 }
115 ensure_ccw(&mut result);
116 Some(result)
117}
118
119pub fn rotate_ring(ring: &[[i64; 2]], start: usize) -> Vec<[i64; 2]> {
121 let n = ring.len();
122 if n == 0 || start == 0 {
123 return ring.to_vec();
124 }
125 let start = start % n;
126 let mut result = Vec::with_capacity(n);
127 result.extend_from_slice(&ring[start..]);
128 result.extend_from_slice(&ring[..start]);
129 result
130}
131
132#[cfg(test)]
133mod tests {
134 use super::*;
135
136 const M: i64 = 1_000_000; fn square_ccw() -> Vec<[i64; 2]> {
139 vec![[0, 0], [M, 0], [M, M], [0, M]]
140 }
141
142 fn square_cw() -> Vec<[i64; 2]> {
143 vec![[0, 0], [0, M], [M, M], [M, 0]]
144 }
145
146 #[test]
147 fn signed_area_ccw_is_positive() {
148 let ring = square_ccw();
149 assert!(signed_area_2x(&ring) > 0);
150 }
151
152 #[test]
153 fn signed_area_cw_is_negative() {
154 let ring = square_cw();
155 assert!(signed_area_2x(&ring) < 0);
156 }
157
158 #[test]
159 fn signed_area_magnitude_is_twice_area() {
160 let ring = square_ccw();
161 let area_2x = signed_area_2x(&ring).unsigned_abs();
162 assert_eq!(area_2x, 2 * (M as u128) * (M as u128));
164 }
165
166 #[test]
167 fn is_ccw_detects_winding() {
168 assert!(is_ccw(&square_ccw()));
169 assert!(!is_ccw(&square_cw()));
170 }
171
172 #[test]
173 fn ensure_ccw_reverses_cw_ring() {
174 let mut ring = square_cw();
175 ensure_ccw(&mut ring);
176 assert!(is_ccw(&ring));
177 }
178
179 #[test]
180 fn ensure_ccw_leaves_ccw_ring_unchanged() {
181 let ccw = square_ccw();
182 let mut ring = ccw.clone();
183 ensure_ccw(&mut ring);
184 assert_eq!(ring, ccw);
185 }
186
187 #[test]
188 fn remove_collinear_removes_midpoint_on_edge() {
189 let ring = vec![[0, 0], [M / 2, 0], [M, 0], [M, M], [0, M]];
191 let result = remove_collinear(&ring);
192 assert_eq!(result.len(), 4);
194 assert!(!result.contains(&[M / 2, 0]));
195 }
196
197 #[test]
198 fn remove_collinear_preserves_non_collinear() {
199 let ring = square_ccw();
200 let result = remove_collinear(&ring);
201 assert_eq!(result, ring); }
203
204 #[test]
205 fn remove_collinear_exact_zero_check() {
206 let ring = vec![[0, 0], [M, 1], [2 * M, 0], [2 * M, M], [0, M]];
208 let result = remove_collinear(&ring);
209 assert_eq!(result.len(), 5);
211 }
212
213 #[test]
214 fn is_simple_convex_ring() {
215 assert!(is_simple(&square_ccw()));
216 }
217
218 #[test]
219 fn is_simple_self_intersecting() {
220 let ring = vec![[0, 0], [2 * M, 2 * M], [2 * M, 0], [0, 2 * M]];
222 assert!(!is_simple(&ring));
223 }
224
225 #[test]
226 fn normalize_ring_removes_collinear_and_ensures_ccw() {
227 let ring = vec![[0, 0], [0, M], [0, 2 * M], [M, 2 * M], [M, 0]]; let result = normalize_ring(&ring);
230 assert!(result.is_some());
231 let normalized = result.unwrap();
232 assert!(is_ccw(&normalized));
233 assert_eq!(normalized.len(), 4);
235 }
236
237 #[test]
238 fn rotate_ring_shifts_start() {
239 let ring = square_ccw();
240 let rotated = rotate_ring(&ring, 2);
241 assert_eq!(rotated[0], [M, M]);
242 assert_eq!(rotated.len(), 4);
243 }
244
245 #[test]
246 fn rotate_ring_zero_start_unchanged() {
247 let ring = square_ccw();
248 let rotated = rotate_ring(&ring, 0);
249 assert_eq!(rotated, ring);
250 }
251}