diff options
| author | Arne Dußin | 2020-11-21 00:36:32 +0100 |
|---|---|---|
| committer | Arne Dußin | 2020-11-21 00:36:32 +0100 |
| commit | d7d90e8b3615db38d1af238ac9c8193c283ca156 (patch) | |
| tree | 6f500e94448748d21557b1f6cb3127a7ee9d3db7 | |
| parent | d6a0708b995b6c0e12ed8890c678c180f859de51 (diff) | |
| download | graf_karto-d7d90e8b3615db38d1af238ac9c8193c283ca156.tar.gz graf_karto-d7d90e8b3615db38d1af238ac9c8193c283ca156.zip | |
Add triangle struct and triangulation template
| -rw-r--r-- | src/math/line_segment.rs | 107 | ||||
| -rw-r--r-- | src/math/mod.rs | 4 | ||||
| -rw-r--r-- | src/math/triangle.rs | 161 | ||||
| -rw-r--r-- | src/math/triangulate.rs | 12 |
4 files changed, 183 insertions, 101 deletions
diff --git a/src/math/line_segment.rs b/src/math/line_segment.rs index e6ca70f..244b0af 100644 --- a/src/math/line_segment.rs +++ b/src/math/line_segment.rs @@ -1,4 +1,4 @@ -use super::{Rect, Vec2}; +use super::{Rect, TripletOrientation, Vec2}; use alga::general::{ClosedDiv, ClosedMul, ClosedSub}; use nalgebra::{RealField, Scalar}; use num_traits::Zero; @@ -50,10 +50,10 @@ impl<T: Scalar + Copy> LineSegment<T> { */ // Cache the necessary orientations. - let ls1_ls2start_orientation = triplet_orientation(ls1.start, ls1.end, ls2.start); - let ls1_ls2end_orientation = triplet_orientation(ls1.start, ls1.end, ls2.end); - let ls2_ls1start_orientation = triplet_orientation(ls2.start, ls2.end, ls1.start); - let ls2_ls1end_orientation = triplet_orientation(ls2.start, ls2.end, ls1.end); + let ls1_ls2start_orientation = super::triplet_orientation(ls1.start, ls1.end, ls2.start); + let ls1_ls2end_orientation = super::triplet_orientation(ls1.start, ls1.end, ls2.end); + let ls2_ls1start_orientation = super::triplet_orientation(ls2.start, ls2.end, ls1.start); + let ls2_ls1end_orientation = super::triplet_orientation(ls2.start, ls2.end, ls1.end); // Check for the first case that wase described (general case). if ls1_ls2start_orientation != ls1_ls2end_orientation @@ -139,7 +139,7 @@ impl<T: Scalar + Copy> LineSegment<T> { assert_eq!( split_points .iter() - .find(|&p| triplet_orientation(self.start, self.end, *p) + .find(|&p| super::triplet_orientation(self.start, self.end, *p) != TripletOrientation::Collinear), None ); @@ -173,76 +173,9 @@ impl<T: Scalar + Copy> LineSegment<T> { } } -#[derive(PartialEq, Eq)] -pub(crate) enum TripletOrientation { - Clockwise, - Counterclockwise, - Collinear, -} - -/// Helper function to determine which direction one would turn to traverse from the first point -/// over the second to the third point. The third option is collinear, in which case the three points -/// are on the same line. -pub(crate) fn triplet_orientation<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> TripletOrientation -where - T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero, -{ - /* Check the slopes of the vector from a to b and b to c. If the slope of ab is greater than - * that of bc, the rotation is clockwise. If ab is smaller than bc it's counterclockwise. If - * they are the same it follows that the three points are collinear. - */ - match (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y) { - q if q > T::zero() => TripletOrientation::Counterclockwise, - q if q < T::zero() => TripletOrientation::Clockwise, - _ => TripletOrientation::Collinear, - } -} - -/// Calculate the angle between three points. Guaranteed to have 0 <= angle < 2Pi. The angle is -/// calculated as the angle between a line from a to b and then from b to c, increasing -/// counterclockwise. -/// -/// # Panics -/// If the length from a to b or the length from b to c is zero. -pub(crate) fn triplet_angle<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> T -where - T: Scalar + Copy + ClosedSub + RealField + Zero, -{ - assert!(a != b); - assert!(b != c); - - // Handle the extreme 0 and 180 degree cases - let orientation = triplet_orientation(a, b, c); - if orientation == TripletOrientation::Collinear { - if LineSegment::new(a, b).contains_collinear(c) - || LineSegment::new(b, c).contains_collinear(a) - { - return T::zero(); - } else { - return T::pi(); - } - } - - // Calculate the vectors out of the points - let ba = a - b; - let bc = c - b; - - // Calculate the angle between 0 and Pi. - let angle = ((ba * bc) / (ba.length() * bc.length())).acos(); - - // Make angle into a full circle angle by looking at the orientation of the triplet. - let angle = match orientation { - TripletOrientation::Counterclockwise => T::pi() + (T::pi() - angle), - _ => angle, - }; - - angle -} - #[cfg(test)] mod test { use super::*; - use std::f64::consts::PI; #[test] fn contains_collinear() { @@ -256,32 +189,4 @@ mod test { assert!(!segment.contains_collinear(Vec2::new(3., 3.))); assert!(!segment.contains_collinear(Vec2::new(-1., 0.))); } - - #[test] - fn triplet_angle() { - assert_eq!( - super::triplet_angle(Vec2::new(0., 0.), Vec2::new(0., -1.), Vec2::new(-1., -1.)), - 1.5 * PI - ); - assert_eq!( - super::triplet_angle(Vec2::new(2., 2.), Vec2::new(0., 0.), Vec2::new(-1., -1.)), - PI - ); - assert_eq!( - super::triplet_angle(Vec2::new(3., 1.), Vec2::new(0., 0.), Vec2::new(6., 2.)), - 0. - ); - assert_eq!( - super::triplet_angle(Vec2::new(3., 1.), Vec2::new(0., 0.), Vec2::new(-6., -2.)), - PI - ); - assert_eq!( - super::triplet_angle(Vec2::new(0., 1.), Vec2::new(0., 2.), Vec2::new(-3., 2.)), - 0.5 * PI - ); - assert_eq!( - super::triplet_angle(Vec2::new(3., 1.), Vec2::new(2., 2.), Vec2::new(0., 2.)), - 0.75 * PI - ); - } } diff --git a/src/math/mod.rs b/src/math/mod.rs index 0b591d7..07bc36b 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -2,12 +2,16 @@ pub mod line_segment; pub mod polygon; pub mod polygon_graph; pub mod rect; +pub mod triangle; +pub mod triangulate; pub mod vec2; pub use self::line_segment::*; pub use self::polygon::*; pub use self::polygon_graph::*; pub use self::rect::*; +pub use self::triangle::*; +pub use self::triangulate::*; pub use self::vec2::*; use std::cmp::Ordering; diff --git a/src/math/triangle.rs b/src/math/triangle.rs new file mode 100644 index 0000000..53c7560 --- /dev/null +++ b/src/math/triangle.rs @@ -0,0 +1,161 @@ +use super::{LineSegment, Vec2}; +use alga::general::{ClosedMul, ClosedSub}; +use nalgebra::{RealField, Scalar}; +use num_traits::Zero; + +/// Represents a triangle +pub struct Triangle<T: Scalar + Copy> { + /// The three corners of the triangle. Internally, it is made sure that the corners are always + /// ordered in a counterclockwise manner, to make operations like contains simpler. + corners: [Vec2<T>; 3], +} + +impl<T: Scalar + Copy> Triangle<T> { + /// Create a new Triangle as defined by its three corner points + pub fn new(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> Self + where + T: ClosedSub + ClosedMul + PartialOrd + Zero, + { + // Make sure the three points are in counterclockwise order. + match triplet_orientation(a, b, c) { + TripletOrientation::Counterclockwise => Self { corners: [a, b, c] }, + TripletOrientation::Clockwise => Self { corners: [a, c, b] }, + TripletOrientation::Collinear => { + warn!( + "Creating triangle without any area: [{:?}, {:?}, {:?}]", + a, b, c + ); + Self { corners: [a, b, c] } + } + } + } + + /// Create a new Triangle from a three-point slice, instead of the three points one after + /// another. + pub fn from_slice(corners: [Vec2<T>; 3]) -> Self + where + T: ClosedSub + ClosedMul + PartialOrd + Zero, + { + Self::new(corners[0], corners[1], corners[2]) + } + + /// Check if the triangle contains a given point. If the point is right on an edge, it still + /// counts as inside it. + pub fn contains_point(&self, point: Vec2<T>) -> bool + where + T: ClosedSub + ClosedMul + PartialOrd + Zero, + { + // Since the points are ordered counterclockwise, check if the point is to the left of all + // edges (or on an edge) from one point to the next. When the point is to the left of all + // edges, it must be inside the triangle. + for i in 0..3 { + if triplet_orientation(self.corners[i], self.corners[(i + 1) % 3], point) + == TripletOrientation::Clockwise + { + return false; + } + } + + true + } +} + +#[derive(PartialEq, Eq)] +pub(crate) enum TripletOrientation { + Clockwise, + Counterclockwise, + Collinear, +} + +/// Helper function to determine which direction one would turn to traverse from the first point +/// over the second to the third point. The third option is collinear, in which case the three points +/// are on the same line. +pub(crate) fn triplet_orientation<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> TripletOrientation +where + T: Scalar + Copy + ClosedSub + ClosedMul + PartialOrd + Zero, +{ + /* Check the slopes of the vector from a to b and b to c. If the slope of ab is greater than + * that of bc, the rotation is clockwise. If ab is smaller than bc it's counterclockwise. If + * they are the same it follows that the three points are collinear. + */ + match (b.y - a.y) * (c.x - b.x) - (b.x - a.x) * (c.y - b.y) { + q if q > T::zero() => TripletOrientation::Counterclockwise, + q if q < T::zero() => TripletOrientation::Clockwise, + _ => TripletOrientation::Collinear, + } +} + +/// Calculate the angle between three points. Guaranteed to have 0 <= angle < 2Pi. The angle is +/// calculated as the angle between a line from a to b and then from b to c, increasing +/// counterclockwise. +/// +/// # Panics +/// If the length from a to b or the length from b to c is zero. +pub(crate) fn triplet_angle<T>(a: Vec2<T>, b: Vec2<T>, c: Vec2<T>) -> T +where + T: Scalar + Copy + ClosedSub + RealField + Zero, +{ + assert!(a != b); + assert!(b != c); + + // Handle the extreme 0 and 180 degree cases + let orientation = triplet_orientation(a, b, c); + if orientation == TripletOrientation::Collinear { + if LineSegment::new(a, b).contains_collinear(c) + || LineSegment::new(b, c).contains_collinear(a) + { + return T::zero(); + } else { + return T::pi(); + } + } + + // Calculate the vectors out of the points + let ba = a - b; + let bc = c - b; + + // Calculate the angle between 0 and Pi. + let angle = ((ba * bc) / (ba.length() * bc.length())).acos(); + + // Make angle into a full circle angle by looking at the orientation of the triplet. + let angle = match orientation { + TripletOrientation::Counterclockwise => T::pi() + (T::pi() - angle), + _ => angle, + }; + + angle +} + +#[cfg(test)] +mod test { + use super::*; + use std::f64::consts::PI; + + #[test] + fn triplet_angle() { + assert_eq!( + super::triplet_angle(Vec2::new(0., 0.), Vec2::new(0., -1.), Vec2::new(-1., -1.)), + 1.5 * PI + ); + assert_eq!( + super::triplet_angle(Vec2::new(2., 2.), Vec2::new(0., 0.), Vec2::new(-1., -1.)), + PI + ); + assert_eq!( + super::triplet_angle(Vec2::new(3., 1.), Vec2::new(0., 0.), Vec2::new(6., 2.)), + 0. + ); + assert_eq!( + super::triplet_angle(Vec2::new(3., 1.), Vec2::new(0., 0.), Vec2::new(-6., -2.)), + PI + ); + assert_eq!( + super::triplet_angle(Vec2::new(0., 1.), Vec2::new(0., 2.), Vec2::new(-3., 2.)), + 0.5 * PI + ); + assert_eq!( + super::triplet_angle(Vec2::new(3., 1.), Vec2::new(2., 2.), Vec2::new(0., 2.)), + 0.75 * PI + ); + } +} diff --git a/src/math/triangulate.rs b/src/math/triangulate.rs new file mode 100644 index 0000000..8ef92f1 --- /dev/null +++ b/src/math/triangulate.rs @@ -0,0 +1,12 @@ +//! Module for turning a polygon into a number of non-overlapping triangles. + +use super::{Polygon, Triangle}; +use nalgebra::Scalar; + +/// Uses earclipping algorithm (see https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) +/// to find an explanation of what exactly is happening. +/// Currently only handles simple polygons, but once the polygon struct supports holes must be +/// extended to also support those. +pub fn triangulate<T: Scalar + Copy>(_polygon: &Polygon<T>) -> Vec<Triangle<T>> { + unimplemented!() +} |
