diff --git a/src/cgmath/lib.rs b/src/cgmath/lib.rs index bb6576b..f0353e0 100644 --- a/src/cgmath/lib.rs +++ b/src/cgmath/lib.rs @@ -30,6 +30,7 @@ pub mod vector; pub mod angle; pub mod plane; pub mod point; +pub mod line; pub mod ray; pub mod rotation; pub mod transform; diff --git a/src/cgmath/line.rs b/src/cgmath/line.rs new file mode 100644 index 0000000..92c82a0 --- /dev/null +++ b/src/cgmath/line.rs @@ -0,0 +1,121 @@ +// Copyright 2013 The CGMath Developers. For a full listing of the authors, +// refer to the AUTHORS file at the top-level directionectory of this distribution. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +//! Line segments + +use std::num::{Zero, zero, One, one}; + +use point::{Point, Point2, Point3}; +use vector::{Vector, Vector2}; +use partial_ord::PartOrdFloat; +use intersect::Intersect; + +/// A generic directed line segment +#[deriving(Clone, Eq)] +pub struct Line

+{ + pub origin: P, + pub dest: P, +} + +impl +< + S: Primitive, + Slice, + V: Vector, + P: Point +> Line

+{ + pub fn new(origin: P, dest: P) -> Line

{ + Line { origin:origin, dest:dest } + } +} + +pub type Line2 = Line>; +pub type Line3 = Line>; + +/// Determines if an intersection between two line segments is found. If the segments are +/// collinear and overlapping, the intersection point that will be returned will be the first +/// intersection point found by traversing the first line segment, starting at its origin. +impl> Intersect>> for (Line2, Line2) { + fn intersection(&self) -> Option> { + match *self { + (ref l1, ref l2) => { + let p = l1.origin; + let mut q = l2.origin; + let r = Vector2::new(l1.dest.x - l1.origin.x, l1.dest.y - l1.origin.y); + let mut s = Vector2::new(l2.dest.x - l2.origin.x, l2.dest.y - l2.origin.y); + let zero: S = Zero::zero(); + + let cross = r.perp_dot(&s); + let mut q_minus_p = Vector2::new(q.x - p.x, q.y - p.y); + let mut p_minus_q = Vector2::new(p.x - q.x, p.y - q.y); + let cross_r = q_minus_p.perp_dot(&r); + let cross_s = q_minus_p.perp_dot(&s); + + if cross.is_zero() { + if cross_r.is_zero() { + // line segments are collinear + + // special case of both lines being the same single point + if r.x == zero && r.y == zero && s.x == zero && s.y == zero && p == q { + return Some(p); + } + + // ensure l2 (q,q+s) is pointing the same direction as l1 (p,p+r) + // if it is not, then swap the two endpoints of the second segment. + // If this is not done, the algorithm below will not find an + // intersection if the two directed line segments point towards the + // opposite segment's origin. + if (r.x != zero && s.x != zero && r.x.signum() != s.x.signum()) || + (r.y != zero && s.y != zero && r.y.signum() != s.y.signum()) { + q = Point2::new(q.x + s.x, q.y + s.y); + s = Vector2::new(-s.x, -s.y); + q_minus_p = Vector2::new(q.x - p.x, q.y - p.y); + p_minus_q = Vector2::new(p.x - q.x, p.y - q.y); + } + let d1 = q_minus_p.dot(&r); + let d2 = p_minus_q.dot(&s); + let rdotr = r.dot(&r); + let sdots = s.dot(&s); + + // make sure to take into account that one or both of the segments could + // be a single point (r.r or s.s = 0) and ignore that case + if (rdotr > zero && zero <= d1 && d1 <= rdotr) || + (sdots > zero && zero <= d2 && d2 <= sdots) { + // overlapping + if (q_minus_p.x != zero && q_minus_p.x.signum() == r.x.signum()) || + (q_minus_p.y != zero && q_minus_p.y.signum() == r.y.signum()) { + return Some(q); + } + return Some(p); + } + } + return None; + } + + let t = cross_s / cross; + let u = cross_r / cross; + + if zero <= t && t <= One::one() && + zero <= One::one() && u <= One::one() { + return Some(Point2::new(p.x + t*r.x, p.y + t*r.y)); + } + + return None; + } + } + } +} diff --git a/src/test/line.rs b/src/test/line.rs new file mode 100644 index 0000000..7184c97 --- /dev/null +++ b/src/test/line.rs @@ -0,0 +1,79 @@ +// Copyright 2013 The CGMath Developers. For a full listing of the authors, +// refer to the AUTHORS file at the top-level directory of this distribution. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +use cgmath::line::*; +use cgmath::point::*; +use cgmath::intersect::Intersect; + +#[test] +fn test_line_intersection() { + // collinear, origins pointing towards each other, first intersection + // from l1.origin is in an endpoint in l2 + let l1 = Line::new(Point2::new(0.0, 0.0), Point2::new(10.0, 0.0)); + let l2 = Line::new(Point2::new(1.5, 0.0), Point2::new(0.5, 0.0)); + assert_eq!((l1, l2).intersection(), Some(Point2::new(0.5, 0.0))); + + // collinear, first intersection from p1.origin is at p1.origin itself + let l3 = Line::new(Point2::new(0.0, 0.0), Point2::new(10.0, 0.0)); + let l4 = Line::new(Point2::new(-11.0, 0.0), Point2::new(1.0, 0.0)); + assert_eq!((l3, l4).intersection(), Some(Point2::new(0.0, 0.0))); + + // no intersection + let l5 = Line::new(Point2::new(5.0, 5.0), Point2::new(10.0, 6.0)); + let l6 = Line::new(Point2::new(5.0, 4.8), Point2::new(10.0, 4.1)); + assert_eq!((l5, l6).intersection(), None); // no intersection + + // collinear, origins pointing same direction + let l7 = Line::new(Point2::new(0.0, 1.0), Point2::new(0.0, 0.0)); + let l8 = Line::new(Point2::new(0.0, 0.5), Point2::new(0.0, -0.5)); + assert_eq!((l7, l8).intersection(), Some(Point2::new(0.0, 0.5))); + + // collinear, no overlap + let l9 = Line::new(Point2::new(0.0, 0.0), Point2::new(3.0, 0.0)); + let l10 = Line::new(Point2::new(10.0, 0.0), Point2::new(5.0, 0.0)); + assert_eq!((l9, l10).intersection(), None); + + // intersection found + let l11 = Line::new(Point2::new(0.0, 0.0), Point2::new(10.0, 10.0)); + let l12 = Line::new(Point2::new(0.0, 10.0), Point2::new(10.0, 0.0)); + assert_eq!((l11, l12).intersection(), Some(Point2::new(5.0, 5.0))); + + // special case of both lines being the same point + let l13 = Line::new(Point2::new(0.0, 0.0), Point2::new(0.0, 0.0)); + let l14 = Line::new(Point2::new(0.0, 0.0), Point2::new(0.0, 0.0)); + assert_eq!((l13, l14).intersection(), Some(Point2::new(0.0, 0.0))); + + // both lines are points that are distinct + let l15 = Line::new(Point2::new(0.0, 0.0), Point2::new(0.0, 0.0)); + let l16 = Line::new(Point2::new(1.0, 0.0), Point2::new(1.0, 0.0)); + assert_eq!((l15, l16).intersection(), None); + + // one line is a point that intersects the other segment + let l15 = Line::new(Point2::new(0.0, 0.0), Point2::new(10.0, 0.0)); + let l16 = Line::new(Point2::new(3.0, 0.0), Point2::new(3.0, 0.0)); + assert_eq!((l15, l16).intersection(), Some(Point2::new(3.0, 0.0))); + + // one line is a point that is collinear but does not intersect with + // the other line + let l17 = Line::new(Point2::new(0.0, 0.0), Point2::new(0.0, 0.0)); + let l18 = Line::new(Point2::new(1.0, 0.0), Point2::new(3.0, 0.0)); + assert_eq!((l17, l18).intersection(), None); + + // one line is a point that is not collinear but does not intersect + // with the other line + let l19 = Line::new(Point2::new(0.0, 0.0), Point2::new(0.0, 0.0)); + let l20 = Line::new(Point2::new(1.0, 0.0), Point2::new(2.0, 10.0)); + assert_eq!((l19, l20).intersection(), None); +} diff --git a/src/test/test.rs b/src/test/test.rs index 9449f1d..43ccefd 100644 --- a/src/test/test.rs +++ b/src/test/test.rs @@ -26,6 +26,7 @@ pub mod vector; pub mod angle; pub mod plane; pub mod point; +pub mod line; // pub mod ray; // pub mod rotation; pub mod transform;