#![allow(dead_code)]
use crate::types::Position;
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum SegmentIntersection {
None,
Point(Position),
Overlap(Position, Position),
}
pub(crate) fn intersection(
a1: Position,
a2: Position,
b1: Position,
b2: Position,
no_endpoint_touch: bool,
) -> SegmentIntersection {
let va: Position = [a2[0] - a1[0], a2[1] - a1[1]];
let vb: Position = [b2[0] - b1[0], b2[1] - b1[1]];
let e: Position = [b1[0] - a1[0], b1[1] - a1[1]];
let mut kross = cross_product(va, vb);
let mut sqr_kross = kross * kross;
let sqr_len_a = dot_product(va, va);
if sqr_kross > 0.0 {
let s = cross_product(e, vb) / kross;
if !(0.0..=1.0).contains(&s) {
return SegmentIntersection::None;
}
let t = cross_product(e, va) / kross;
if !(0.0..=1.0).contains(&t) {
return SegmentIntersection::None;
}
if s == 0.0 || s == 1.0 {
return if no_endpoint_touch {
SegmentIntersection::None
} else {
SegmentIntersection::Point(to_point(a1, s, va))
};
}
if t == 0.0 || t == 1.0 {
return if no_endpoint_touch {
SegmentIntersection::None
} else {
SegmentIntersection::Point(to_point(b1, t, vb))
};
}
return SegmentIntersection::Point(to_point(a1, s, va));
}
kross = cross_product(e, va);
sqr_kross = kross * kross;
if sqr_kross > 0.0 {
return SegmentIntersection::None;
}
let sa = dot_product(va, e) / sqr_len_a;
let sb = sa + dot_product(va, vb) / sqr_len_a;
let smin = sa.min(sb);
let smax = sa.max(sb);
if smin <= 1.0 && smax >= 0.0 {
if smin == 1.0 {
return if no_endpoint_touch {
SegmentIntersection::None
} else {
SegmentIntersection::Point(to_point(a1, if smin > 0.0 { smin } else { 0.0 }, va))
};
}
if smax == 0.0 {
return if no_endpoint_touch {
SegmentIntersection::None
} else {
SegmentIntersection::Point(to_point(a1, if smax < 1.0 { smax } else { 1.0 }, va))
};
}
if no_endpoint_touch && smin == 0.0 && smax == 1.0 {
return SegmentIntersection::None;
}
let p1 = to_point(a1, if smin > 0.0 { smin } else { 0.0 }, va);
let p2 = to_point(a1, if smax < 1.0 { smax } else { 1.0 }, va);
return SegmentIntersection::Overlap(p1, p2);
}
SegmentIntersection::None
}
fn cross_product(a: Position, b: Position) -> f64 {
a[0] * b[1] - a[1] * b[0]
}
fn dot_product(a: Position, b: Position) -> f64 {
a[0] * b[0] + a[1] * b[1]
}
fn to_point(p: Position, s: f64, d: Position) -> Position {
[p[0] + s * d[0], p[1] + s * d[1]]
}
#[cfg(test)]
mod tests {
use super::*;
fn assert_point(result: SegmentIntersection, expected: Position) {
match result {
SegmentIntersection::Point(p) => assert_eq!(p, expected),
other => panic!("expected Point({expected:?}), got {other:?}"),
}
}
fn assert_overlap(result: SegmentIntersection, e1: Position, e2: Position) {
match result {
SegmentIntersection::Overlap(p1, p2) => {
assert_eq!(p1, e1, "first overlap endpoint");
assert_eq!(p2, e2, "second overlap endpoint");
}
other => panic!("expected Overlap({e1:?}, {e2:?}), got {other:?}"),
}
}
#[test]
fn no_intersection_when_segments_dont_meet() {
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [1.0, 0.0], [2.0, 2.0], false),
SegmentIntersection::None,
);
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [1.0, 0.0], [10.0, 2.0], false),
SegmentIntersection::None,
);
assert_eq!(
intersection([2.0, 2.0], [3.0, 3.0], [0.0, 6.0], [2.0, 4.0], false),
SegmentIntersection::None,
);
}
#[test]
fn single_intersection_in_interior() {
assert_point(
intersection([0.0, 0.0], [1.0, 1.0], [1.0, 0.0], [0.0, 1.0], false),
[0.5, 0.5],
);
}
#[test]
fn shared_endpoint_points() {
assert_point(
intersection([0.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0], false),
[0.0, 0.0],
);
assert_point(
intersection([0.0, 0.0], [1.0, 1.0], [0.0, 1.0], [1.0, 1.0], false),
[1.0, 1.0],
);
}
#[test]
fn t_crossing() {
assert_point(
intersection([0.0, 0.0], [1.0, 1.0], [0.5, 0.5], [1.0, 0.0], false),
[0.5, 0.5],
);
}
#[test]
fn overlapping_segments() {
assert_overlap(
intersection([0.0, 0.0], [10.0, 10.0], [1.0, 1.0], [5.0, 5.0], false),
[1.0, 1.0],
[5.0, 5.0],
);
assert_overlap(
intersection([1.0, 1.0], [10.0, 10.0], [1.0, 1.0], [5.0, 5.0], false),
[1.0, 1.0],
[5.0, 5.0],
);
assert_overlap(
intersection([3.0, 3.0], [10.0, 10.0], [0.0, 0.0], [5.0, 5.0], false),
[3.0, 3.0],
[5.0, 5.0],
);
assert_overlap(
intersection([0.0, 0.0], [1.0, 1.0], [0.0, 0.0], [1.0, 1.0], false),
[0.0, 0.0],
[1.0, 1.0],
);
assert_overlap(
intersection([1.0, 1.0], [0.0, 0.0], [0.0, 0.0], [1.0, 1.0], false),
[1.0, 1.0],
[0.0, 0.0],
);
}
#[test]
fn collinear_segments_touching_at_endpoint() {
assert_point(
intersection([0.0, 0.0], [1.0, 1.0], [1.0, 1.0], [2.0, 2.0], false),
[1.0, 1.0],
);
assert_point(
intersection([1.0, 1.0], [0.0, 0.0], [1.0, 1.0], [2.0, 2.0], false),
[1.0, 1.0],
);
}
#[test]
fn collinear_segments_disjoint() {
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [2.0, 2.0], [4.0, 4.0], false),
SegmentIntersection::None,
);
}
#[test]
fn parallel_but_not_collinear_segments() {
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [0.0, -1.0], [1.0, 0.0], false),
SegmentIntersection::None,
);
assert_eq!(
intersection([1.0, 1.0], [0.0, 0.0], [0.0, -1.0], [1.0, 0.0], false),
SegmentIntersection::None,
);
assert_eq!(
intersection([0.0, -1.0], [1.0, 0.0], [0.0, 0.0], [1.0, 1.0], false),
SegmentIntersection::None,
);
}
#[test]
fn skip_touches_shared_endpoints() {
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [0.0, 1.0], [0.0, 0.0], true),
SegmentIntersection::None,
);
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [0.0, 1.0], [1.0, 1.0], true),
SegmentIntersection::None,
);
}
#[test]
fn skip_touches_collinear_segments() {
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [1.0, 1.0], [2.0, 2.0], true),
SegmentIntersection::None,
);
assert_eq!(
intersection([1.0, 1.0], [0.0, 0.0], [1.0, 1.0], [2.0, 2.0], true),
SegmentIntersection::None,
);
}
#[test]
fn skip_touches_fully_overlapping_segments() {
assert_eq!(
intersection([0.0, 0.0], [1.0, 1.0], [0.0, 0.0], [1.0, 1.0], true),
SegmentIntersection::None,
);
assert_eq!(
intersection([1.0, 1.0], [0.0, 0.0], [0.0, 0.0], [1.0, 1.0], true),
SegmentIntersection::None,
);
}
#[test]
fn skip_touches_does_not_suppress_real_intersections() {
assert_point(
intersection([0.0, 0.0], [1.0, 1.0], [1.0, 0.0], [0.0, 1.0], true),
[0.5, 0.5],
);
}
}