use geometry_coords::CoordinateScalar;
use geometry_trait::{Point, PointMut, Segment as SegmentTrait, segment_end, segment_start};
use super::orientation::{Sign, orientation_2d};
use super::range_guard::coordinate_in_range;
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum SegmentIntersection<P> {
Disjoint,
Single(P),
Collinear {
from: P,
to: P,
},
OutOfRange,
}
#[must_use]
pub fn segment_intersection<S, P>(a: &S, b: &S) -> SegmentIntersection<P>
where
S: SegmentTrait<Point = P>,
P: PointMut + Default,
P::Scalar: CoordinateScalar + Into<f64>,
{
let p1 = segment_start(a);
let p2 = segment_end(a);
let p3 = segment_start(b);
let p4 = segment_end(b);
if !(coordinate_in_range(&p1)
&& coordinate_in_range(&p2)
&& coordinate_in_range(&p3)
&& coordinate_in_range(&p4))
{
return SegmentIntersection::OutOfRange;
}
let d1 = orientation_2d(&p3, &p4, &p1);
let d2 = orientation_2d(&p3, &p4, &p2);
let d3 = orientation_2d(&p1, &p2, &p3);
let d4 = orientation_2d(&p1, &p2, &p4);
if straddles(d1, d2) && straddles(d3, d4) {
return SegmentIntersection::Single(line_cross_point(&p1, &p2, &p3, &p4));
}
if d1 == Sign::Collinear
&& d2 == Sign::Collinear
&& d3 == Sign::Collinear
&& d4 == Sign::Collinear
{
return collinear_overlap(&p1, &p2, &p3, &p4);
}
if d1 == Sign::Collinear && on_segment(&p1, &p3, &p4) {
return SegmentIntersection::Single(clone_point(&p1));
}
if d2 == Sign::Collinear && on_segment(&p2, &p3, &p4) {
return SegmentIntersection::Single(clone_point(&p2));
}
if d3 == Sign::Collinear && on_segment(&p3, &p1, &p2) {
return SegmentIntersection::Single(clone_point(&p3));
}
if d4 == Sign::Collinear && on_segment(&p4, &p1, &p2) {
return SegmentIntersection::Single(clone_point(&p4));
}
SegmentIntersection::Disjoint
}
fn straddles(a: Sign, b: Sign) -> bool {
matches!(
(a, b),
(Sign::Positive, Sign::Negative) | (Sign::Negative, Sign::Positive)
)
}
fn make_point<P>(x: P::Scalar, y: P::Scalar) -> P
where
P: PointMut + Default,
{
let mut p = P::default();
p.set::<0>(x);
p.set::<1>(y);
p
}
fn clone_point<P>(src: &P) -> P
where
P: PointMut + Default,
{
make_point::<P>(src.get::<0>(), src.get::<1>())
}
fn line_cross_point<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> P
where
P: PointMut + Default,
P::Scalar: CoordinateScalar,
{
let x1 = p1.get::<0>();
let y1 = p1.get::<1>();
let x2 = p2.get::<0>();
let y2 = p2.get::<1>();
let x3 = p3.get::<0>();
let y3 = p3.get::<1>();
let x4 = p4.get::<0>();
let y4 = p4.get::<1>();
let denom = (x1 - x2) * (y3 - y4) - (y1 - y2) * (x3 - x4);
let a = x1 * y2 - y1 * x2;
let b = x3 * y4 - y3 * x4;
let px = (a * (x3 - x4) - (x1 - x2) * b) / denom;
let py = (a * (y3 - y4) - (y1 - y2) * b) / denom;
make_point::<P>(px, py)
}
fn on_segment<P>(p: &P, s1: &P, s2: &P) -> bool
where
P: Point,
P::Scalar: CoordinateScalar,
{
let (px, py) = (p.get::<0>(), p.get::<1>());
let (ax, ay) = (s1.get::<0>(), s1.get::<1>());
let (bx, by) = (s2.get::<0>(), s2.get::<1>());
min(ax, bx) <= px && px <= max(ax, bx) && min(ay, by) <= py && py <= max(ay, by)
}
fn collinear_overlap<P>(p1: &P, p2: &P, p3: &P, p4: &P) -> SegmentIntersection<P>
where
P: PointMut + Default,
P::Scalar: CoordinateScalar,
{
let spread_x = (p1.get::<0>() - p2.get::<0>()).abs();
let spread_y = (p1.get::<1>() - p2.get::<1>()).abs();
let project_on_x = spread_x >= spread_y;
let key = |p: &P| -> P::Scalar {
if project_on_x {
p.get::<0>()
} else {
p.get::<1>()
}
};
let (a_lo, a_hi) = ordered(p1, p2, &key);
let (b_lo, b_hi) = ordered(p3, p4, &key);
let lo = if key(a_lo) >= key(b_lo) { a_lo } else { b_lo };
let hi = if key(a_hi) <= key(b_hi) { a_hi } else { b_hi };
if key(lo) > key(hi) {
SegmentIntersection::Disjoint
} else if key(lo) == key(hi) {
SegmentIntersection::Single(clone_point(lo))
} else {
SegmentIntersection::Collinear {
from: clone_point(lo),
to: clone_point(hi),
}
}
}
fn ordered<'p, P, F>(a: &'p P, b: &'p P, key: &F) -> (&'p P, &'p P)
where
P: Point,
F: Fn(&P) -> P::Scalar,
{
if key(a) <= key(b) { (a, b) } else { (b, a) }
}
fn min<T: CoordinateScalar>(a: T, b: T) -> T {
if a <= b { a } else { b }
}
fn max<T: CoordinateScalar>(a: T, b: T) -> T {
if a >= b { a } else { b }
}
#[cfg(test)]
mod tests {
use super::{SegmentIntersection, segment_intersection};
use geometry_cs::Cartesian;
use geometry_model::{Point2D, Segment};
type P = Point2D<f64, Cartesian>;
type Seg = Segment<P>;
#[test]
fn proper_crossing() {
let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 2.0));
let b = Seg::new(P::new(0.0, 2.0), P::new(2.0, 0.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Single(P::new(1.0, 1.0))
);
}
#[test]
fn t_junction_at_endpoint() {
let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
let b = Seg::new(P::new(2.0, 0.0), P::new(2.0, 3.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Single(P::new(2.0, 0.0))
);
}
#[test]
fn collinear_overlap() {
let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
let b = Seg::new(P::new(2.0, 0.0), P::new(6.0, 0.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Collinear {
from: P::new(2.0, 0.0),
to: P::new(4.0, 0.0),
}
);
}
#[test]
fn collinear_touching_at_one_point() {
let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
let b = Seg::new(P::new(4.0, 0.0), P::new(8.0, 0.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Single(P::new(4.0, 0.0))
);
}
#[test]
fn collinear_disjoint() {
let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 0.0));
let b = Seg::new(P::new(5.0, 0.0), P::new(7.0, 0.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Disjoint
);
}
#[test]
fn parallel_disjoint() {
let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 0.0));
let b = Seg::new(P::new(0.0, 1.0), P::new(4.0, 1.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Disjoint
);
}
#[test]
fn skew_disjoint() {
let a = Seg::new(P::new(0.0, 0.0), P::new(1.0, 0.0));
let b = Seg::new(P::new(3.0, 1.0), P::new(3.0, 2.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Disjoint
);
}
#[test]
fn out_of_range_endpoint_refused() {
let a = Seg::new(P::new(0.0, 0.0), P::new(2.0, 2.0));
let b = Seg::new(P::new(0.0, 2.0), P::new(2.0e9, 0.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::OutOfRange
);
}
#[test]
fn off_center_crossing_point() {
let a = Seg::new(P::new(0.0, 0.0), P::new(4.0, 4.0));
let b = Seg::new(P::new(0.0, 4.0), P::new(4.0, 0.0));
assert_eq!(
segment_intersection::<Seg, P>(&a, &b),
SegmentIntersection::Single(P::new(2.0, 2.0))
);
}
}