From 8ff2598dd9bbbd88b19a78db220200eea75ca4a5 Mon Sep 17 00:00:00 2001 From: Brandon Waskiewicz Date: Mon, 12 May 2014 22:33:47 -0400 Subject: [PATCH] Add line segment shape and functions Create a new line segment struct that contains two Points in either 2D or 3D space. Also create an implementation of the Intersect trait for testing whether two line segments intersect, and where. --- src/cgmath/lib.rs | 1 + src/cgmath/line.rs | 121 +++++++++++++++++++++++++++++++++++++++++++++ src/test/line.rs | 79 +++++++++++++++++++++++++++++ src/test/test.rs | 1 + 4 files changed, 202 insertions(+) create mode 100644 src/cgmath/line.rs create mode 100644 src/test/line.rs 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;