use super::Point;
const TOLERANCE: f64 = 0.0001;
pub struct Segment {
pub a: Point,
pub b: Point,
}
impl Segment {
pub fn new(x1: f64, y1: f64, x2: f64, y2: f64) -> Segment {
Segment {
a: Point::new(x1, y1),
b: Point::new(x2, y2),
}
}
pub fn from_points(a: Point, b: Point) -> Segment {
Segment { a, b }
}
pub fn direction(&self, p: &Point) -> f64 {
let a = Point::new(p.x - self.a.x, p.y - self.a.y);
let b = Point::new(self.b.x - self.a.x, self.b.y - self.a.y);
a.cross_prod(&b)
}
pub fn is_vertical(&self) -> bool {
self.a.x == self.b.x
}
pub fn get_line_equation(&self) -> (f64, f64) {
let slope = (self.a.y - self.b.y) / (self.a.x - self.b.x);
let y_intercept = self.a.y - slope * self.a.x;
(slope, y_intercept)
}
pub fn compute_y_at_x(&self, x: f64) -> f64 {
let (slope, y_intercept) = self.get_line_equation();
slope * x + y_intercept
}
pub fn is_colinear(&self, p: &Point) -> bool {
if self.is_vertical() {
p.x == self.a.x
} else {
(self.compute_y_at_x(p.x) - p.y).abs() < TOLERANCE
}
}
pub fn colinear_point_on_segment(&self, p: &Point) -> bool {
assert!(self.is_colinear(p), "p must be colinear!");
let (low_x, high_x) = if self.a.x < self.b.x {
(self.a.x, self.b.x)
} else {
(self.b.x, self.a.x)
};
let (low_y, high_y) = if self.a.y < self.b.y {
(self.a.y, self.b.y)
} else {
(self.b.y, self.a.y)
};
p.x >= low_x && p.x <= high_x && p.y >= low_y && p.y <= high_y
}
pub fn on_segment(&self, p: &Point) -> bool {
if !self.is_colinear(p) {
return false;
}
self.colinear_point_on_segment(p)
}
pub fn intersects(&self, other: &Segment) -> bool {
let direction1 = self.direction(&other.a);
let direction2 = self.direction(&other.b);
let direction3 = other.direction(&self.a);
let direction4 = other.direction(&self.b);
if ((direction1 > 0.0 && direction2 < 0.0) || (direction1 < 0.0 && direction2 > 0.0))
&& ((direction3 > 0.0 && direction4 < 0.0) || (direction3 < 0.0 && direction4 > 0.0))
{
return true;
}
(direction1 == 0.0 && self.colinear_point_on_segment(&other.a))
|| (direction2 == 0.0 && self.colinear_point_on_segment(&other.b))
|| (direction3 == 0.0 && other.colinear_point_on_segment(&self.a))
|| (direction4 == 0.0 && other.colinear_point_on_segment(&self.b))
}
}
#[cfg(test)]
mod tests {
use super::Point;
use super::Segment;
#[test]
fn colinear() {
let segment = Segment::new(2.0, 3.0, 6.0, 5.0);
assert_eq!((0.5, 2.0), segment.get_line_equation());
assert!(segment.is_colinear(&Point::new(2.0, 3.0)));
assert!(segment.is_colinear(&Point::new(6.0, 5.0)));
assert!(segment.is_colinear(&Point::new(0.0, 2.0)));
assert!(segment.is_colinear(&Point::new(-5.0, -0.5)));
assert!(segment.is_colinear(&Point::new(10.0, 7.0)));
assert!(!segment.is_colinear(&Point::new(0.0, 0.0)));
assert!(!segment.is_colinear(&Point::new(1.9, 3.0)));
assert!(!segment.is_colinear(&Point::new(2.1, 3.0)));
assert!(!segment.is_colinear(&Point::new(2.0, 2.9)));
assert!(!segment.is_colinear(&Point::new(2.0, 3.1)));
assert!(!segment.is_colinear(&Point::new(5.9, 5.0)));
assert!(!segment.is_colinear(&Point::new(6.1, 5.0)));
assert!(!segment.is_colinear(&Point::new(6.0, 4.9)));
assert!(!segment.is_colinear(&Point::new(6.0, 5.1)));
}
#[test]
fn colinear_vertical() {
let segment = Segment::new(2.0, 3.0, 2.0, 5.0);
assert!(segment.is_colinear(&Point::new(2.0, 1.0)));
assert!(segment.is_colinear(&Point::new(2.0, 3.0)));
assert!(segment.is_colinear(&Point::new(2.0, 4.0)));
assert!(segment.is_colinear(&Point::new(2.0, 5.0)));
assert!(segment.is_colinear(&Point::new(2.0, 6.0)));
assert!(!segment.is_colinear(&Point::new(1.0, 3.0)));
assert!(!segment.is_colinear(&Point::new(3.0, 3.0)));
}
fn test_intersect(s1: &Segment, s2: &Segment, result: bool) {
assert_eq!(s1.intersects(s2), result);
assert_eq!(s2.intersects(s1), result);
}
#[test]
fn intersects() {
let s1 = Segment::new(2.0, 3.0, 6.0, 5.0);
let s2 = Segment::new(-1.0, 9.0, 10.0, -3.0);
let s3 = Segment::new(-0.0, 10.0, 11.0, -2.0);
let s4 = Segment::new(100.0, 200.0, 40.0, 50.0);
test_intersect(&s1, &s2, true);
test_intersect(&s1, &s3, true);
test_intersect(&s2, &s3, false);
test_intersect(&s1, &s4, false);
test_intersect(&s2, &s4, false);
test_intersect(&s3, &s4, false);
}
#[test]
fn intersects_endpoint_on_segment() {
let s1 = Segment::new(2.0, 3.0, 6.0, 5.0);
let s2 = Segment::new(4.0, 4.0, -11.0, 20.0);
let s3 = Segment::new(4.0, 4.0, 14.0, -19.0);
test_intersect(&s1, &s2, true);
test_intersect(&s1, &s3, true);
}
#[test]
fn intersects_self() {
let s1 = Segment::new(2.0, 3.0, 6.0, 5.0);
let s2 = Segment::new(2.0, 3.0, 6.0, 5.0);
test_intersect(&s1, &s2, true);
}
#[test]
fn too_short_to_intersect() {
let s1 = Segment::new(2.0, 3.0, 6.0, 5.0);
let s2 = Segment::new(-1.0, 10.0, 3.0, 5.0);
let s3 = Segment::new(5.0, 3.0, 10.0, -11.0);
test_intersect(&s1, &s2, false);
test_intersect(&s1, &s3, false);
test_intersect(&s2, &s3, false);
}
#[test]
fn parallel_segments() {
let s1 = Segment::new(-5.0, 0.0, 5.0, 0.0);
let s2 = Segment::new(-5.0, 1.0, 5.0, 1.0);
let s3 = Segment::new(-5.0, -1.0, 5.0, -1.0);
test_intersect(&s1, &s2, false);
test_intersect(&s1, &s3, false);
test_intersect(&s2, &s3, false);
}
}