use crate::vector::Vector;
use crate::point::Point;
use crate::rect::Rect;
use crate::CoordinateType;
use num_traits::Float;
use num_traits::cast::NumCast;
use crate::traits::MapPointwise;
pub use crate::traits::{BoundingBox, TryBoundingBox, RotateOrtho, TryCastCoord};
pub use crate::types::Angle;
pub use crate::types::{Side, ContainsResult};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum EdgeIntersection<TP: CoordinateType, TO: CoordinateType> {
None,
Point(Point<TP>),
EndPoint(Point<TO>),
Overlap(Edge<TO>),
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum LineIntersection<TP: CoordinateType, TE: CoordinateType> {
None,
Point(Point<TP>, (TE, TE, TE)),
Collinear,
}
impl<T: CoordinateType> Into<(Point<T>, Point<T>)> for Edge<T> {
fn into(self) -> (Point<T>, Point<T>) {
(self.start, self.end)
}
}
impl<T: CoordinateType> Into<(Point<T>, Point<T>)> for &Edge<T> {
fn into(self) -> (Point<T>, Point<T>) {
(self.start, self.end)
}
}
impl<T: CoordinateType> From<(Point<T>, Point<T>)> for Edge<T> {
fn from(points: (Point<T>, Point<T>)) -> Self {
Edge::new(points.0, points.1)
}
}
impl<T: CoordinateType> From<[Point<T>; 2]> for Edge<T> {
fn from(points: [Point<T>; 2]) -> Self {
Edge::new(points[0], points[1])
}
}
#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub struct Edge<T: CoordinateType> {
pub start: Point<T>,
pub end: Point<T>,
}
impl<T: CoordinateType> Edge<T> {
pub fn new<C>(start: C, end: C) -> Self
where C: Into<Point<T>> {
Edge {
start: start.into(),
end: end.into(),
}
}
pub fn reversed(&self) -> Self {
Edge {
start: self.end,
end: self.start,
}
}
pub fn is_degenerate(&self) -> bool {
self.start == self.end
}
pub fn is_rectilinear(&self) -> bool {
!self.is_degenerate() &&
(self.start.x == self.end.x || self.start.y == self.end.y)
}
pub fn is_horizontal(&self) -> bool {
!self.is_degenerate() &&
self.start.y == self.end.y
}
pub fn is_vertical(&self) -> bool {
!self.is_degenerate() &&
self.start.x == self.end.x
}
pub fn vector(&self) -> Vector<T> {
self.end - self.start
}
pub fn side_of(&self, point: Point<T>) -> Side {
assert!(!self.is_degenerate());
let a = self.vector();
let b = point - self.start;
let area = b.cross_prod(a);
if area.is_zero() {
Side::Center
} else if area < T::zero() {
Side::Left
} else {
Side::Right
}
}
pub fn contains_point(&self, point: Point<T>) -> ContainsResult {
if self.start == point || self.end == point {
ContainsResult::OnBounds
} else if self.is_degenerate() {
ContainsResult::No
} else {
let a = self.start - point;
let b = self.end - point;
if a.cross_prod(b).is_zero() && a.dot(b) <= T::zero() {
ContainsResult::WithinBounds
} else {
ContainsResult::No
}
}
}
pub fn line_contains_point(&self, point: Point<T>) -> bool {
if self.is_degenerate() {
self.start == point
} else {
let l = self.vector();
let b = point - self.start;
l.cross_prod(b).is_zero()
}
}
pub fn is_parallel(&self, other: &Edge<T>) -> bool {
if self.is_degenerate() || other.is_degenerate() {
false
} else {
let a = self.vector();
let b = other.vector();
a.cross_prod(b).is_zero()
}
}
pub fn is_collinear(&self, other: &Edge<T>) -> bool
where T: CoordinateType {
if self.is_degenerate() || other.is_degenerate() {
false
} else {
let v = self.vector();
let a = other.start - self.start;
let b = other.end - self.start;
v.cross_prod(a).is_zero() &&
v.cross_prod(b).is_zero()
}
}
pub fn is_coincident(&self, other: &Edge<T>) -> bool {
let self_start_in_other = other.contains_point(self.start).inclusive_bounds();
let self_end_in_other = other.contains_point(self.end).inclusive_bounds();
let other_start_in_self = self.contains_point(other.start).inclusive_bounds();
let other_end_in_self = self.contains_point(other.end).inclusive_bounds();
let share_more_than_one_point =
self.end != other.start && self.start != other.end && (
other_start_in_self && self_end_in_other ||
other_end_in_self && self_start_in_other ||
other_start_in_self && other_end_in_self ||
self_start_in_other && self_end_in_other);
debug_assert!(if share_more_than_one_point { self.is_parallel(other) } else { true });
let oriented_the_same_way = self.vector().dot(other.vector()) > T::zero();
share_more_than_one_point && oriented_the_same_way
}
pub fn is_parallel_approx(&self, other: &Edge<T>, epsilon_squared: T) -> bool
{
if self.is_degenerate() || other.is_degenerate() {
false
} else {
let d1 = self.vector();
let d2 = other.vector();
let len1_sqr = d1.norm2_squared();
let len2_sqr = d2.norm2_squared();
let cross = d1.cross_prod(d2);
let cross_sqr = cross * cross;
cross_sqr <= len1_sqr * len2_sqr * epsilon_squared
}
}
pub fn is_collinear_approx(&self, other: &Edge<T>, epsilon_squared: T) -> bool {
if self.is_degenerate() || other.is_degenerate() {
false
} else {
let d1 = self.vector();
let d2 = other.vector();
let len1_sqr = d1.norm2_squared();
let len2_sqr = d2.norm2_squared();
let cross = d1.cross_prod(d2);
let cross_sqr = cross * cross;
let approx_parallel = cross_sqr <= len1_sqr * len2_sqr * epsilon_squared;
if approx_parallel {
let e = other.start - self.start;
let len_e_sqrt = e.norm2_squared();
let cross = e.cross_prod(d1);
let cross_sqr = cross * cross;
cross_sqr <= len1_sqr * len_e_sqrt * epsilon_squared
} else {
false
}
}
}
pub fn lines_intersect_approx(&self, other: &Edge<T>, epsilon_squared: T) -> bool {
!self.is_parallel_approx(other, epsilon_squared) || self.is_collinear_approx(other, epsilon_squared)
}
pub fn crossed_by_line(&self, other: &Edge<T>) -> ContainsResult {
let side1 = other.side_of(self.start);
if side1 == Side::Center {
ContainsResult::OnBounds
} else {
let side2 = other.side_of(self.end);
if side2 == Side::Center {
ContainsResult::OnBounds
} else {
if side1 == side2 {
ContainsResult::No
} else {
ContainsResult::WithinBounds
}
}
}
}
pub fn lines_intersect(&self, other: &Edge<T>) -> bool {
!self.is_parallel(other) || self.is_collinear(other)
}
pub fn edges_intersect(&self, other: &Edge<T>) -> ContainsResult {
if self.is_degenerate() {
other.contains_point(self.start)
} else if other.is_degenerate() {
self.contains_point(other.start)
} else if !self.bounding_box().touches(&other.bounding_box()) {
ContainsResult::No
} else {
match self.crossed_by_line(&other) {
ContainsResult::No => ContainsResult::No,
r => r.min(other.crossed_by_line(self))
}
}
}
}
impl<T: CoordinateType + NumCast> Edge<T> {
pub fn line_contains_point_approx<F: Float + NumCast>(&self, point: Point<T>, tolerance: F) -> bool {
if self.is_degenerate() {
self.start == point
} else {
self.distance_to_line_abs_approx::<F>(point) <= tolerance
}
}
pub fn line_intersection_approx<F: Float>(&self, other: &Edge<T>, tolerance: F) -> LineIntersection<F, T> {
debug_assert!(tolerance >= F::zero(), "Tolerance cannot be negative.");
if self.is_degenerate() {
LineIntersection::None
} else if other.is_degenerate() {
LineIntersection::None
} else {
let ab = self.vector();
let cd = other.vector();
debug_assert!(ab.norm2_squared() > T::zero());
debug_assert!(cd.norm2_squared() > T::zero());
let s = ab.cross_prod(cd);
let s_float = F::from(s).unwrap();
if s_float.abs() <= tolerance * ab.length() * cd.length() {
if self.line_contains_point_approx(other.start, tolerance) {
LineIntersection::Collinear
} else {
LineIntersection::None
}
} else {
let ac = other.start - self.start;
let ac_cross_cd = ac.cross_prod(cd);
let i = F::from(ac_cross_cd).unwrap() / s_float;
let p: Point<F> = self.start.cast() + ab.cast() * i;
let ca_cross_ab = ac.cross_prod(ab);
debug_assert!(self.cast().line_contains_point_approx(p, tolerance + tolerance));
debug_assert!(other.cast().line_contains_point_approx(p, tolerance + tolerance));
debug_assert!({
let j = F::from(ca_cross_ab).unwrap() / s_float;
let p2: Point<F> = other.start.cast() + cd.cast() * j;
(p - p2).norm2_squared() < tolerance + tolerance
}
);
let positions = if s < T::zero() {
(T::zero() - ac_cross_cd, T::zero() - ca_cross_ab, T::zero() - s)
} else {
(ac_cross_cd, ca_cross_ab, s)
};
LineIntersection::Point(p, positions)
}
}
}
pub fn edge_intersection_approx<F: Float>(&self, other: &Edge<T>, tolerance: F) -> EdgeIntersection<F, T> {
debug_assert!(tolerance >= F::zero(), "Tolerance cannot be negative.");
let other = if (self.start < self.end) != (other.start < other.end) {
other.reversed()
} else {
*other
};
let same_start_start = self.start == other.start;
let same_start_end = self.start == other.end;
let same_end_start = self.end == other.start;
let same_end_end = self.end == other.end;
let fully_coincident = (same_start_start & same_end_end) ^ (same_start_end & same_end_start);
let result = if self.is_degenerate() {
if other.contains_point_approx(self.start, tolerance) {
EdgeIntersection::EndPoint(self.start)
} else {
EdgeIntersection::None
}
} else if other.is_degenerate() {
if self.contains_point_approx(other.start, tolerance) {
EdgeIntersection::EndPoint(other.start)
} else {
EdgeIntersection::None
}
} else if fully_coincident {
EdgeIntersection::Overlap(*self)
} else if !self.bounding_box().touches(&other.bounding_box()) {
EdgeIntersection::None
} else {
let line_intersection = self.line_intersection_approx(&other, tolerance);
match line_intersection {
LineIntersection::None => EdgeIntersection::None,
LineIntersection::Point(_, _) if same_start_start || same_start_end =>
EdgeIntersection::EndPoint(self.start),
LineIntersection::Point(_, _) if same_end_start || same_end_end =>
EdgeIntersection::EndPoint(self.end),
LineIntersection::Point(p, (pos1, pos2, len)) => {
let len_f = F::from(len).unwrap();
let zero_tol = -tolerance;
let len_tol = len_f + tolerance;
let pos1 = F::from(pos1).unwrap();
let pos2 = F::from(pos2).unwrap();
if pos1 >= zero_tol && pos1 <= len_tol
&& pos2 >= zero_tol && pos2 <= len_tol {
let zero_tol = tolerance;
let len_tol = len_f - tolerance;
if pos1 <= zero_tol {
EdgeIntersection::EndPoint(self.start)
} else if pos1 >= len_tol {
EdgeIntersection::EndPoint(self.end)
} else if pos2 <= zero_tol {
EdgeIntersection::EndPoint(other.start)
} else if pos2 >= len_tol {
EdgeIntersection::EndPoint(other.end)
} else {
debug_assert!({
let e1 = self.cast();
let e2 = other.cast();
p != e1.start
&& p != e1.end
&& p != e2.start
&& p != e2.end
},
"Intersection should not be an endpoint.");
EdgeIntersection::Point(p)
}
} else {
EdgeIntersection::None
}
}
LineIntersection::Collinear => {
let (pa, pb) = self.into();
let (pc, pd) = other.into();
let b = pb - pa;
let c = pc - pa;
let d = pd - pa;
let dist_a = T::zero();
let dist_b = b.dot(b);
let dist_c = b.dot(c);
let dist_d = b.dot(d);
let start1 = (dist_a, pa);
let end1 = (dist_b, pb);
let (start2, end2) = if dist_c < dist_d {
((dist_c, pc), (dist_d, pd))
} else {
((dist_d, pd), (dist_c, pc))
};
let start = if start1.0 < start2.0 {
start2
} else {
start1
};
let end = if end1.0 < end2.0 {
end1
} else {
end2
};
if start.0 < end.0 {
EdgeIntersection::Overlap(Edge::new(start.1, end.1))
} else if start.0 == end.0 {
EdgeIntersection::EndPoint(start.1)
} else {
EdgeIntersection::None
}
}
}
};
debug_assert!(
if self.edges_intersect(&other).inclusive_bounds() {
result != EdgeIntersection::None
} else {
true
}
);
result
}
}
impl<T: CoordinateType + NumCast> Edge<T> {
pub fn try_cast<Target: NumCast>(&self) -> Option<Edge<Target>>
where Target: CoordinateType {
match (self.start.try_cast(), self.end.try_cast()) {
(Some(start), Some(end)) => Some(Edge { start, end }),
_ => None
}
}
pub fn cast<Target>(&self) -> Edge<Target>
where Target: CoordinateType + NumCast {
Edge {
start: self.start.cast(),
end: self.end.cast(),
}
}
pub fn cast_to_float<Target>(&self) -> Edge<Target>
where Target: CoordinateType + NumCast + Float {
Edge {
start: self.start.cast_to_float(),
end: self.end.cast_to_float(),
}
}
pub fn distance_to_line<F: Float>(&self, point: Point<T>) -> F {
assert!(!self.is_degenerate());
let a = self.vector();
let b = point - self.start;
let area = b.cross_prod(a);
let distance = F::from(area).unwrap() / a.length();
distance
}
pub fn distance<F: Float>(&self, point: Point<T>) -> F {
let dist_ortho: F = self.distance_to_line_abs_approx(point);
let dist_a: F = (point - self.start).length();
let dist_b = (point - self.end).length();
dist_ortho.max(dist_a.min(dist_b))
}
pub fn projection_approx<F: Float>(&self, point: Point<T>) -> Point<F> {
assert!(!self.is_degenerate());
let p = (point - self.start).cast();
let d = (self.vector()).cast();
let projection = self.start.cast() + d * d.dot(p) / d.norm2_squared();
projection
}
pub fn reflection_approx<F: Float>(&self, point: Point<T>) -> Point<F> {
let proj = self.projection_approx(point);
proj + proj - point.cast().v()
}
pub fn distance_to_line_abs_approx<F: Float>(&self, point: Point<T>) -> F {
let dist: F = self.distance_to_line(point);
dist.abs()
}
pub fn contains_point_approx<F: Float>(&self, point: Point<T>, tolerance: F) -> bool {
debug_assert!(tolerance >= F::zero());
if self.is_degenerate() {
let l = (self.start - point).norm2_squared();
F::from(l).unwrap() <= tolerance
} else {
let p = point - self.start;
let v = self.vector();
let n: Vector<F> = v.rotate_ortho(Angle::R270).cast() / v.length();
let o = n.dot(p.cast());
if o.abs() >= tolerance {
false
} else {
let l = F::from(v.dot(p)).unwrap();
l >= -tolerance && l <= F::from(v.norm2_squared()).unwrap() + tolerance
}
}
}
}
impl<T: CoordinateType> MapPointwise<T> for Edge<T> {
fn transform<F: Fn(Point<T>) -> Point<T>>(&self, tf: F) -> Self {
Edge {
start: tf(self.start),
end: tf(self.end),
}
}
}
impl<T: CoordinateType> BoundingBox<T> for Edge<T> {
fn bounding_box(&self) -> Rect<T> {
Rect::new(self.start, self.end)
}
}
impl<T: CoordinateType> TryBoundingBox<T> for Edge<T> {
fn try_bounding_box(&self) -> Option<Rect<T>> {
Some(self.bounding_box())
}
}
impl<T: CoordinateType + NumCast, Dst: CoordinateType + NumCast> TryCastCoord<T, Dst> for Edge<T> {
type Output = Edge<Dst>;
fn try_cast(&self) -> Option<Self::Output> {
match (self.start.try_cast(), self.end.try_cast()) {
(Some(s), Some(e)) => Some(Edge::new(s, e)),
_ => None
}
}
}
#[cfg(test)]
mod tests {
extern crate rand;
use std::f64;
use crate::edge::Edge;
use crate::point::Point;
use crate::types::*;
use super::*;
use self::rand::distributions::{Uniform, Distribution, Bernoulli};
use self::rand::rngs::StdRng;
use self::rand::SeedableRng;
#[test]
fn test_is_parallel() {
let e1 = Edge::new((1, 2), (3, 4));
let e2 = Edge::new((1 - 10, 2 - 20), (5 - 10, 6 - 20));
let e3 = Edge::new((1 - 10, 2 - 20), (5 - 10, 6 - 20 + 1));
assert!(e1.is_parallel(&e2));
assert!(!e1.is_parallel(&e3));
assert!(e1.is_parallel_approx(&e2, 0));
assert!(!e1.is_parallel_approx(&e3, 0));
assert!(e1.is_parallel_approx(&e3, 1));
}
#[test]
fn test_is_collinear() {
let e1 = Edge::new((0, 0), (1, 2));
let e2 = Edge::new((10, 20), (100, 200));
assert!(e1.is_collinear(&e2));
assert!(e2.is_collinear(&e1));
assert!(e1.is_collinear_approx(&e2, 0));
assert!(e2.is_collinear_approx(&e1, 0));
let e1 = Edge::new((0i64, 0), (1, 2));
let e2 = Edge::new((10, 20), (1000, 2001));
assert!(!e1.is_collinear(&e2));
assert!(!e2.is_collinear(&e1));
assert!(!e1.is_collinear_approx(&e2, 0));
assert!(!e2.is_collinear_approx(&e1, 0));
assert!(e1.is_collinear_approx(&e2, 1));
assert!(e2.is_collinear_approx(&e1, 1));
}
#[test]
fn test_distance_to_line() {
let e1 = Edge::new((1, 0), (2, 1));
let p0 = Point::new(2, 0);
let d: f64 = e1.distance_to_line(p0);
let diff = (d - f64::sqrt(2.) / 2.).abs();
assert!(diff < 1e-9);
}
#[test]
fn test_line_contains_point() {
let e1 = Edge::new((1, 2), (5, 6));
let p0 = Point::new(1, 2);
let p1 = Point::new(3, 4);
let p2 = Point::new(5, 6);
let p3 = Point::new(6, 7);
let p4 = Point::new(0, 1);
let p5 = Point::new(0, 0);
assert!(e1.line_contains_point(p0));
assert!(e1.line_contains_point(p1));
assert!(e1.line_contains_point(p2));
assert!(e1.line_contains_point(p3));
assert!(e1.line_contains_point(p4));
assert!(!e1.line_contains_point(p5));
let e1 = Edge::new((0, 0), (1, 1));
assert!(e1.line_contains_point(Point::new(2, 2)));
assert!(e1.line_contains_point(Point::new(-1, -1)));
}
#[test]
fn test_contains() {
let e1 = Edge::new((1, 2), (5, 6));
let p0 = Point::new(1, 2);
let p1 = Point::new(3, 4);
let p2 = Point::new(5, 6);
let p3 = Point::new(0, 0);
let p4 = Point::new(6, 7);
assert!(e1.contains_point(p0).inclusive_bounds());
assert!(e1.contains_point(p1).inclusive_bounds());
assert!(e1.contains_point(p2).inclusive_bounds());
assert!(!e1.contains_point(p3).inclusive_bounds());
assert!(!e1.contains_point(p4).inclusive_bounds());
let tol = 1e-6;
assert!(e1.contains_point_approx(p0, tol));
assert!(e1.contains_point_approx(p1, tol));
assert!(e1.contains_point_approx(p2, tol));
assert!(!e1.contains_point_approx(p3, tol));
assert!(!e1.contains_point_approx(p4, tol));
let e1 = Edge::new((0, 0), (1, 1));
let p0 = Point::new(2, 2);
assert!(!e1.contains_point(p0).inclusive_bounds());
}
#[test]
fn test_projection() {
let e1 = Edge::new((-6., -5.), (4., 7.));
let p1 = Point::new(1., 2.);
let p2 = Point::new(-10., 10.);
let proj1 = e1.projection_approx(p1);
let proj2 = e1.projection_approx(p2);
assert!(e1.contains_point_approx(proj1, PREC_DISTANCE));
assert!(e1.contains_point_approx(proj2, PREC_DISTANCE));
}
#[test]
fn test_side_of() {
let e1 = Edge::new((1, 0), (4, 4));
let p1 = Point::new(-10, 0);
let p2 = Point::new(10, -10);
let p3 = Point::new(1, 0);
assert_eq!(e1.side_of(p1), Side::Left);
assert_eq!(e1.side_of(p2), Side::Right);
assert_eq!(e1.side_of(p3), Side::Center);
}
#[test]
fn test_crossed_by() {
let e1 = Edge::new((1, 0), (4, 4));
let e2 = Edge::new((1 + 1, 0), (4 + 1, 4));
let e3 = Edge::new((1, 0), (4, 5));
let e4 = Edge::new((2, -2), (0, 0));
assert!(e1.crossed_by_line(&e1).inclusive_bounds());
assert!(e1.is_parallel(&e2));
assert!(!e1.crossed_by_line(&e2).inclusive_bounds());
assert!(e1.crossed_by_line(&e3).inclusive_bounds());
assert!(!e1.crossed_by_line(&e4).inclusive_bounds());
assert!(e4.crossed_by_line(&e1).inclusive_bounds());
}
#[test]
fn test_intersect() {
let e1 = Edge::new((0, 0), (4, 4));
let e2 = Edge::new((1, 0), (0, 1));
let e3 = Edge::new((0, -1), (-1, 1));
assert!(e1.edges_intersect(&e1).inclusive_bounds());
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(!e1.crossed_by_line(&e3).inclusive_bounds());
assert!(e3.crossed_by_line(&e1).inclusive_bounds());
assert!(!e1.edges_intersect(&e3).inclusive_bounds());
let e1 = Edge::new((0, 0), (8, 8));
let e2 = Edge::new((4, 0), (3, 1));
assert!(!e1.is_rectilinear());
assert!(!e2.is_rectilinear());
assert!(e1.bounding_box().touches(&e2.bounding_box()));
assert!(!e1.edges_intersect(&e2).inclusive_bounds());
assert!(e1.lines_intersect(&e2));
let e1 = Edge::new((0, 0), (4, 4));
let e2 = Edge::new((1, 1), (2, 0));
assert_eq!(e1.edges_intersect(&e2), ContainsResult::OnBounds);
assert_eq!(e2.edges_intersect(&e1), ContainsResult::OnBounds);
}
#[test]
fn test_line_intersection() {
let tol = 1e-12;
let e1 = Edge::new((0, 0), (2, 2));
let e2 = Edge::new((1, 0), (0, 1));
let e3 = Edge::new((1, 0), (3, 2));
assert_eq!(e1.line_intersection_approx(&e2, tol),
LineIntersection::Point(Point::new(0.5, 0.5), (1, 2, 4)));
assert_eq!(e1.line_intersection_approx(&e3, tol), LineIntersection::None);
let e4 = Edge::new((-320., 2394.), (94., -448382.));
let e5 = Edge::new((71., 133.), (-13733., 1384.));
if let LineIntersection::Point(intersection, _) = e4.line_intersection_approx(&e5, tol) {
assert!(e4.distance_to_line::<f64>(intersection).abs() < tol);
assert!(e5.distance_to_line::<f64>(intersection).abs() < tol);
} else {
assert!(false);
}
let e1 = Edge::new((0., 0.), (2., 2.));
let e2 = Edge::new((4., 4.), (8., 8.));
assert!(!e1.is_coincident(&e2));
assert!(e1.is_parallel_approx(&e2, tol));
assert_eq!(e1.line_intersection_approx(&e2, tol), LineIntersection::Collinear);
}
#[test]
fn test_edge_intersection() {
let tol = 1e-20;
let e1 = Edge::new((0, 0), (2, 2));
let e2 = Edge::new((2, 0), (0, 2));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::Point(Point::new(1f64, 1f64)));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::Point(Point::new(1f64, 1f64)));
let e1 = Edge::new((0, 0), (2, 2));
let e2 = Edge::new((2, 0), (1, 1));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::EndPoint(Point::new(1, 1)));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::EndPoint(Point::new(1, 1)));
let e1 = Edge::new((0, 0), (4, 4));
let e2 = Edge::new((3, 0), (2, 1));
assert!(!e1.edges_intersect(&e2).inclusive_bounds());
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::None);
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::None);
}
#[test]
fn test_edge_intersection_overlap() {
let tol = 1e-12;
let e1 = Edge::new((0, 0), (2, 0));
let e2 = Edge::new((1, 0), (3, 0));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(e1.is_collinear(&e2));
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::Overlap(Edge::new((1, 0), (2, 0))));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::Overlap(Edge::new((1, 0), (2, 0))));
let e1 = Edge::new((0, 0), (2, 2));
let e2 = Edge::new((3, 3), (1, 1));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(e1.is_collinear(&e2));
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::Overlap(Edge::new((1, 1), (2, 2))));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::Overlap(Edge::new((2, 2), (1, 1))));
let e1 = Edge::new((0, 0), (4, 4));
let e2 = Edge::new((1, 1), (2, 2));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(e1.is_collinear(&e2));
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::Overlap(e2));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::Overlap(e2));
let e1 = Edge::new((0, 0), (4, 4));
let e2 = Edge::new((2, 2), (1, 1));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(e1.is_collinear(&e2));
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::Overlap(e2.reversed()));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::Overlap(e2));
let e1 = Edge::new((0, 0), (1, 1));
let e2 = Edge::new((1, 1), (2, 2));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(e1.is_collinear(&e2));
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::EndPoint(Point::new(1, 1)));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::EndPoint(Point::new(1, 1)));
let e1 = Edge::new((1.1, 1.2), (0., 0.));
let e2 = Edge::new((1.1, 1.2), (2., 2.));
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(e2.edges_intersect(&e1).inclusive_bounds());
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::EndPoint(Point::new(1.1, 1.2)));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::EndPoint(Point::new(1.1, 1.2)));
let e1 = Edge {
start: Point::new(20.90725737794763, 65.33301386746126),
end: Point::new(32.799556599584776, 63.13131890182373),
};
let e2 = Edge {
start: Point::new(22.217533978705163, 70.84660296990562),
end: Point::new(32.799556599584776, 63.13131890182373),
};
assert!(e1.edges_intersect(&e2).inclusive_bounds());
assert!(e2.edges_intersect(&e1).inclusive_bounds());
assert_eq!(e1.edge_intersection_approx(&e2, tol),
EdgeIntersection::EndPoint(Point::new(32.799556599584776, 63.13131890182373)));
assert_eq!(e2.edge_intersection_approx(&e1, tol),
EdgeIntersection::EndPoint(Point::new(32.799556599584776, 63.13131890182373)));
}
#[test]
fn test_coincident() {
let e1 = Edge::new((0, 0), (2, 2));
let e2 = Edge::new((1, 1), (3, 3));
assert!(e1.is_coincident(&e2));
assert!(e2.is_coincident(&e1));
let e1 = Edge::new((0, 0), (3, 3));
let e2 = Edge::new((1, 1), (2, 2));
assert!(e1.is_coincident(&e2));
assert!(e2.is_coincident(&e1));
let e1 = Edge::new((0, 0), (1, 1));
let e2 = Edge::new((1, 1), (2, 2));
assert!(!e1.is_coincident(&e2));
assert!(!e2.is_coincident(&e1));
}
#[test]
fn test_intersection_of_random_edges() {
let tol = 1e-12;
let seed1 = [1u8; 32];
let seed2 = [2u8; 32];
let between = Uniform::from(-1.0..1.0);
let mut rng = StdRng::from_seed(seed1);
let mut rand_edge = || -> Edge<f64> {
let points: Vec<(f64, f64)> = (0..2).into_iter()
.map(|_| (between.sample(&mut rng), between.sample(&mut rng)))
.collect();
Edge::new(points[0], points[1])
};
let bernoulli_rare = Bernoulli::new(0.05).unwrap();
let bernoulli_50 = Bernoulli::new(0.5).unwrap();
let mut rng = StdRng::from_seed(seed2);
for _i in 0..1000 {
let (a, b) = {
let a = rand_edge();
let b = {
let b = rand_edge();
if bernoulli_rare.sample(&mut rng) {
let shared = if bernoulli_50.sample(&mut rng) {
a.start
} else {
a.end
};
let result = Edge::new(b.start, shared);
if bernoulli_50.sample(&mut rng) {
result
} else {
result.reversed()
}
} else {
b
}
};
(a, b)
};
let intersection_ab = a.edge_intersection_approx(&b, tol);
assert_eq!(intersection_ab != EdgeIntersection::None, a.edges_intersect(&b).inclusive_bounds());
let intersection_ba = b.edge_intersection_approx(&a, tol);
assert_eq!(intersection_ba != EdgeIntersection::None, b.edges_intersect(&a).inclusive_bounds());
}
}
}