1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
7pub enum Orientation {
8 CounterClockwise,
9 Clockwise,
10 Collinear,
11}
12
13pub fn cross2d(ax: i64, ay: i64, bx: i64, by: i64, cx: i64, cy: i64) -> i128 {
18 let dx1 = (bx as i128) - (ax as i128);
19 let dy1 = (by as i128) - (ay as i128);
20 let dx2 = (cx as i128) - (ax as i128);
21 let dy2 = (cy as i128) - (ay as i128);
22
23 dx1 * dy2 - dy1 * dx2
24}
25
26pub fn orientation(ax: i64, ay: i64, bx: i64, by: i64, cx: i64, cy: i64) -> Orientation {
28 match cross2d(ax, ay, bx, by, cx, cy).cmp(&0) {
29 std::cmp::Ordering::Greater => Orientation::CounterClockwise,
30 std::cmp::Ordering::Less => Orientation::Clockwise,
31 std::cmp::Ordering::Equal => Orientation::Collinear,
32 }
33}
34
35pub fn is_left(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
37 cross2d(ax, ay, bx, by, px, py) > 0
38}
39
40pub fn is_left_or_on(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
42 cross2d(ax, ay, bx, by, px, py) >= 0
43}
44
45pub fn is_right(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
47 cross2d(ax, ay, bx, by, px, py) < 0
48}
49
50pub fn is_right_or_on(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
52 cross2d(ax, ay, bx, by, px, py) <= 0
53}
54
55pub fn is_collinear_pts(ax: i64, ay: i64, bx: i64, by: i64, px: i64, py: i64) -> bool {
57 cross2d(ax, ay, bx, by, px, py) == 0
58}
59
60pub fn is_reflex(
64 prev_x: i64,
65 prev_y: i64,
66 curr_x: i64,
67 curr_y: i64,
68 next_x: i64,
69 next_y: i64,
70) -> bool {
71 cross2d(prev_x, prev_y, curr_x, curr_y, next_x, next_y) < 0
72}
73
74pub fn edge_squared_length(ax: i64, ay: i64, bx: i64, by: i64) -> u128 {
77 let dx = (bx as i128) - (ax as i128);
78 let dy = (by as i128) - (ay as i128);
79
80 (dx * dx + dy * dy) as u128
81}
82
83pub fn point_on_segment(px: i64, py: i64, ax: i64, ay: i64, bx: i64, by: i64) -> bool {
85 if cross2d(ax, ay, bx, by, px, py) != 0 {
86 return false;
87 }
88
89 px >= ax.min(bx) && px <= ax.max(bx) && py >= ay.min(by) && py <= ay.max(by)
90}
91
92pub fn segments_properly_intersect(
96 a1x: i64,
97 a1y: i64,
98 a2x: i64,
99 a2y: i64,
100 b1x: i64,
101 b1y: i64,
102 b2x: i64,
103 b2y: i64,
104) -> bool {
105 let d1 = cross2d(b1x, b1y, b2x, b2y, a1x, a1y);
106 let d2 = cross2d(b1x, b1y, b2x, b2y, a2x, a2y);
107 let d3 = cross2d(a1x, a1y, a2x, a2y, b1x, b1y);
108 let d4 = cross2d(a1x, a1y, a2x, a2y, b2x, b2y);
109
110 ((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))
111}
112
113pub fn segments_intersect(
115 a1x: i64,
116 a1y: i64,
117 a2x: i64,
118 a2y: i64,
119 b1x: i64,
120 b1y: i64,
121 b2x: i64,
122 b2y: i64,
123) -> bool {
124 if segments_properly_intersect(a1x, a1y, a2x, a2y, b1x, b1y, b2x, b2y) {
125 return true;
126 }
127
128 point_on_segment(b1x, b1y, a1x, a1y, a2x, a2y)
129 || point_on_segment(b2x, b2y, a1x, a1y, a2x, a2y)
130 || point_on_segment(a1x, a1y, b1x, b1y, b2x, b2y)
131 || point_on_segment(a2x, a2y, b1x, b1y, b2x, b2y)
132}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 const M: i64 = 1_000_000;
139 const MAX_WORLD: i64 = 40_075_017_000_000;
140
141 #[test]
142 fn cross2d_sign_and_collinearity_cases() {
143 assert!(cross2d(0, 0, M, 0, 0, M) > 0);
144 assert!(cross2d(0, 0, 0, M, M, 0) < 0);
145 assert_eq!(cross2d(0, 0, M, 0, 2 * M, 0), 0);
146 }
147
148 #[test]
149 fn cross2d_matches_expected_magnitude() {
150 assert_eq!(
151 cross2d(0, 0, 3 * M, 0, 0, 4 * M),
152 12 * (M as i128) * (M as i128)
153 );
154 assert_eq!(cross2d(-M, -M, M, -M, -M, M), 4 * (M as i128) * (M as i128));
155 assert_eq!(
156 cross2d(5 * M, 5 * M, 6 * M, 7 * M, 9 * M, 12 * M),
157 -(M as i128) * (M as i128)
158 );
159 }
160
161 #[test]
162 fn cross2d_handles_max_world_without_overflow() {
163 let result = cross2d(0, 0, MAX_WORLD, 0, 0, MAX_WORLD);
164 let expected = (MAX_WORLD as i128) * (MAX_WORLD as i128);
165 assert_eq!(result, expected);
166 assert!(cross2d(-MAX_WORLD, 0, MAX_WORLD, 0, 0, MAX_WORLD) > 0);
167 assert_eq!(
168 cross2d(0, 0, MAX_WORLD, MAX_WORLD, 2 * MAX_WORLD, 2 * MAX_WORLD),
169 0
170 );
171 }
172
173 #[test]
174 fn orientation_classifies_three_cases() {
175 assert_eq!(orientation(0, 0, M, 0, 0, M), Orientation::CounterClockwise);
176 assert_eq!(orientation(0, 0, 0, M, M, 0), Orientation::Clockwise);
177 assert_eq!(
178 orientation(0, 0, M, M, 2 * M, 2 * M),
179 Orientation::Collinear
180 );
181 }
182
183 #[test]
184 fn orientation_handles_shifted_and_negative_points() {
185 assert_eq!(
186 orientation(-M, -M, M, -M, 0, M),
187 Orientation::CounterClockwise
188 );
189 assert_eq!(orientation(-M, -M, 0, M, M, -M), Orientation::Clockwise);
190 assert_eq!(
191 orientation(-2 * M, -2 * M, -M, -M, 0, 0),
192 Orientation::Collinear
193 );
194 }
195
196 #[test]
197 fn orientation_handles_degenerate_repeated_points() {
198 assert_eq!(orientation(0, 0, 0, 0, M, M), Orientation::Collinear);
199 assert_eq!(orientation(0, 0, M, M, M, M), Orientation::Collinear);
200 assert_eq!(orientation(7, 11, 7, 11, 7, 11), Orientation::Collinear);
201 }
202
203 #[test]
204 fn side_predicates_classify_left_right_and_on() {
205 assert!(is_left(0, 0, M, 0, 0, M));
206 assert!(!is_left(0, 0, M, 0, M, 0));
207 assert!(!is_left(0, 0, M, 0, 0, -M));
208
209 assert!(is_left_or_on(0, 0, M, 0, 0, M));
210 assert!(is_left_or_on(0, 0, M, 0, M, 0));
211 assert!(!is_left_or_on(0, 0, M, 0, 0, -M));
212
213 assert!(is_right(0, 0, M, 0, 0, -M));
214 assert!(!is_right(0, 0, M, 0, M, 0));
215 assert!(!is_right(0, 0, M, 0, 0, M));
216
217 assert!(is_right_or_on(0, 0, M, 0, 0, -M));
218 assert!(is_right_or_on(0, 0, M, 0, M, 0));
219 assert!(!is_right_or_on(0, 0, M, 0, 0, M));
220
221 assert!(is_collinear_pts(0, 0, M, M, 2 * M, 2 * M));
222 assert!(is_collinear_pts(0, 0, 2 * M, 0, M, 0));
223 assert!(!is_collinear_pts(0, 0, M, 0, M, M));
224 }
225
226 #[test]
227 fn side_predicates_work_with_vertical_lines() {
228 assert!(is_left(0, 0, 0, M, -M, 0));
229 assert!(is_left_or_on(0, 0, 0, M, 0, M / 2));
230 assert!(is_right(0, 0, 0, M, M, 0));
231 assert!(is_right_or_on(0, 0, 0, M, 0, M / 2));
232 assert!(is_collinear_pts(0, 0, 0, 2 * M, 0, M));
233 }
234
235 #[test]
236 fn side_predicates_work_with_negative_coordinates() {
237 assert!(is_left(-M, -M, M, -M, 0, 0));
238 assert!(is_left_or_on(-M, -M, M, -M, -M, -M));
239 assert!(is_right(-M, -M, M, -M, 0, -2 * M));
240 assert!(is_right_or_on(-M, -M, M, -M, M, -M));
241 assert!(!is_collinear_pts(-M, -M, M, -M, 0, 0));
242 }
243
244 #[test]
245 fn is_reflex_distinguishes_concave_and_convex_vertices() {
246 assert!(is_reflex(0, 0, M, M, 2 * M, 0));
247 assert!(!is_reflex(0, 2 * M, 0, 0, 2 * M, 0));
248 assert!(!is_reflex(0, 0, M, 0, 2 * M, 0));
249 }
250
251 #[test]
252 fn is_reflex_handles_shifted_vertices() {
253 assert!(is_reflex(-2 * M, -2 * M, -M, -M, 0, -2 * M));
254 assert!(!is_reflex(-2 * M, -2 * M, -M, -2 * M, -M, -M));
255 assert!(!is_reflex(5 * M, 5 * M, 6 * M, 6 * M, 7 * M, 7 * M));
256 }
257
258 #[test]
259 fn is_reflex_uses_exact_orientation_semantics() {
260 assert_eq!(is_reflex(0, 0, M, 0, M, M), false);
261 assert_eq!(is_reflex(0, 0, M, 0, M, -M), true);
262 assert_eq!(is_reflex(0, 0, M, 0, 2 * M, 0), false);
263 }
264
265 #[test]
266 fn edge_squared_length_matches_known_distances() {
267 assert_eq!(
268 edge_squared_length(0, 0, 3 * M, 4 * M),
269 25 * (M as u128) * (M as u128)
270 );
271 assert_eq!(edge_squared_length(0, 0, M, 0), (M as u128) * (M as u128));
272 assert_eq!(
273 edge_squared_length(-M, -M, M, M),
274 8 * (M as u128) * (M as u128)
275 );
276 }
277
278 #[test]
279 fn edge_squared_length_is_symmetric_and_zero_for_same_point() {
280 let ab = edge_squared_length(-7, 11, 13, -17);
281 let ba = edge_squared_length(13, -17, -7, 11);
282 assert_eq!(ab, ba);
283 assert_eq!(edge_squared_length(5, 5, 5, 5), 0);
284 assert_eq!(
285 edge_squared_length(-M, 0, -M, 3 * M),
286 9 * (M as u128) * (M as u128)
287 );
288 }
289
290 #[test]
291 fn edge_squared_length_handles_max_world() {
292 let len_sq = edge_squared_length(0, 0, MAX_WORLD, MAX_WORLD);
293 assert_eq!(len_sq, 2 * (MAX_WORLD as u128) * (MAX_WORLD as u128));
294 assert!(len_sq > 0);
295 assert_eq!(
296 edge_squared_length(-MAX_WORLD, 0, MAX_WORLD, 0),
297 4 * (MAX_WORLD as u128) * (MAX_WORLD as u128)
298 );
299 }
300
301 #[test]
302 fn point_on_segment_handles_midpoint_endpoint_and_outside() {
303 assert!(point_on_segment(M, 0, 0, 0, 2 * M, 0));
304 assert!(point_on_segment(0, 0, 0, 0, 2 * M, 0));
305 assert!(!point_on_segment(3 * M, 0, 0, 0, 2 * M, 0));
306 }
307
308 #[test]
309 fn point_on_segment_handles_diagonal_and_vertical_segments() {
310 assert!(point_on_segment(M, M, 0, 0, 2 * M, 2 * M));
311 assert!(point_on_segment(0, M, 0, 0, 0, 2 * M));
312 assert!(!point_on_segment(M, M + 1, 0, 0, 2 * M, 2 * M));
313 }
314
315 #[test]
316 fn point_on_segment_handles_reversed_bounds() {
317 assert!(point_on_segment(M, 0, 2 * M, 0, 0, 0));
318 assert!(point_on_segment(0, M, 0, 2 * M, 0, 0));
319 assert!(!point_on_segment(-1, 0, 2 * M, 0, 0, 0));
320 }
321
322 #[test]
323 fn segments_properly_intersect_detects_crossings() {
324 assert!(segments_properly_intersect(
325 0,
326 0,
327 2 * M,
328 2 * M,
329 0,
330 2 * M,
331 2 * M,
332 0
333 ));
334 assert!(segments_properly_intersect(0, 0, 3 * M, 0, M, -M, M, M));
335 assert!(segments_properly_intersect(-M, -M, M, M, -M, M, M, -M));
336 }
337
338 #[test]
339 fn segments_properly_intersect_excludes_parallel_touching_and_collinear_cases() {
340 assert!(!segments_properly_intersect(0, 0, 2 * M, 0, 0, M, 2 * M, M));
341 assert!(!segments_properly_intersect(0, 0, 2 * M, 0, M, 0, M, 2 * M));
342 assert!(!segments_properly_intersect(0, 0, 2 * M, 0, M, 0, 3 * M, 0));
343 }
344
345 #[test]
346 fn segments_properly_intersect_excludes_separated_segments() {
347 assert!(!segments_properly_intersect(
348 0,
349 0,
350 M,
351 M,
352 2 * M,
353 2 * M,
354 3 * M,
355 3 * M
356 ));
357 assert!(!segments_properly_intersect(0, 0, 0, M, M, 0, M, M));
358 assert!(!segments_properly_intersect(
359 -2 * M,
360 0,
361 -M,
362 0,
363 M,
364 0,
365 2 * M,
366 0
367 ));
368 }
369
370 #[test]
371 fn segments_intersect_detects_proper_and_endpoint_intersections() {
372 assert!(segments_intersect(0, 0, 2 * M, 2 * M, 0, 2 * M, 2 * M, 0));
373 assert!(segments_intersect(0, 0, 2 * M, 0, M, 0, M, 2 * M));
374 assert!(segments_intersect(0, 0, 2 * M, 0, 2 * M, 0, 3 * M, 0));
375 }
376
377 #[test]
378 fn segments_intersect_detects_collinear_overlap_and_containment() {
379 assert!(segments_intersect(0, 0, 4 * M, 0, M, 0, 3 * M, 0));
380 assert!(segments_intersect(M, 0, 3 * M, 0, 0, 0, 4 * M, 0));
381 assert!(segments_intersect(0, 0, 0, 4 * M, 0, M, 0, 3 * M));
382 }
383
384 #[test]
385 fn segments_intersect_excludes_disjoint_segments() {
386 assert!(!segments_intersect(0, 0, M, 0, 2 * M, 0, 3 * M, 0));
387 assert!(!segments_intersect(0, 0, 0, M, M, 0, M, M));
388 assert!(!segments_intersect(
389 -3 * M,
390 -3 * M,
391 -2 * M,
392 -2 * M,
393 2 * M,
394 2 * M,
395 3 * M,
396 3 * M
397 ));
398 }
399}