Skip to main content

geo/algorithm/
triangulate_earcut.rs

1use crate::{CoordFloat, CoordsIter, Polygon, Triangle, coord};
2use earcut::Earcut;
3
4/// Triangulate polygons using an [ear-cutting algorithm](https://www.geometrictools.com/Documentation/TriangulationByEarClipping.pdf).
5///
6/// Requires the `"earcut"` feature, which is enabled by default.
7pub trait TriangulateEarcut<T: CoordFloat> {
8    /// # Examples
9    ///
10    /// ```
11    /// use geo::{coord, polygon, Triangle, TriangulateEarcut};
12    ///
13    /// let square_polygon = polygon![
14    ///     (x: 0., y: 0.), // SW
15    ///     (x: 10., y: 0.), // SE
16    ///     (x: 10., y: 10.), // NE
17    ///     (x: 0., y: 10.), // NW
18    ///     (x: 0., y: 0.), // SW
19    /// ];
20    ///
21    /// let triangles = square_polygon.earcut_triangles();
22    ///
23    /// assert_eq!(
24    ///     vec![
25    ///         Triangle(
26    ///             coord! { x: 0., y: 0. }, // SW
27    ///             coord! { x: 10., y: 0. }, // SE
28    ///             coord! { x: 10., y: 10. }, // NE
29    ///         ),
30    ///         Triangle(
31    ///             coord! { x: 10., y: 10. }, // NE
32    ///             coord! { x: 0., y: 10. }, // NW
33    ///             coord! { x: 0., y: 0. }, // SW
34    ///         ),
35    ///     ],
36    ///     triangles,
37    /// );
38    /// ```
39    fn earcut_triangles(&self) -> Vec<Triangle<T>> {
40        self.earcut_triangles_iter().collect()
41    }
42
43    /// # Examples
44    ///
45    /// ```
46    /// use geo::{coord, polygon, Triangle, TriangulateEarcut};
47    ///
48    /// let square_polygon = polygon![
49    ///     (x: 0., y: 0.), // SW
50    ///     (x: 10., y: 0.), // SE
51    ///     (x: 10., y: 10.), // NE
52    ///     (x: 0., y: 10.), // NW
53    ///     (x: 0., y: 0.), // SW
54    /// ];
55    ///
56    /// let mut triangles_iter = square_polygon.earcut_triangles_iter();
57    ///
58    /// assert_eq!(
59    ///     Some(Triangle(
60    ///         coord! { x: 0., y: 0. }, // SW
61    ///         coord! { x: 10., y: 0. }, // SE
62    ///         coord! { x: 10., y: 10. }, // NE
63    ///     )),
64    ///     triangles_iter.next(),
65    /// );
66    ///
67    /// assert_eq!(
68    ///     Some(Triangle(
69    ///         coord! { x: 10., y: 10. }, // NE
70    ///         coord! { x: 0., y: 10. }, // NW
71    ///         coord! { x: 0., y: 0. }, // SW
72    ///     )),
73    ///     triangles_iter.next(),
74    /// );
75    ///
76    /// assert!(triangles_iter.next().is_none());
77    /// ```
78    fn earcut_triangles_iter(&self) -> Iter<T> {
79        Iter(self.earcut_triangles_raw())
80    }
81
82    /// Return the raw result from the `earcut` library: a one-dimensional vector of polygon
83    /// vertices (in XY order), and the indices of the triangles within the vertices vector. This
84    /// method is less ergonomic than the `earcut_triangles` and `earcut_triangles_iter`
85    /// methods, but can be helpful when working in graphics contexts that expect flat vectors of
86    /// data.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use geo::{coord, polygon, Triangle, TriangulateEarcut};
92    /// use geo::triangulate_earcut::RawTriangulation;
93    ///
94    /// let square_polygon = polygon![
95    ///     (x: 0., y: 0.), // SW
96    ///     (x: 10., y: 0.), // SE
97    ///     (x: 10., y: 10.), // NE
98    ///     (x: 0., y: 10.), // NW
99    ///     (x: 0., y: 0.), // SW
100    /// ];
101    ///
102    /// let mut triangles_raw = square_polygon.earcut_triangles_raw();
103    ///
104    /// assert_eq!(
105    ///     RawTriangulation {
106    ///         vertices: vec![
107    ///             [0., 0.], // SW
108    ///             [10., 0.], // SE
109    ///             [10., 10.], // NE
110    ///             [0., 10.], // NW
111    ///         ],
112    ///         triangle_indices: vec![
113    ///             2, 3, 0, // NE-NW-SW
114    ///             0, 1, 2, // SW-SE-NE
115    ///         ],
116    ///     },
117    ///     triangles_raw,
118    /// );
119    /// ```
120    fn earcut_triangles_raw(&self) -> RawTriangulation<T>;
121}
122
123impl<T: CoordFloat> TriangulateEarcut<T> for Polygon<T> {
124    fn earcut_triangles_raw(&self) -> RawTriangulation<T> {
125        let mut earcut = Earcut::new();
126
127        let input = EarcutInput::from(self);
128        // PERF: expose a way to pass in a re-usable output vector for those who are triangulating
129        // in a hot loop
130        let mut triangle_indices = vec![];
131        earcut.earcut(
132            input.vertices.clone(), // PERF: iterate `vertices` lazily rather than create a Vec
133            &input.interior_indexes,
134            &mut triangle_indices,
135        );
136
137        RawTriangulation {
138            // PERF: As mentioned above, if we don't manifest a concrete vertices Vec,
139            // we'll need to return some kind of "getter" that translates triangle_indices back to polygon coords, accounting
140            // for the lack of an explicit "closing" coordinate in the triangle_indices
141            //
142            // We'll need to measure if all that indirection is cheaper than just manifesting the Vec.
143            vertices: input.vertices,
144            triangle_indices,
145        }
146    }
147}
148
149/// The raw result of triangulating a polygon from `earcut`.
150#[derive(Debug, PartialEq, Clone)]
151pub struct RawTriangulation<T: CoordFloat> {
152    /// Flattened vector of polygon vertices (in XY order).
153    pub vertices: Vec<[T; 2]>,
154
155    /// Indices of the triangles within the vertices vector.
156    pub triangle_indices: Vec<usize>,
157}
158
159#[derive(Debug)]
160pub struct Iter<T: CoordFloat>(RawTriangulation<T>);
161
162impl<T: CoordFloat> Iterator for Iter<T> {
163    type Item = Triangle<T>;
164
165    fn next(&mut self) -> Option<Self::Item> {
166        let triangle_index_1 = self.0.triangle_indices.pop()?;
167        let triangle_index_2 = self.0.triangle_indices.pop()?;
168        let triangle_index_3 = self.0.triangle_indices.pop()?;
169        Some(Triangle::new(
170            self.triangle_index_to_coord(triangle_index_1),
171            self.triangle_index_to_coord(triangle_index_2),
172            self.triangle_index_to_coord(triangle_index_3),
173        ))
174    }
175}
176
177impl<T: CoordFloat> Iter<T> {
178    fn triangle_index_to_coord(&self, triangle_index: usize) -> crate::Coord<T> {
179        coord! {
180            x: self.0.vertices[triangle_index][0],
181            y: self.0.vertices[triangle_index][1],
182        }
183    }
184}
185
186struct EarcutInput<T: CoordFloat> {
187    pub vertices: Vec<[T; 2]>,
188    pub interior_indexes: Vec<usize>,
189}
190
191impl<'a, T: CoordFloat> From<&'a Polygon<T>> for EarcutInput<T> {
192    fn from(polygon: &'a Polygon<T>) -> EarcutInput<T> {
193        let mut vertices = Vec::with_capacity(polygon.coords_count() - 1);
194        let mut interior_indexes = Vec::with_capacity(polygon.interiors().len());
195        debug_assert!(polygon.exterior().0.len() >= 4);
196
197        flatten_ring(polygon.exterior(), &mut vertices);
198
199        for interior in polygon.interiors() {
200            debug_assert!(interior.0.len() >= 4);
201            interior_indexes.push(vertices.len());
202            flatten_ring(interior, &mut vertices);
203        }
204
205        Self {
206            vertices,
207            interior_indexes,
208        }
209    }
210}
211
212fn flatten_ring<T: CoordFloat>(line_string: &crate::LineString<T>, vertices: &mut Vec<[T; 2]>) {
213    if line_string.0.is_empty() {
214        return;
215    }
216    debug_assert!(line_string.is_closed(), "Only suitable for polygon rings");
217    // skip final (redundant) coord for closed line_string to match
218    // earcutr's expected input
219    for coord in &line_string.0[0..line_string.0.len() - 1] {
220        vertices.push([coord.x, coord.y]);
221    }
222}
223
224#[cfg(test)]
225mod test {
226    use super::TriangulateEarcut;
227    use crate::{polygon, wkt};
228
229    #[test]
230    fn test_triangle() {
231        let triangle_polygon = polygon![
232            (x: 0., y: 0.),
233            (x: 10., y: 0.),
234            (x: 10., y: 10.),
235            (x: 0., y: 0.),
236        ];
237
238        let triangles = triangle_polygon.earcut_triangles();
239
240        assert_eq!(&[wkt!(TRIANGLE(10.0 0.0,10.0 10.0,0.0 0.0))][..], triangles,);
241    }
242
243    #[test]
244    fn test_square() {
245        let square_polygon = polygon![
246            (x: 0., y: 0.),
247            (x: 10., y: 0.),
248            (x: 10., y: 10.),
249            (x: 0., y: 10.),
250            (x: 0., y: 0.),
251        ];
252
253        let triangles = square_polygon.earcut_triangles();
254
255        assert_eq!(
256            &[
257                wkt!(TRIANGLE(0.0 0.0,10.0 0.0,10.0 10.0)),
258                wkt!(TRIANGLE(10.0 10.0,0.0 10.0,0.0 0.0))
259            ][..],
260            &triangles,
261        );
262    }
263
264    #[test]
265    fn test_square_raw() {
266        let square_polygon = wkt!(POLYGON(
267            (0. 0.,10. 0., 10. 10., 0. 10.)
268        ));
269
270        let triangles = square_polygon.earcut_triangles_raw();
271        assert_eq!(
272            triangles.vertices,
273            vec![[0., 0.], [10., 0.], [10., 10.], [0., 10.]] // exterior
274        );
275        assert_eq!(triangles.triangle_indices, vec![2, 3, 0, 0, 1, 2]);
276    }
277
278    #[test]
279    fn test_square_with_hole_raw() {
280        let poly_with_hole = wkt!(POLYGON(
281            (0. 0.,10. 0., 10. 10., 0. 10.,0. 0.),
282            (2. 2., 8. 2., 8. 8., 2. 8.,2. 2.)
283        ));
284
285        let triangles = poly_with_hole.earcut_triangles_raw();
286
287        assert_eq!(
288            triangles.vertices,
289            vec![
290                // exterior
291                [0., 0.],
292                [10., 0.],
293                [10., 10.],
294                [0., 10.],
295                // interior hole
296                [2., 2.],
297                [8., 2.],
298                [8., 8.],
299                [2., 8.],
300            ]
301        );
302
303        // manually inspected that the output was a reasonable triangulation.
304        // let output = poly_with_hole.earcut_triangles();
305        // let mp = crate::MultiPolygon::new(output.iter().map(|tri| tri.to_polygon()).collect());
306        // dbg!(mp);
307        assert_eq!(
308            triangles.triangle_indices,
309            vec![
310                0, 4, 7, 5, 4, 0, 3, 0, 7, 5, 0, 1, 2, 3, 7, 6, 5, 1, 2, 7, 6, 6, 1, 2
311            ]
312        );
313    }
314}