extern crate geo;
extern crate num_traits;
use geo::{Line, Point};
use num_traits::Float;
#[derive(Debug, PartialEq)]
pub struct LineInterval<T: Float> {
pub line: Line<T>,
pub interval_of_intersection: (T, T),
}
#[derive(Debug, PartialEq)]
pub enum LineRelation<T: Float> {
DivergentIntersecting(Point<T>),
DivergentDisjoint,
Collinear,
Parallel,
}
impl<T: Float> LineRelation<T> {
pub fn unique_intersection(self) -> Option<Point<T>> {
match self {
LineRelation::DivergentIntersecting(p) => Some(p),
_ => None,
}
}
}
impl<T: Float> LineInterval<T> {
pub fn line_segment(line: Line<T>) -> LineInterval<T> {
LineInterval {
line: line,
interval_of_intersection: (T::zero(), T::one()),
}
}
pub fn ray(line: Line<T>) -> LineInterval<T> {
LineInterval {
line: line,
interval_of_intersection: (T::zero(), T::infinity()),
}
}
pub fn line(line: Line<T>) -> LineInterval<T> {
LineInterval {
line: line,
interval_of_intersection: (T::neg_infinity(), T::infinity()),
}
}
pub fn relate(&self, other: &LineInterval<T>) -> LineRelation<T> {
let p = self.line.start;
let q = other.line.start;
let r = self.line.end - self.line.start;
let s = other.line.end - other.line.start;
let r_cross_s = Self::cross(&r, &s);
let q_minus_p = q - p;
let q_minus_p_cross_r = Self::cross(&q_minus_p, &r);
if r_cross_s == T::zero() {
if q_minus_p_cross_r == T::zero() {
LineRelation::Collinear
} else {
LineRelation::Parallel
}
} else {
let t = Self::cross(&q_minus_p, &Self::div(&s, r_cross_s));
let u = Self::cross(&q_minus_p, &Self::div(&r, r_cross_s));
let t_in_range = self.interval_of_intersection.0 <= t &&
t <= self.interval_of_intersection.1;
let u_in_range = other.interval_of_intersection.0 <= u &&
u <= other.interval_of_intersection.1;
if t_in_range && u_in_range {
LineRelation::DivergentIntersecting(Self::t_coord_to_point(&p, &r, t))
} else {
LineRelation::DivergentDisjoint
}
}
}
fn cross(a: &Point<T>, b: &Point<T>) -> T {
a.x() * b.y() - a.y() * b.x()
}
fn div(a: &Point<T>, b: T) -> Point<T> {
(a.x() / b, a.y() / b).into()
}
fn t_coord_to_point(p: &Point<T>, r: &Point<T>, t: T) -> Point<T> {
(p.x() + t * r.x(), p.y() + t * r.y()).into()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn divergent_intersecting_segments() {
let a = Line {
start: (1.0, 0.0).into(),
end: (1.0, 1.0).into(),
};
let b = Line {
start: (0.0, 0.0).into(),
end: (2.0, 0.5).into(),
};
let s1 = LineInterval::line_segment(a);
let s2 = LineInterval::line_segment(b);
let relation = LineRelation::DivergentIntersecting((1.0, 0.25).into());
assert_eq!(relation, s1.relate(&s2));
assert_eq!(relation, s2.relate(&s1));
}
#[test]
fn divergent_intersecting_segment_and_ray() {
let a = Line {
start: (0.0, 0.0).into(),
end: (1.0, 1.0).into(),
};
let b = Line {
start: (2.0, 0.0).into(),
end: (2.0, 3.0).into(),
};
let s1 = LineInterval::ray(a);
let s2 = LineInterval::line_segment(b);
let relation = LineRelation::DivergentIntersecting((2.0, 2.0).into());
assert_eq!(relation, s1.relate(&s2));
assert_eq!(relation, s2.relate(&s1));
}
#[test]
fn divergent_disjoint_segments() {
let a = Line {
start: (0.0, 0.0).into(),
end: (1.0, 1.0).into(),
};
let b = Line {
start: (3.0, 0.0).into(),
end: (0.0, 3.0).into(),
};
let s1 = LineInterval::line_segment(a);
let s2 = LineInterval::line_segment(b);
let relation = LineRelation::DivergentDisjoint;
assert_eq!(relation, s1.relate(&s2));
assert_eq!(relation, s2.relate(&s1));
}
#[test]
fn divergent_disjoint_ray_and_line() {
let a = Line {
start: (1.0, 1.0).into(),
end: (0.0, 0.0).into(),
};
let b = Line {
start: (3.0, 0.0).into(),
end: (0.0, 3.0).into(),
};
let s1 = LineInterval::ray(a);
let s2 = LineInterval::line(b);
let relation = LineRelation::DivergentDisjoint;
assert_eq!(relation, s1.relate(&s2));
assert_eq!(relation, s2.relate(&s1));
}
#[test]
fn parallel_disjoint_segments() {
let a = Line {
start: (0.0, 0.0).into(),
end: (1.0, 1.0).into(),
};
let b = Line {
start: (0.0, 1.0).into(),
end: (1.0, 2.0).into(),
};
let s1 = LineInterval::line(a);
let s2 = LineInterval::line(b);
let relation = LineRelation::Parallel;
assert_eq!(relation, s1.relate(&s2));
assert_eq!(relation, s2.relate(&s1));
}
#[test]
fn collinear_overlapping_segment_and_line() {
let a = Line {
start: (0.0, 0.0).into(),
end: (0.0, 1.5).into(),
};
let b = Line {
start: (0.0, 4.0).into(),
end: (0.0, 5.0).into(),
};
let s1 = LineInterval::line(a);
let s2 = LineInterval::ray(b);
let relation = LineRelation::Collinear;
assert_eq!(relation, s1.relate(&s2));
assert_eq!(relation, s2.relate(&s1));
}
}