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}