diff options
Diffstat (limited to 'src/math/polygon')
| -rw-r--r-- | src/math/polygon/mod.rs | 194 | ||||
| -rw-r--r-- | src/math/polygon/polygon_graph.rs | 67 | ||||
| -rw-r--r-- | src/math/polygon/triangulate.rs | 59 |
3 files changed, 193 insertions, 127 deletions
diff --git a/src/math/polygon/mod.rs b/src/math/polygon/mod.rs index 1577f63..bc145ed 100644 --- a/src/math/polygon/mod.rs +++ b/src/math/polygon/mod.rs @@ -6,12 +6,12 @@ pub mod triangulate; pub use polygon_graph::*; pub use triangulate::*; -use super::{LineSegment, Rect, Surface, TripletOrientation, Vec2}; +use super::{ExactSurface, LineSegment, Rect, Surface, TripletOrientation, Vec2}; use crate::math; -use nalgebra::{ClosedDiv, ClosedMul, ClosedSub, RealField, Scalar}; -use num_traits::Zero; +use float_cmp::ApproxEq; +use nalgebra::{RealField, Scalar}; use serde::{Deserialize, Serialize}; -use std::ops::Neg; +use std::fmt::Debug; use thiserror::Error; /// Describes errors that can happen when handling polygons, especially on creation. @@ -41,13 +41,14 @@ impl<T: Scalar + Copy> Polygon<T> { /// be reversed to be left-turning. /// Checks if the corners make a valid polygon before creating it. If the check fails, an error /// will be returned. - pub fn new(corners: Vec<Vec2<T>>) -> Result<Self, PolygonError<T>> + pub fn new<M>(corners: Vec<Vec2<T>>, t_margin: M) -> Result<Self, PolygonError<T>> where - T: RealField, + T: RealField + ApproxEq<Margin = M>, + M: Copy, { - Self::check_validity(&corners)?; + Self::check_validity(&corners, t_margin)?; - let corners = if combined_angle(&corners) > T::zero() { + let corners = if combined_angle(&corners, t_margin) > T::zero() { corners } else { corners.into_iter().rev().collect() @@ -57,13 +58,14 @@ impl<T: Scalar + Copy> Polygon<T> { } /// Like new, but does not perform any validity checks, so be careful when using this function. - pub fn new_unchecked(corners: Vec<Vec2<T>>) -> Self + pub fn new_unchecked<M>(corners: Vec<Vec2<T>>, t_margin: M) -> Self where - T: RealField, + T: RealField + ApproxEq<Margin = M>, + M: Copy, { - assert!(Polygon::check_validity(&corners).is_ok()); + assert!(Polygon::check_validity(&corners, t_margin).is_ok()); - let corners = if combined_angle(&corners) > T::zero() { + let corners = if combined_angle(&corners, t_margin) > T::zero() { corners } else { corners.into_iter().rev().collect() @@ -74,9 +76,10 @@ impl<T: Scalar + Copy> Polygon<T> { /// Checks if a set of corners can be made into a polygon or not. Returns Ok if they can, or /// the reason they cannot in form of a PolygonError. - pub fn check_validity(corners: &[Vec2<T>]) -> Result<(), PolygonError<T>> + pub fn check_validity<M>(corners: &[Vec2<T>], t_margin: M) -> Result<(), PolygonError<T>> where - T: RealField, + T: RealField + ApproxEq<Margin = M>, + M: Copy, { if corners.len() < 3 { return Err(PolygonError::TooFewVertices(corners.len())); @@ -105,7 +108,7 @@ impl<T: Scalar + Copy> Polygon<T> { let next_j = (j + 1) % corners.len(); let line_j = LineSegment::new(corners[j], corners[next_j]); - if LineSegment::intersect(&line_i, &line_j) { + if LineSegment::intersect(&line_i, &line_j, t_margin) { return Err(PolygonError::SelfIntersect(line_i, line_j)); } } @@ -170,31 +173,28 @@ impl<T: Scalar + Copy> Polygon<T> { /// Join this polygon with another, ensuring the area of the two stays the same, but the /// overlap is not doubled, but instead joined into one. /// Returns the Polygons themselves, if there is no overlap - pub fn unite(self, other: Polygon<T>) -> Vec<Polygon<T>> + pub fn unite<M>(self, other: Polygon<T>, t_margin: M) -> Vec<Polygon<T>> where - T: RealField, + T: RealField + ApproxEq<Margin = M>, + M: Copy, { let mut graph = PolygonGraph::from_polygon(&self); graph.add_all(&other); - // TODO: Make bounding box support multiple polygons - vec![graph.bounding_polygon()] + // TODO: Make bounding polygon support multiple polygons + match graph.bounding_polygon(t_margin) { + Some(polygon) => vec![polygon], + None => vec![], + } } } -impl< - T: Scalar - + Copy - + ClosedSub - + ClosedMul - + ClosedDiv - + Neg<Output = T> - + PartialOrd - + RealField - + Zero, - > Surface<T> for Polygon<T> +impl<T, M> Surface<T, M> for Polygon<T> +where + T: RealField + ApproxEq<Margin = M>, + M: Copy, { - fn contains_point(&self, p: &Vec2<T>) -> bool { + fn contains_point(&self, p: &Vec2<T>, margin: M) -> bool { let n = self.corners.len(); let a = self.corners[n - 1]; @@ -247,7 +247,7 @@ impl< } let lx = ax + (((bx - ax) * -ay) / (by - ay)); - if lx == T::zero() { + if lx.approx_eq(T::zero(), margin) { // point on edge return true; } @@ -269,11 +269,13 @@ impl< (depth & 1) == 1 } - fn contains_line_segment(&self, line_segment: &LineSegment<T>) -> bool { + fn contains_line_segment(&self, line_segment: &LineSegment<T>, margin: M) -> bool { /* In case at least one of the points is not contained by the polygon, the line cannot lie * inside of the polygon in its entirety. */ - if !self.contains_point(&line_segment.start) || !self.contains_point(&line_segment.end) { + if !self.contains_point(&line_segment.start, margin) + || !self.contains_point(&line_segment.end, margin) + { return false; } @@ -294,9 +296,13 @@ impl< let prev = (c + self.corners.len() - 1) % self.corners.len(); let next = (c + 1) % self.corners.len(); - let edge_angle = - math::triplet_angle(self.corners[prev], self.corners[c], self.corners[next]); - let vec_angle = math::triplet_angle(self.corners[prev], self.corners[c], p); + let edge_angle = math::triplet_angle( + self.corners[prev], + self.corners[c], + self.corners[next], + margin, + ); + let vec_angle = math::triplet_angle(self.corners[prev], self.corners[c], p, margin); vec_angle == T::zero() || vec_angle >= edge_angle }; @@ -328,16 +334,18 @@ impl< let current_edge = LineSegment::new(self.corners[c], self.corners[next]); - if LineSegment::intersect(&line_segment, ¤t_edge) { + if LineSegment::intersect(&line_segment, ¤t_edge, margin) { let orientation_start = math::triplet_orientation( current_edge.start, current_edge.end, line_segment.start, + margin, ); let orientation_end = math::triplet_orientation( current_edge.start, current_edge.end, line_segment.end, + margin, ); match (orientation_start, orientation_end) { /* If at least one of the points is on the edge, make sure, the line points @@ -364,7 +372,7 @@ impl< true } - fn contains_rect(&self, rect: &Rect<T>) -> bool { + fn contains_rect(&self, rect: &Rect<T>, margin: M) -> bool { /* Turn the rectangle into a vector with its hull line segments. If all hull segments are * contained in the polygon, the rectangle is contained completely. */ @@ -393,18 +401,19 @@ impl< hull_edges .iter() - .all(|edge| self.contains_line_segment(edge)) + .all(|edge| self.contains_line_segment(edge, margin)) } - fn contains_polygon(&self, polygon: &Polygon<T>) -> bool { + fn contains_polygon(&self, polygon: &Polygon<T>, margin: M) -> bool { /* Check for all edges of the polygon that they are contained by the polygon. If they all * are, then the polygon itself must also be contained. */ for i in 0..polygon.corners.len() { let next = (i + 1) % polygon.corners.len(); - if !self - .contains_line_segment(&LineSegment::new(polygon.corners[i], polygon.corners[next])) - { + if !self.contains_line_segment( + &LineSegment::new(polygon.corners[i], polygon.corners[next]), + margin, + ) { return false; } } @@ -421,13 +430,17 @@ impl< * after another until finally connecting the last point to the first point in radians. Negative, * when the points in sum are right-turning, positive, when they are left-turning. */ -fn combined_angle<T: Scalar + Copy + RealField>(points: &[Vec2<T>]) -> T { +fn combined_angle<T: Scalar + Copy + RealField, M>(points: &[Vec2<T>], margin: M) -> T +where + T: ApproxEq<Margin = M>, + M: Copy, +{ let mut combined_angle = T::zero(); for i in 0..points.len() { let prev = (i + points.len() - 1) % points.len(); let next = (i + 1) % points.len(); - let angle = math::triplet_angle(points[prev], points[i], points[next]); + let angle = math::triplet_angle(points[prev], points[i], points[next], margin); if angle == T::zero() || angle == T::two_pi() { continue; } @@ -445,21 +458,27 @@ mod test { #[test] fn check_validity() { - Polygon::check_validity(&[Vec2::new(0., 0.), Vec2::new(1., 0.), Vec2::new(0., 1.)]) - .expect("Simple triangle does not pass validity check"); + Polygon::check_validity( + &[Vec2::new(0., 0.), Vec2::new(1., 0.), Vec2::new(0., 1.)], + (f64::EPSILON, 0), + ) + .expect("Simple triangle does not pass validity check"); } #[test] fn polygon_contains() { - let polygon = Polygon::new(vec![ - Vec2::new(0., 0.), - Vec2::new(-1., 1.), - Vec2::new(0., 2.), - Vec2::new(1., 3.), - Vec2::new(3., 1.5), - Vec2::new(2., 0.), - Vec2::new(1., 1.), - ]) + let polygon = Polygon::new( + vec![ + Vec2::new(0., 0.), + Vec2::new(-1., 1.), + Vec2::new(0., 2.), + Vec2::new(1., 3.), + Vec2::new(3., 1.5), + Vec2::new(2., 0.), + Vec2::new(1., 1.), + ], + (f64::EPSILON, 0), + ) .unwrap(); assert!(!polygon.contains_point(&Vec2::new(1., -2.))); @@ -474,18 +493,21 @@ mod test { #[test] fn contains_line_segment() { - let polygon = Polygon::new(vec![ - Vec2::new(0., 0.), - Vec2::new(0., 4.5), - Vec2::new(6.5, 4.5), - Vec2::new(5.5, 0.), - Vec2::new(5.5, 3.), - Vec2::new(1.5, 3.), - Vec2::new(1.5, 1.), - Vec2::new(2., 0.5), - Vec2::new(4., 2.), - Vec2::new(4., 0.), - ]) + let polygon = Polygon::new( + vec![ + Vec2::new(0., 0.), + Vec2::new(0., 4.5), + Vec2::new(6.5, 4.5), + Vec2::new(5.5, 0.), + Vec2::new(5.5, 3.), + Vec2::new(1.5, 3.), + Vec2::new(1.5, 1.), + Vec2::new(2., 0.5), + Vec2::new(4., 2.), + Vec2::new(4., 0.), + ], + (f64::EPSILON, 0), + ) .unwrap(); /* NOTE: From now on, inside means inside the polygon, but might be on an edge or on a @@ -531,22 +553,28 @@ mod test { #[test] fn polygon_union() { - let first = Polygon::new(vec![ - Vec2::new(-2., 1.), - Vec2::new(-0.5, 2.5), - Vec2::new(2., 2.), - Vec2::new(0.5, 1.5), - Vec2::new(1., 0.), - Vec2::new(-0.5, 1.), - ]) + let first = Polygon::new( + vec![ + Vec2::new(-2., 1.), + Vec2::new(-0.5, 2.5), + Vec2::new(2., 2.), + Vec2::new(0.5, 1.5), + Vec2::new(1., 0.), + Vec2::new(-0.5, 1.), + ], + (f64::EPSILON, 0), + ) .unwrap(); - let second = Polygon::new(vec![ - Vec2::new(0., 0.), - Vec2::new(-2., 2.), - Vec2::new(3., 2.), - Vec2::new(1.5, 0.), - ]) + let second = Polygon::new( + vec![ + Vec2::new(0., 0.), + Vec2::new(-2., 2.), + Vec2::new(3., 2.), + Vec2::new(1.5, 0.), + ], + (f64::EPSILON, 0), + ) .unwrap(); let union = first.unite(second); diff --git a/src/math/polygon/polygon_graph.rs b/src/math/polygon/polygon_graph.rs index fd373dd..5e3a576 100644 --- a/src/math/polygon/polygon_graph.rs +++ b/src/math/polygon/polygon_graph.rs @@ -5,16 +5,18 @@ use super::Polygon; use crate::math::{self, LineSegment, Vec2}; +use float_cmp::ApproxEq; use nalgebra::{RealField, Scalar}; use std::cmp::{Ordering, PartialOrd}; -#[derive(Debug)] -struct Node<T: Scalar + Copy> { +#[derive(Debug, Clone)] +pub(super) struct Node<T: Scalar + Copy> { pub vec: Vec2<T>, pub adjacent: Vec<Vec2<T>>, } -struct EdgeIterator<'a, T: Scalar + Copy> { +/// An iterator over the graph edges. These are not in a particular order. +pub struct EdgeIterator<'a, T: Scalar + Copy> { nodes: &'a [Node<T>], pos: (usize, usize), } @@ -22,7 +24,7 @@ struct EdgeIterator<'a, T: Scalar + Copy> { /// An undirected graph, that is optimised for polygon edge operations. Since edges of a polygon /// are an undirected graph, this structure also holds both directions. This makes it rather space /// inefficient, but makes edge operations rather swift. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct PolygonGraph<T: Scalar + Copy + PartialOrd> { /// The nodes of the graph, together with their adjacency list. nodes: Vec<Node<T>>, @@ -45,7 +47,7 @@ fn find_node<T: Scalar + Copy + PartialOrd>( } impl<'a, T: Scalar + Copy> EdgeIterator<'a, T> { - pub fn new(nodes: &'a [Node<T>]) -> Self { + pub(super) fn new(nodes: &'a [Node<T>]) -> Self { Self { nodes, pos: (0, 0) } } } @@ -114,6 +116,11 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { // Helper function to add the edge into the internal graph representation for one side only. // Since to the outside the graph should always be undirected, this must be private. fn add_edge_onesided(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool { + // Cannot add self-referential edges. + if from == to { + return false; + } + match find_node(&self.nodes, from) { Ok(pos) => match find_vec2(&self.nodes[pos].adjacent, to) { Ok(_) => return false, @@ -131,8 +138,10 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { true } - /// Add an edge between the given vectors. If the edge already exists, it does nothing and - /// returns false, otherwise it returns true after addition. + /// Add an edge between the given vectors. If the edge already exists or the starting and end + /// point are the same, it does nothing and returns false, otherwise it returns true after + /// addition. Note, that in a normal graph adding a self-referential edge would be perfectly fine, + /// but in a graph representing a polygon this does not really make sense. pub fn add_edge(&mut self, from: &Vec2<T>, to: &Vec2<T>) -> bool { if !self.add_edge_onesided(from, to) { return false; @@ -204,9 +213,10 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { /// Calculates all points where the graph edges intersect with one another. It then adds them /// into the adjacency list such that the intersection point lies between the nodes of the /// lines. - pub fn intersect_self(&mut self) + pub fn intersect_self<M>(&mut self, margin: M) where - T: RealField, + T: RealField + ApproxEq<Margin = M>, + M: Copy, { // Find all intersections with all other edges. let mut to_delete: Vec<LineSegment<T>> = Vec::new(); @@ -216,12 +226,14 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { * intersecting with. */ let mut intersections: Vec<Vec2<T>> = Vec::new(); - for compare_segment in EdgeIterator::new(&self.nodes) { + for compare_segment in self.edge_iter() { if segment.eq_ignore_dir(&compare_segment) { continue; } - if let Some(intersection) = LineSegment::intersection(&segment, &compare_segment) { + if let Some(intersection) = + LineSegment::intersection(&segment, &compare_segment, margin) + { intersections.push(intersection); } } @@ -233,7 +245,7 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { to_delete.push(segment.clone()); // Safe, since at least the line segment itself is represented. - let segments = segment.segments(&intersections); + let segments = segment.segments(&intersections, margin); for i in 1..segments.len() { to_add.push((segments[i - 1], segments[i])); } @@ -247,16 +259,32 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { } } + /// Get an iterator over all edges in the graph. + pub fn edge_iter(&self) -> EdgeIterator<T> { + EdgeIterator::new(&self.nodes) + } + + /// Check if the the graph has a vertex (node) at the given position. Returns true if so. + /// A point that lies on an edge, but is not registered as a node will not count. + pub fn has_node(&self, at: &Vec2<T>) -> bool { + find_node(&self.nodes, at).is_ok() + } + /// Finds the minimal polygon that could hold this graph together, i.e. could contain the /// entire graph, but with the minimal amount of space. It may however still contain extra /// corner points, meaning an extra edge for three collinear points on this edge, that can be /// cleaned up. - pub fn bounding_polygon(mut self) -> Polygon<T> + /// If the graph cannot be turned into a polygon, it will return `None` + pub fn bounding_polygon<M>(mut self, margin: M) -> Option<Polygon<T>> where - T: RealField, + T: RealField + ApproxEq<Margin = M>, + M: Copy, { - assert!(self.num_nodes() >= 3); - self.intersect_self(); + if self.num_nodes() < 3 { + return None; + } + + self.intersect_self(margin); /* Start with the top-left node. Since the nodes are always sorted by y over x from top to * bottom and left to right, this is the very first element in the vector. This is also a @@ -279,8 +307,8 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { .adjacent .iter() .max_by(|&a, &b| { - math::triplet_angle(last_vec, current_node.vec, *a) - .partial_cmp(&math::triplet_angle(last_vec, current_node.vec, *b)) + math::triplet_angle(last_vec, current_node.vec, *a, margin) + .partial_cmp(&math::triplet_angle(last_vec, current_node.vec, *b, margin)) .unwrap_or(Ordering::Equal) }) .expect("Adjacency list is empty. The polygon has an open edge (is broken)"); @@ -296,7 +324,8 @@ impl<T: Scalar + Copy + PartialOrd> PolygonGraph<T> { .expect("Failure to find node that should be inside list.")]; } - Polygon::new(bounding_corners).expect("PolygonGraph produced invalid polygon") + // Try to create a polygon from the corners and return it. + Polygon::new(bounding_corners, margin).ok() } } diff --git a/src/math/polygon/triangulate.rs b/src/math/polygon/triangulate.rs index 8a18cd7..4106095 100644 --- a/src/math/polygon/triangulate.rs +++ b/src/math/polygon/triangulate.rs @@ -2,7 +2,8 @@ use super::Polygon; use crate::math::{self, LineSegment, Surface, Triangle}; -use nalgebra::{RealField, Scalar}; +use float_cmp::ApproxEq; +use nalgebra::RealField; /// Type that saves the flags that match a corner in a space efficient manner. type Flags = u8; @@ -16,9 +17,10 @@ const FLAG_EAR: Flags = 0b0000_0001; // used. Consider removing it entirely. const FLAG_CONVEX: Flags = 0b0000_0010; -fn flag_corner<T: Scalar + Copy>(polygon: &Polygon<T>, corner: usize) -> Flags +fn flag_corner<T: RealField, M>(polygon: &Polygon<T>, corner: usize, margin: M) -> Flags where - T: RealField, + T: ApproxEq<Margin = M>, + M: Copy, { // First, check if it is convex. If it is not, it can also not be an ear. let prev = (corner + polygon.corners.len() - 1) % polygon.corners.len(); @@ -31,6 +33,7 @@ where polygon.corners[prev], polygon.corners[corner], polygon.corners[next], + margin, ) < T::pi() { // The corner is reflex. @@ -38,10 +41,10 @@ where } // The corner is convex, check if it is also an ear. - if polygon.contains_line_segment(&LineSegment::new( - polygon.corners[prev], - polygon.corners[next], - )) { + if polygon.contains_line_segment( + &LineSegment::new(polygon.corners[prev], polygon.corners[next]), + margin, + ) { // Corner is an ear. FLAG_EAR | FLAG_CONVEX } else { @@ -50,13 +53,14 @@ where } } -/// Uses earclipping algorithm (see https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf) +/// 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>(mut polygon: Polygon<T>) -> Vec<Triangle<T>> +pub fn triangulate<T: RealField, M>(mut polygon: Polygon<T>, margin: M) -> Vec<Triangle<T>> where - T: RealField, + T: ApproxEq<Margin = M>, + M: Copy, { assert!(polygon.corners.len() >= 3); /* Information about the corner of the polygon. See the flags constant for information about @@ -64,7 +68,7 @@ where */ let mut flags = Vec::with_capacity(polygon.corners.len()); for c in 0..polygon.corners.len() { - flags.push(flag_corner(&polygon, c)); + flags.push(flag_corner(&polygon, c, margin)); } let mut triangles = Vec::with_capacity(polygon.corners.len() - 2); @@ -91,6 +95,7 @@ where polygon.corners[prev], polygon.corners[ear], polygon.corners[next], + margin, )); // Remove the ear from the polygon and the flag list. @@ -107,8 +112,8 @@ where }; let next = if ear == polygon.corners.len() { 0 } else { ear }; - flags[prev] = flag_corner(&polygon, prev); - flags[next] = flag_corner(&polygon, next); + flags[prev] = flag_corner(&polygon, prev, margin); + flags[next] = flag_corner(&polygon, next, margin); } // Push the remaining triangle into the list. @@ -116,6 +121,7 @@ where polygon.corners[0], polygon.corners[1], polygon.corners[2], + margin, )); triangles @@ -128,18 +134,21 @@ mod test { #[test] fn triangulate() { - let polygon = Polygon::new(vec![ - Vec2::new(0., 0.), - Vec2::new(0., 4.5), - Vec2::new(6.5, 4.5), - Vec2::new(5.5, 0.), - Vec2::new(5.5, 3.), - Vec2::new(1.5, 3.), - Vec2::new(1.5, 1.), - Vec2::new(2., 0.5), - Vec2::new(4., 2.), - Vec2::new(4., 0.), - ]) + let polygon = Polygon::new( + vec![ + Vec2::new(0., 0.), + Vec2::new(0., 4.5), + Vec2::new(6.5, 4.5), + Vec2::new(5.5, 0.), + Vec2::new(5.5, 3.), + Vec2::new(1.5, 3.), + Vec2::new(1.5, 1.), + Vec2::new(2., 0.5), + Vec2::new(4., 2.), + Vec2::new(4., 0.), + ], + (f64::EPSILON, 0), + ) .unwrap(); let triangles = super::triangulate(polygon); |
