diff options
| author | Arne Dußin | 2020-11-23 21:06:56 +0100 |
|---|---|---|
| committer | Arne Dußin | 2020-11-23 21:06:56 +0100 |
| commit | 1363c7713d19bd733a97dff5727827cf7684a27b (patch) | |
| tree | c12eb0f0f2374f7450324bb01250440b004da0ad /src/math/polygon/triangulate.rs | |
| parent | abf55d8d46fc7d5cfccc9f778da6fca10b33d0cd (diff) | |
| download | graf_karto-1363c7713d19bd733a97dff5727827cf7684a27b.tar.gz graf_karto-1363c7713d19bd733a97dff5727827cf7684a27b.zip | |
Add ear clipping algorithm
Diffstat (limited to 'src/math/polygon/triangulate.rs')
| -rw-r--r-- | src/math/polygon/triangulate.rs | 114 |
1 files changed, 110 insertions, 4 deletions
diff --git a/src/math/polygon/triangulate.rs b/src/math/polygon/triangulate.rs index 4860518..096a1c6 100644 --- a/src/math/polygon/triangulate.rs +++ b/src/math/polygon/triangulate.rs @@ -1,13 +1,119 @@ //! Module for turning a polygon into a number of non-overlapping triangles. use super::Polygon; -use crate::math::Triangle; -use nalgebra::Scalar; +use crate::math::{self, LineSegment, Surface, Triangle}; +use nalgebra::{RealField, Scalar}; + +/// Type that saves the flags that match a corner in a space efficient manner. +type Flags = u8; + +/// Tells the algorithm that this corner of the polygon is an ear. An ear means the adjacent corners +/// form a triangle with this corner of which the area is entirely contained by the polygon itself. +const FLAG_EAR: Flags = 0b0000_0001; +/// Tells the algorithm that this corner is convex, meaning its internal angle is less than Pi. +/// Useful, because this is a necessary condition for earness. False if the vertex is reflex. +// TODO: The convex flag is a remnant from the previous algorithm, but currently it's not being +// used. Consider removing it entirely. +const FLAG_CONVEX: Flags = 0b0000_0010; + +fn flag_corner<T: Scalar + Copy>(polygon: &Polygon<T>, corner: usize) -> Flags +where + T: RealField, +{ + // 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(); + let next = (corner + 1) % polygon.corners.len(); + + /* Since the angle is also in counterclockwise direction, like the polygon itself, the corner + * is convex if and only if the angle is **not**. + */ + if math::triplet_angle( + polygon.corners[prev], + polygon.corners[corner], + polygon.corners[next], + ) < T::pi() + { + // The corner is reflex. + return 0b0; + } + + // The corner is convex, check if it is also an ear. + if polygon.contains_line_segment(&LineSegment::new( + polygon.corners[prev], + polygon.corners[next], + )) { + // Corner is an ear. + FLAG_EAR | FLAG_CONVEX + } else { + // Corner is not an ear. + FLAG_CONVEX + } +} /// 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!() +pub fn triangulate<T: Scalar + Copy>(mut polygon: Polygon<T>) -> Vec<Triangle<T>> +where + T: RealField, +{ + assert!(polygon.corners.len() >= 3); + /* Information about the corner of the polygon. See the flags constant for information about + * what the bits mean. + */ + let mut flags = Vec::with_capacity(polygon.corners.len()); + for c in 0..polygon.corners.len() { + flags.push(flag_corner(&polygon, c)); + } + + let mut triangles = Vec::with_capacity(polygon.corners.len() - 2); + // Clip ears until there's only the last triangle left. + /* NOTE: This could be changed to > 2 and the last triangle would be pushed inside the loop, + * because it is also detected as an ear, however this is more logical to the original idea + * imo. + */ + while polygon.corners.len() > 3 { + // Find the ear with the highest index. + let ear = flags + .iter() + .rposition(|&x| (x & FLAG_EAR) != 0) + .expect("Polygon has more than three vertices, but no ear."); + + // Add the ear's triangle to the list. + { + let prev = (ear + polygon.corners.len() - 1) % polygon.corners.len(); + let next = (ear + 1) % polygon.corners.len(); + triangles.push(Triangle::new( + polygon.corners[prev], + polygon.corners[ear], + polygon.corners[next], + )); + + // Remove the ear from the polygon and the flag list. + polygon.corners.remove(ear); + flags.remove(ear); + } + + // Reassess the status of the two adjacent points. Notice that since the ear was removed, + // their array positions have changed. + let prev = if ear == 0 || ear == polygon.corners.len() { + polygon.corners.len() - 1 + } else { + ear - 1 + }; + let next = if ear == polygon.corners.len() { 0 } else { ear }; + + flags[prev] = flag_corner(&polygon, prev); + flags[next] = flag_corner(&polygon, next); + } + + // Push the remaining triangle into the list. + triangles.push(Triangle::new( + polygon.corners[0], + polygon.corners[1], + polygon.corners[2], + )); + + triangles } |
