1use alloc::{vec, vec::Vec};
10
11use geometry_coords::precise_math;
12use geometry_cs::{CartesianFamily, CoordinateSystem};
13use geometry_model::{Polygon as ModelPolygon, Ring as ModelRing};
14use geometry_tag::SameAs;
15use geometry_trait::{Point, Polygon, Ring};
16
17#[inline]
23#[must_use]
24pub fn triangulate_earcut<Pg, P>(polygon: &Pg) -> Vec<ModelPolygon<P>>
25where
26 Pg: Polygon<Point = P>,
27 P: Point<Scalar = f64> + Copy,
28 P::Cs: CoordinateSystem,
29 <P::Cs as CoordinateSystem>::Family: SameAs<CartesianFamily>,
30{
31 let mut exterior = ring_vertices(polygon.exterior());
32 if exterior.len() < 3 {
33 return Vec::new();
34 }
35 make_counter_clockwise(&mut exterior);
36
37 let mut holes: Vec<Vec<P>> = polygon
38 .interiors()
39 .map(ring_vertices)
40 .filter(|ring| ring.len() >= 3)
41 .collect();
42 for hole in &mut holes {
43 make_clockwise(hole);
44 }
45 for hole_index in 0..holes.len() {
46 let Some(merged) = bridge_hole(&exterior, &holes[hole_index], &holes) else {
47 return Vec::new();
48 };
49 exterior = merged;
50 }
51
52 clip_ears(&exterior)
53}
54
55fn ring_vertices<R, P>(ring: &R) -> Vec<P>
56where
57 R: Ring<Point = P>,
58 P: Point<Scalar = f64> + Copy,
59{
60 let mut points: Vec<P> = ring.points().copied().collect();
61 while points.len() > 1 && same_point(&points[0], points.last().unwrap_or(&points[0])) {
62 points.pop();
63 }
64 points.dedup_by(|second, first| same_point(first, second));
65 points
66}
67
68fn make_counter_clockwise<P: Point<Scalar = f64>>(points: &mut [P]) {
69 if signed_area(points) < 0.0 {
70 points.reverse();
71 }
72}
73
74fn make_clockwise<P: Point<Scalar = f64>>(points: &mut [P]) {
75 if signed_area(points) > 0.0 {
76 points.reverse();
77 }
78}
79
80fn signed_area<P: Point<Scalar = f64>>(points: &[P]) -> f64 {
81 if points.len() < 3 {
82 return 0.0;
83 }
84 let mut area = 0.0;
85 for index in 0..points.len() {
86 let first = &points[index];
87 let second = &points[(index + 1) % points.len()];
88 area += first.get::<0>() * second.get::<1>() - second.get::<0>() * first.get::<1>();
89 }
90 area / 2.0
91}
92
93fn bridge_hole<P>(exterior: &[P], hole: &[P], holes: &[Vec<P>]) -> Option<Vec<P>>
94where
95 P: Point<Scalar = f64> + Copy,
96{
97 let hole_vertex = hole
98 .iter()
99 .enumerate()
100 .max_by(|(_, left), (_, right)| {
101 left.get::<0>()
102 .total_cmp(&right.get::<0>())
103 .then_with(|| right.get::<1>().total_cmp(&left.get::<1>()))
104 })?
105 .0;
106 let source = hole[hole_vertex];
107
108 let exterior_vertex = (0..exterior.len())
109 .filter(|&index| bridge_is_visible(source, exterior[index], index, exterior, hole, holes))
110 .min_by(|&left, &right| {
111 squared_distance(source, exterior[left])
112 .total_cmp(&squared_distance(source, exterior[right]))
113 })?;
114
115 let mut merged = Vec::with_capacity(exterior.len() + hole.len() + 2);
116 merged.extend_from_slice(&exterior[..=exterior_vertex]);
117 merged.push(source);
118 for offset in 1..hole.len() {
119 merged.push(hole[(hole_vertex + offset) % hole.len()]);
120 }
121 merged.push(source);
122 merged.push(exterior[exterior_vertex]);
123 merged.extend_from_slice(&exterior[exterior_vertex + 1..]);
124 Some(merged)
125}
126
127fn bridge_is_visible<P>(
128 source: P,
129 target: P,
130 target_index: usize,
131 exterior: &[P],
132 source_hole: &[P],
133 holes: &[Vec<P>],
134) -> bool
135where
136 P: Point<Scalar = f64> + Copy,
137{
138 if same_point(&source, &target) {
139 return false;
140 }
141 for edge in 0..exterior.len() {
142 let next = (edge + 1) % exterior.len();
143 if edge == target_index || next == target_index {
144 continue;
145 }
146 if segments_intersect(source, target, exterior[edge], exterior[next]) {
147 return false;
148 }
149 }
150 for ring in holes {
151 for edge in 0..ring.len() {
152 let next = (edge + 1) % ring.len();
153 if core::ptr::eq(ring.as_slice(), source_hole)
154 && (same_point(&ring[edge], &source) || same_point(&ring[next], &source))
155 {
156 continue;
157 }
158 if segments_intersect(source, target, ring[edge], ring[next]) {
159 return false;
160 }
161 }
162 }
163 let midpoint = [
164 source.get::<0>() / 2.0 + target.get::<0>() / 2.0,
165 source.get::<1>() / 2.0 + target.get::<1>() / 2.0,
166 ];
167 point_in_ring(midpoint, exterior)
168 && holes.iter().all(|ring| {
169 core::ptr::eq(ring.as_slice(), source_hole) || !point_in_ring(midpoint, ring)
170 })
171}
172
173fn clip_ears<P>(points: &[P]) -> Vec<ModelPolygon<P>>
174where
175 P: Point<Scalar = f64> + Copy,
176{
177 if points.len() < 3 {
178 return Vec::new();
179 }
180 let mut indices: Vec<usize> = (0..points.len()).collect();
181 let mut triangles = Vec::with_capacity(points.len().saturating_sub(2));
182 while indices.len() > 3 {
183 let mut clipped = false;
184 for position in 0..indices.len() {
185 let previous = indices[(position + indices.len() - 1) % indices.len()];
186 let current = indices[position];
187 let next = indices[(position + 1) % indices.len()];
188 if !is_ear(points, &indices, previous, current, next) {
189 continue;
190 }
191 triangles.push(triangle(points[previous], points[current], points[next]));
192 indices.remove(position);
193 clipped = true;
194 break;
195 }
196 if !clipped {
197 if let Some(position) = removable_collinear(points, &indices) {
198 indices.remove(position);
199 } else {
200 return Vec::new();
201 }
202 }
203 }
204 if orientation(points[indices[0]], points[indices[1]], points[indices[2]]) == 0.0 {
205 return Vec::new();
206 }
207 triangles.push(triangle(
208 points[indices[0]],
209 points[indices[1]],
210 points[indices[2]],
211 ));
212 triangles
213}
214
215fn is_ear<P>(points: &[P], polygon: &[usize], previous: usize, current: usize, next: usize) -> bool
216where
217 P: Point<Scalar = f64> + Copy,
218{
219 let a = points[previous];
220 let b = points[current];
221 let c = points[next];
222 if orientation(a, b, c) <= 0.0 {
223 return false;
224 }
225 if diagonal_crosses_polygon(points, polygon, previous, next) {
226 return false;
227 }
228 !polygon.iter().copied().any(|index| {
229 index != previous
230 && index != current
231 && index != next
232 && !same_point(&points[index], &a)
233 && !same_point(&points[index], &b)
234 && !same_point(&points[index], &c)
235 && point_in_triangle(points[index], a, b, c)
236 })
237}
238
239fn diagonal_crosses_polygon<P>(points: &[P], polygon: &[usize], first: usize, second: usize) -> bool
240where
241 P: Point<Scalar = f64> + Copy,
242{
243 for edge in 0..polygon.len() {
244 let edge_first = polygon[edge];
245 let edge_second = polygon[(edge + 1) % polygon.len()];
246 if edge_first == first
247 || edge_second == first
248 || edge_first == second
249 || edge_second == second
250 || same_point(&points[edge_first], &points[first])
251 || same_point(&points[edge_second], &points[first])
252 || same_point(&points[edge_first], &points[second])
253 || same_point(&points[edge_second], &points[second])
254 {
255 continue;
256 }
257 if segments_intersect(
258 points[first],
259 points[second],
260 points[edge_first],
261 points[edge_second],
262 ) {
263 return true;
264 }
265 }
266 false
267}
268
269fn removable_collinear<P>(points: &[P], polygon: &[usize]) -> Option<usize>
270where
271 P: Point<Scalar = f64> + Copy,
272{
273 (0..polygon.len()).find(|&position| {
274 let previous = polygon[(position + polygon.len() - 1) % polygon.len()];
275 let current = polygon[position];
276 let next = polygon[(position + 1) % polygon.len()];
277 same_point(&points[previous], &points[current])
278 || same_point(&points[current], &points[next])
279 || orientation(points[previous], points[current], points[next]) == 0.0
280 })
281}
282
283fn triangle<P: Point<Scalar = f64> + Copy>(a: P, b: P, c: P) -> ModelPolygon<P> {
284 ModelPolygon::new(ModelRing::from_vec(vec![a, c, b, a]))
287}
288
289fn point_in_triangle<P: Point<Scalar = f64> + Copy>(point: P, a: P, b: P, c: P) -> bool {
290 orientation(a, b, point) >= 0.0
291 && orientation(b, c, point) >= 0.0
292 && orientation(c, a, point) >= 0.0
293}
294
295fn point_in_ring<P: Point<Scalar = f64>>(point: [f64; 2], ring: &[P]) -> bool {
296 let mut inside = false;
297 for index in 0..ring.len() {
298 let first = &ring[index];
299 let second = &ring[(index + 1) % ring.len()];
300 let crosses = (first.get::<1>() > point[1]) != (second.get::<1>() > point[1]);
301 if crosses {
302 let x = (second.get::<0>() - first.get::<0>()) * (point[1] - first.get::<1>())
303 / (second.get::<1>() - first.get::<1>())
304 + first.get::<0>();
305 if point[0] < x {
306 inside = !inside;
307 }
308 }
309 }
310 inside
311}
312
313fn segments_intersect<P>(a: P, b: P, c: P, d: P) -> bool
314where
315 P: Point<Scalar = f64> + Copy,
316{
317 let ab_c = orientation(a, b, c);
318 let ab_d = orientation(a, b, d);
319 let cd_a = orientation(c, d, a);
320 let cd_b = orientation(c, d, b);
321 if ab_c == 0.0 && on_segment(a, b, c) {
322 return true;
323 }
324 if ab_d == 0.0 && on_segment(a, b, d) {
325 return true;
326 }
327 if cd_a == 0.0 && on_segment(c, d, a) {
328 return true;
329 }
330 if cd_b == 0.0 && on_segment(c, d, b) {
331 return true;
332 }
333 (ab_c > 0.0) != (ab_d > 0.0) && (cd_a > 0.0) != (cd_b > 0.0)
334}
335
336#[allow(
337 clippy::needless_pass_by_value,
338 reason = "ear clipping operates on Copy point handles throughout"
339)]
340fn orientation<P: Point<Scalar = f64>>(a: P, b: P, c: P) -> f64 {
341 precise_math::orient2d(
342 [a.get::<0>(), a.get::<1>()],
343 [b.get::<0>(), b.get::<1>()],
344 [c.get::<0>(), c.get::<1>()],
345 )
346}
347
348fn on_segment<P: Point<Scalar = f64> + Copy>(a: P, b: P, point: P) -> bool {
349 point.get::<0>() >= a.get::<0>().min(b.get::<0>())
350 && point.get::<0>() <= a.get::<0>().max(b.get::<0>())
351 && point.get::<1>() >= a.get::<1>().min(b.get::<1>())
352 && point.get::<1>() <= a.get::<1>().max(b.get::<1>())
353}
354
355fn squared_distance<P: Point<Scalar = f64> + Copy>(first: P, second: P) -> f64 {
356 let dx = second.get::<0>() - first.get::<0>();
357 let dy = second.get::<1>() - first.get::<1>();
358 dx * dx + dy * dy
359}
360
361#[allow(
362 clippy::float_cmp,
363 reason = "coordinate identity, not approximate geometric equality, is required"
364)]
365fn same_point<P: Point<Scalar = f64>>(first: &P, second: &P) -> bool {
366 first.get::<0>() == second.get::<0>() && first.get::<1>() == second.get::<1>()
367}
368
369#[cfg(test)]
370mod tests {
371 use geometry_cs::Cartesian;
372 use geometry_model::{Point2D, Polygon, Ring};
373
374 use super::*;
375 use crate::area::area;
376
377 #[test]
378 fn concave_pentagon_becomes_three_triangles() {
379 type P = Point2D<f64, Cartesian>;
380 let polygon: Polygon<P> = Polygon::new(Ring::from_vec(alloc::vec![
381 P::new(0.0, 0.0),
382 P::new(0.0, 2.0),
383 P::new(1.0, 1.0),
384 P::new(2.0, 2.0),
385 P::new(2.0, 0.0),
386 P::new(0.0, 0.0),
387 ]));
388 let triangles = triangulate_earcut(&polygon);
389 assert_eq!(triangles.len(), 3);
390 let sum: f64 = triangles.iter().map(|triangle| area(triangle).abs()).sum();
391 assert!((sum - area(&polygon).abs()).abs() < 1e-12);
392 }
393
394 #[test]
395 fn square_with_square_hole_preserves_area() {
396 type P = Point2D<f64, Cartesian>;
397 let outer: Ring<P> = Ring::from_vec(alloc::vec![
398 P::new(0.0, 0.0),
399 P::new(0.0, 4.0),
400 P::new(4.0, 4.0),
401 P::new(4.0, 0.0),
402 P::new(0.0, 0.0),
403 ]);
404 let hole: Ring<P> = Ring::from_vec(alloc::vec![
405 P::new(1.0, 1.0),
406 P::new(3.0, 1.0),
407 P::new(3.0, 3.0),
408 P::new(1.0, 3.0),
409 P::new(1.0, 1.0),
410 ]);
411 let polygon = Polygon::with_inners(outer, alloc::vec![hole]);
412
413 let triangles = triangulate_earcut(&polygon);
414
415 assert_eq!(triangles.len(), 8);
416 let sum: f64 = triangles.iter().map(|triangle| area(triangle).abs()).sum();
417 assert!((sum - area(&polygon).abs()).abs() < 1e-12);
418 }
419
420 #[test]
421 fn private_degenerate_clipping_and_intersection_guards() {
422 type P = Point2D<f64, Cartesian>;
423 assert!(signed_area(&[P::new(0.0, 0.0), P::new(1.0, 0.0)]).abs() < f64::EPSILON);
424 assert!(clip_ears::<P>(&[]).is_empty());
425 assert!(
426 clip_ears(&[
427 P::new(0.0, 0.0),
428 P::new(1.0, 0.0),
429 P::new(2.0, 0.0),
430 P::new(3.0, 0.0),
431 ])
432 .is_empty()
433 );
434
435 assert!(segments_intersect(
436 P::new(0.0, 0.0),
437 P::new(2.0, 0.0),
438 P::new(1.0, 0.0),
439 P::new(1.0, 1.0),
440 ));
441 assert!(segments_intersect(
442 P::new(0.0, 0.0),
443 P::new(2.0, 0.0),
444 P::new(1.0, 1.0),
445 P::new(1.0, 0.0),
446 ));
447 assert!(segments_intersect(
448 P::new(1.0, 0.0),
449 P::new(1.0, 1.0),
450 P::new(0.0, 0.0),
451 P::new(2.0, 0.0),
452 ));
453 assert!(segments_intersect(
454 P::new(1.0, 1.0),
455 P::new(1.0, 0.0),
456 P::new(0.0, 0.0),
457 P::new(2.0, 0.0),
458 ));
459
460 let exterior = [P::new(0.0, 0.0), P::new(2.0, 0.0), P::new(0.0, 2.0)];
461 assert!(!bridge_is_visible(
462 exterior[0],
463 exterior[0],
464 0,
465 &exterior,
466 &[],
467 &[],
468 ));
469 }
470}