1use crate::primitives::cross2d;
9
10pub(crate) fn normalize_edge(a: [i64; 2], b: [i64; 2]) -> ([i64; 2], [i64; 2]) {
16 if a <= b {
17 (a, b)
18 } else {
19 (b, a)
20 }
21}
22
23pub fn has_exact_shared_edge(a: &[[i64; 2]], b: &[[i64; 2]]) -> bool {
29 let na = a.len();
30 let nb = b.len();
31
32 for i in 0..na {
33 let j = (i + 1) % na;
34 let edge_a = normalize_edge(a[i], a[j]);
35
36 for k in 0..nb {
37 let l = (k + 1) % nb;
38 let edge_b = normalize_edge(b[k], b[l]);
39
40 if edge_a == edge_b {
41 return true;
42 }
43 }
44 }
45
46 false
47}
48
49pub fn segments_contact(
56 ax1: i64,
57 ay1: i64,
58 ax2: i64,
59 ay2: i64,
60 bx1: i64,
61 by1: i64,
62 bx2: i64,
63 by2: i64,
64) -> bool {
65 let dax = (ax2 as i128) - (ax1 as i128);
67 let day = (ay2 as i128) - (ay1 as i128);
68 let dbx = (bx2 as i128) - (bx1 as i128);
69 let dby = (by2 as i128) - (by1 as i128);
70
71 if dax * dby != day * dbx {
72 return false; }
74
75 let collinear = cross2d(ax1, ay1, ax2, ay2, bx1, by1);
77 if collinear != 0 {
78 return false;
79 }
80
81 if dax.abs() > day.abs() {
83 let (a_lo, a_hi) = (ax1.min(ax2), ax1.max(ax2));
85 let (b_lo, b_hi) = (bx1.min(bx2), bx1.max(bx2));
86 a_lo.max(b_lo) < a_hi.min(b_hi)
87 } else {
88 let (a_lo, a_hi) = (ay1.min(ay2), ay1.max(ay2));
90 let (b_lo, b_hi) = (by1.min(by2), by1.max(by2));
91 a_lo.max(b_lo) < a_hi.min(b_hi)
92 }
93}
94
95#[derive(Debug, Clone, Copy, PartialEq, Eq)]
101pub enum ContactKind {
102 None,
106 SharedEdge,
109 PartialContact,
113}
114
115pub fn classify_contact(a: &[[i64; 2]], b: &[[i64; 2]]) -> ContactKind {
128 if has_exact_shared_edge(a, b) {
129 return ContactKind::SharedEdge;
130 }
131
132 let na = a.len();
133 let nb = b.len();
134 for i in 0..na {
135 let j = (i + 1) % na;
136 for k in 0..nb {
137 let l = (k + 1) % nb;
138 if segments_contact(
139 a[i][0], a[i][1], a[j][0], a[j][1], b[k][0], b[k][1], b[l][0], b[l][1],
140 ) {
141 return ContactKind::PartialContact;
142 }
143 }
144 }
145
146 ContactKind::None
147}
148
149#[cfg(test)]
150mod tests {
151 use super::*;
152
153 const M: i64 = 1_000_000;
154
155 #[test]
158 fn shared_edge_normalize_orders_lexicographically() {
159 assert_eq!(normalize_edge([0, 0], [M, M]), ([0, 0], [M, M]));
161 assert_eq!(normalize_edge([M, M], [0, 0]), ([0, 0], [M, M]));
163 }
164
165 #[test]
166 fn shared_edge_normalize_same_x_sorts_by_y() {
167 assert_eq!(normalize_edge([M, 2 * M], [M, 0]), ([M, 0], [M, 2 * M]));
168 assert_eq!(normalize_edge([M, 0], [M, 2 * M]), ([M, 0], [M, 2 * M]));
169 }
170
171 #[test]
172 fn shared_edge_normalize_degenerate_point() {
173 assert_eq!(normalize_edge([5, 5], [5, 5]), ([5, 5], [5, 5]));
175 }
176
177 #[test]
180 fn shared_edge_exact_detected() {
181 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
183 let b = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
184 assert!(has_exact_shared_edge(&a, &b));
185 }
186
187 #[test]
188 fn shared_edge_exact_direction_independent() {
189 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
191 let b = vec![[M, M], [2 * M, M], [2 * M, 0], [M, 0]];
194 assert!(has_exact_shared_edge(&a, &b));
195 }
196
197 #[test]
198 fn shared_edge_no_match_separated_squares() {
199 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
200 let b = vec![[3 * M, 0], [4 * M, 0], [4 * M, M], [3 * M, M]];
201 assert!(!has_exact_shared_edge(&a, &b));
202 }
203
204 #[test]
205 fn shared_edge_no_match_vertex_only_contact() {
206 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
208 let b = vec![[M, M], [2 * M, M], [2 * M, 2 * M], [M, 2 * M]];
209 assert!(!has_exact_shared_edge(&a, &b));
210 }
211
212 #[test]
213 fn shared_edge_triangles_sharing_hypotenuse() {
214 let a = vec![[0, 0], [M, 0], [0, M]];
217 let b = vec![[M, M], [0, M], [M, 0]];
218 assert!(has_exact_shared_edge(&a, &b));
219 }
220
221 #[test]
224 fn shared_edge_segments_collinear_overlap() {
225 assert!(segments_contact(0, 0, 2 * M, 0, M, 0, 3 * M, 0));
227 }
228
229 #[test]
230 fn shared_edge_segments_collinear_overlap_vertical() {
231 assert!(segments_contact(0, 0, 0, 2 * M, 0, M, 0, 3 * M));
233 }
234
235 #[test]
236 fn shared_edge_segments_collinear_overlap_diagonal() {
237 assert!(segments_contact(0, 0, 4 * M, 4 * M, M, M, 3 * M, 3 * M));
239 }
240
241 #[test]
242 fn shared_edge_segments_no_overlap_gap() {
243 assert!(!segments_contact(0, 0, M, 0, 2 * M, 0, 3 * M, 0));
245 }
246
247 #[test]
248 fn shared_edge_segments_touching_endpoint_no_overlap() {
249 assert!(!segments_contact(0, 0, M, 0, M, 0, 2 * M, 0));
251 }
252
253 #[test]
254 fn shared_edge_segments_not_collinear() {
255 assert!(!segments_contact(0, 0, M, 0, 0, 0, 0, M));
257 }
258
259 #[test]
260 fn shared_edge_segments_parallel_not_collinear() {
261 assert!(!segments_contact(0, 0, M, 0, 0, M, M, M));
263 }
264
265 #[test]
266 fn shared_edge_segments_reversed_direction() {
267 assert!(segments_contact(0, 0, 2 * M, 0, 3 * M, 0, M, 0));
269 }
270
271 #[test]
272 fn shared_edge_segments_degenerate_point() {
273 assert!(!segments_contact(M, 0, M, 0, 0, 0, 2 * M, 0));
275 }
276
277 #[test]
280 fn classify_contact_via_exact_edge() {
281 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
282 let b = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
283 assert_eq!(classify_contact(&a, &b), ContactKind::SharedEdge);
284 }
285
286 #[test]
287 fn shared_edge_no_match_partial_overlap_subsegment() {
288 let a = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
289 let b = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
290 assert!(!has_exact_shared_edge(&a, &b));
291 }
292
293 #[test]
294 fn shared_edge_no_match_collinear_but_disjoint_edges() {
295 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
296 let b = vec![[2 * M, 0], [3 * M, 0], [3 * M, M], [2 * M, M]];
297 assert!(!has_exact_shared_edge(&a, &b));
298 }
299
300 #[test]
301 fn classify_contact_none_for_separated_squares() {
302 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
303 let b = vec![[3 * M, 0], [4 * M, 0], [4 * M, M], [3 * M, M]];
304 assert_eq!(classify_contact(&a, &b), ContactKind::None);
305 }
306
307 #[test]
308 fn classify_contact_symmetric_shared_edge() {
309 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
310 let b = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
311 assert_eq!(classify_contact(&a, &b), classify_contact(&b, &a));
312 }
313
314 #[test]
319 fn classify_contact_partial_edge_is_t_junction() {
320 let a = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
321 let b = vec![[M, M], [3 * M, M], [2 * M, 2 * M]];
322 assert_eq!(classify_contact(&a, &b), ContactKind::PartialContact);
323 }
324
325 #[test]
326 fn classify_contact_t_junction_symmetric() {
327 let a = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
328 let b = vec![[M, M], [3 * M, M], [2 * M, 2 * M]];
329 assert_eq!(classify_contact(&a, &b), classify_contact(&b, &a));
330 }
331
332 #[test]
336 fn classify_contact_vertex_only_is_none() {
337 let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
338 let b = vec![[M, M], [2 * M, M], [2 * M, 2 * M], [M, 2 * M]];
339 assert_eq!(classify_contact(&a, &b), ContactKind::None);
340 }
341
342 #[test]
343 fn classify_contact_vertex_only_sharing_one_corner_is_none() {
344 let a = vec![[0, 0], [2 * M, 0], [2 * M, 2 * M], [0, 2 * M]];
345 let b = vec![
346 [2 * M, 2 * M],
347 [4 * M, 2 * M],
348 [4 * M, 4 * M],
349 [2 * M, 4 * M],
350 ];
351 assert_eq!(classify_contact(&a, &b), ContactKind::None);
352 }
353
354 #[test]
355 fn classify_contact_t_junction_returns_partial_contact() {
356 let a = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
357 let b = vec![[M, M], [3 * M, M], [3 * M, 2 * M], [M, 2 * M]];
358 assert_eq!(classify_contact(&a, &b), ContactKind::PartialContact);
359 }
360}