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.
This commit is contained in:
Brandon Waskiewicz 2014-05-12 22:33:47 -04:00
parent 10e911d180
commit 8ff2598dd9
4 changed files with 202 additions and 0 deletions

View file

@ -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;

121
src/cgmath/line.rs Normal file
View file

@ -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<P>
{
pub origin: P,
pub dest: P,
}
impl
<
S: Primitive,
Slice,
V: Vector<S,Slice>,
P: Point<S,V,Slice>
> Line<P>
{
pub fn new(origin: P, dest: P) -> Line<P> {
Line { origin:origin, dest:dest }
}
}
pub type Line2<S> = Line<Point2<S>>;
pub type Line3<S> = Line<Point3<S>>;
/// 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<S: PartOrdFloat<S>> Intersect<Option<Point2<S>>> for (Line2<S>, Line2<S>) {
fn intersection(&self) -> Option<Point2<S>> {
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;
}
}
}
}

79
src/test/line.rs Normal file
View file

@ -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);
}

View file

@ -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;