use crate::primitives::cross2d;
pub(crate) fn normalize_edge(a: [i64; 2], b: [i64; 2]) -> ([i64; 2], [i64; 2]) {
if a <= b {
(a, b)
} else {
(b, a)
}
}
pub fn has_exact_shared_edge(a: &[[i64; 2]], b: &[[i64; 2]]) -> bool {
let na = a.len();
let nb = b.len();
for i in 0..na {
let j = (i + 1) % na;
let edge_a = normalize_edge(a[i], a[j]);
for k in 0..nb {
let l = (k + 1) % nb;
let edge_b = normalize_edge(b[k], b[l]);
if edge_a == edge_b {
return true;
}
}
}
false
}
pub fn segments_contact(
ax1: i64,
ay1: i64,
ax2: i64,
ay2: i64,
bx1: i64,
by1: i64,
bx2: i64,
by2: i64,
) -> bool {
let dax = (ax2 as i128) - (ax1 as i128);
let day = (ay2 as i128) - (ay1 as i128);
let dbx = (bx2 as i128) - (bx1 as i128);
let dby = (by2 as i128) - (by1 as i128);
if dax * dby != day * dbx {
return false; }
let collinear = cross2d(ax1, ay1, ax2, ay2, bx1, by1);
if collinear != 0 {
return false;
}
if dax.abs() > day.abs() {
let (a_lo, a_hi) = (ax1.min(ax2), ax1.max(ax2));
let (b_lo, b_hi) = (bx1.min(bx2), bx1.max(bx2));
a_lo.max(b_lo) < a_hi.min(b_hi)
} else {
let (a_lo, a_hi) = (ay1.min(ay2), ay1.max(ay2));
let (b_lo, b_hi) = (by1.min(by2), by1.max(by2));
a_lo.max(b_lo) < a_hi.min(b_hi)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContactKind {
None,
SharedEdge,
PartialContact,
}
pub fn classify_contact(a: &[[i64; 2]], b: &[[i64; 2]]) -> ContactKind {
if has_exact_shared_edge(a, b) {
return ContactKind::SharedEdge;
}
let na = a.len();
let nb = b.len();
for i in 0..na {
let j = (i + 1) % na;
for k in 0..nb {
let l = (k + 1) % nb;
if segments_contact(
a[i][0], a[i][1], a[j][0], a[j][1], b[k][0], b[k][1], b[l][0], b[l][1],
) {
return ContactKind::PartialContact;
}
}
}
ContactKind::None
}
#[cfg(test)]
mod tests {
use super::*;
const M: i64 = 1_000_000;
#[test]
fn shared_edge_normalize_orders_lexicographically() {
assert_eq!(normalize_edge([0, 0], [M, M]), ([0, 0], [M, M]));
assert_eq!(normalize_edge([M, M], [0, 0]), ([0, 0], [M, M]));
}
#[test]
fn shared_edge_normalize_same_x_sorts_by_y() {
assert_eq!(normalize_edge([M, 2 * M], [M, 0]), ([M, 0], [M, 2 * M]));
assert_eq!(normalize_edge([M, 0], [M, 2 * M]), ([M, 0], [M, 2 * M]));
}
#[test]
fn shared_edge_normalize_degenerate_point() {
assert_eq!(normalize_edge([5, 5], [5, 5]), ([5, 5], [5, 5]));
}
#[test]
fn shared_edge_exact_detected() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
assert!(has_exact_shared_edge(&a, &b));
}
#[test]
fn shared_edge_exact_direction_independent() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[M, M], [2 * M, M], [2 * M, 0], [M, 0]];
assert!(has_exact_shared_edge(&a, &b));
}
#[test]
fn shared_edge_no_match_separated_squares() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[3 * M, 0], [4 * M, 0], [4 * M, M], [3 * M, M]];
assert!(!has_exact_shared_edge(&a, &b));
}
#[test]
fn shared_edge_no_match_vertex_only_contact() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[M, M], [2 * M, M], [2 * M, 2 * M], [M, 2 * M]];
assert!(!has_exact_shared_edge(&a, &b));
}
#[test]
fn shared_edge_triangles_sharing_hypotenuse() {
let a = vec![[0, 0], [M, 0], [0, M]];
let b = vec![[M, M], [0, M], [M, 0]];
assert!(has_exact_shared_edge(&a, &b));
}
#[test]
fn shared_edge_segments_collinear_overlap() {
assert!(segments_contact(0, 0, 2 * M, 0, M, 0, 3 * M, 0));
}
#[test]
fn shared_edge_segments_collinear_overlap_vertical() {
assert!(segments_contact(0, 0, 0, 2 * M, 0, M, 0, 3 * M));
}
#[test]
fn shared_edge_segments_collinear_overlap_diagonal() {
assert!(segments_contact(0, 0, 4 * M, 4 * M, M, M, 3 * M, 3 * M));
}
#[test]
fn shared_edge_segments_no_overlap_gap() {
assert!(!segments_contact(0, 0, M, 0, 2 * M, 0, 3 * M, 0));
}
#[test]
fn shared_edge_segments_touching_endpoint_no_overlap() {
assert!(!segments_contact(0, 0, M, 0, M, 0, 2 * M, 0));
}
#[test]
fn shared_edge_segments_not_collinear() {
assert!(!segments_contact(0, 0, M, 0, 0, 0, 0, M));
}
#[test]
fn shared_edge_segments_parallel_not_collinear() {
assert!(!segments_contact(0, 0, M, 0, 0, M, M, M));
}
#[test]
fn shared_edge_segments_reversed_direction() {
assert!(segments_contact(0, 0, 2 * M, 0, 3 * M, 0, M, 0));
}
#[test]
fn shared_edge_segments_degenerate_point() {
assert!(!segments_contact(M, 0, M, 0, 0, 0, 2 * M, 0));
}
#[test]
fn classify_contact_via_exact_edge() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
assert_eq!(classify_contact(&a, &b), ContactKind::SharedEdge);
}
#[test]
fn shared_edge_no_match_partial_overlap_subsegment() {
let a = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
let b = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
assert!(!has_exact_shared_edge(&a, &b));
}
#[test]
fn shared_edge_no_match_collinear_but_disjoint_edges() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[2 * M, 0], [3 * M, 0], [3 * M, M], [2 * M, M]];
assert!(!has_exact_shared_edge(&a, &b));
}
#[test]
fn classify_contact_none_for_separated_squares() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[3 * M, 0], [4 * M, 0], [4 * M, M], [3 * M, M]];
assert_eq!(classify_contact(&a, &b), ContactKind::None);
}
#[test]
fn classify_contact_symmetric_shared_edge() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[M, 0], [2 * M, 0], [2 * M, M], [M, M]];
assert_eq!(classify_contact(&a, &b), classify_contact(&b, &a));
}
#[test]
fn classify_contact_partial_edge_is_t_junction() {
let a = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
let b = vec![[M, M], [3 * M, M], [2 * M, 2 * M]];
assert_eq!(classify_contact(&a, &b), ContactKind::PartialContact);
}
#[test]
fn classify_contact_t_junction_symmetric() {
let a = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
let b = vec![[M, M], [3 * M, M], [2 * M, 2 * M]];
assert_eq!(classify_contact(&a, &b), classify_contact(&b, &a));
}
#[test]
fn classify_contact_vertex_only_is_none() {
let a = vec![[0, 0], [M, 0], [M, M], [0, M]];
let b = vec![[M, M], [2 * M, M], [2 * M, 2 * M], [M, 2 * M]];
assert_eq!(classify_contact(&a, &b), ContactKind::None);
}
#[test]
fn classify_contact_vertex_only_sharing_one_corner_is_none() {
let a = vec![[0, 0], [2 * M, 0], [2 * M, 2 * M], [0, 2 * M]];
let b = vec![
[2 * M, 2 * M],
[4 * M, 2 * M],
[4 * M, 4 * M],
[2 * M, 4 * M],
];
assert_eq!(classify_contact(&a, &b), ContactKind::None);
}
#[test]
fn classify_contact_t_junction_returns_partial_contact() {
let a = vec![[0, 0], [4 * M, 0], [4 * M, M], [0, M]];
let b = vec![[M, M], [3 * M, M], [3 * M, 2 * M], [M, 2 * M]];
assert_eq!(classify_contact(&a, &b), ContactKind::PartialContact);
}
}