geop_core_math/
polygon.rs1use crate::{
8 plain::{self, cross2, sub},
9 scalars::Scalar,
10 vector::Vector2,
11};
12
13fn signed_area2(poly: &[Vector2]) -> Scalar {
16 let n = poly.len();
17 let mut sum = Scalar::ZERO;
18 for i in 0..n {
19 let j = (i + 1) % n;
20 sum = sum.add(poly[i][0].mul(poly[j][1]).sub(poly[j][0].mul(poly[i][1])));
21 }
22 sum
23}
24
25pub fn polygon_signed_area(poly: &[Vector2]) -> Scalar {
27 signed_area2(poly).div(Scalar::TWO).unwrap_or(Scalar::ZERO)
28}
29
30pub fn polygons_contain(loops: &[Vec<Vector2>], p: &Vector2) -> bool {
35 let mut inside = false;
36 for poly in loops {
37 for (i, a) in poly.iter().enumerate() {
38 let b = poly[(i + 1) % poly.len()];
39 if a[1].definitely_greater(p[1]) != b[1].definitely_greater(p[1]) {
40 let Ok(along) = p[1].sub(a[1]).div(b[1].sub(a[1])) else {
41 continue;
42 };
43 if a[0].add(along.mul(b[0].sub(a[0]))).definitely_greater(p[0]) {
44 inside = !inside;
45 }
46 }
47 }
48 }
49 inside
50}
51
52#[derive(Clone, Debug, PartialEq)]
56pub struct Convex {
57 pub sides: Vec<(Vector2, Scalar)>,
58}
59
60impl Convex {
61 pub fn new(corners: &[Vector2]) -> Option<Self> {
66 let area = signed_area2(corners);
67 if corners.len() < 3 || area.could_be_equal(Scalar::ZERO) {
68 return None;
69 }
70 let counter = area.definitely_greater(Scalar::ZERO);
73 let sides = (0..corners.len())
74 .map(|i| {
75 let (a, b) = (corners[i], corners[(i + 1) % corners.len()]);
76 let along = b.sub(&a);
77 let left = along.perp();
78 let n = if counter { left.neg() } else { left };
79 (n, n.prod_dot(&a))
80 })
81 .collect();
82 Some(Self { sides })
83 }
84
85 pub fn past<'a>(&'a self, p: &'a Vector2) -> impl Iterator<Item = Scalar> + 'a {
88 (self.sides.iter()).map(move |(n, c)| n.prod_dot(p).sub(*c))
89 }
90
91 pub fn could_contain(&self, p: &Vector2) -> bool {
94 self.past(p).all(|d| !d.definitely_greater(Scalar::ZERO))
95 }
96}
97
98pub fn ear_clip(poly: &[[f64; 2]]) -> Vec<[usize; 3]> {
103 let n = poly.len();
104 if n < 3 {
105 return Vec::new();
106 }
107 let orient_positive = plain::signed_area2(poly) >= 0.0;
108 let point_in_triangle = |p: [f64; 2], a: [f64; 2], b: [f64; 2], c: [f64; 2]| -> bool {
109 let d1 = cross2(sub(b, a), sub(p, a));
110 let d2 = cross2(sub(c, b), sub(p, b));
111 let d3 = cross2(sub(a, c), sub(p, c));
112 let has_neg = d1 < 0.0 || d2 < 0.0 || d3 < 0.0;
113 let has_pos = d1 > 0.0 || d2 > 0.0 || d3 > 0.0;
114 !(has_neg && has_pos)
115 };
116
117 let mut indices: Vec<usize> = (0..n).collect();
118 let mut triangles = Vec::with_capacity(n.saturating_sub(2));
119 for _ in 3..n {
122 let m = indices.len();
123 let mut clipped = false;
124 for k in 0..m {
125 let ip = indices[(k + m - 1) % m];
126 let ic = indices[k];
127 let inext = indices[(k + 1) % m];
128 let turn = cross2(sub(poly[ic], poly[ip]), sub(poly[inext], poly[ic]));
129 let is_convex = if orient_positive {
130 turn > 0.0
131 } else {
132 turn < 0.0
133 };
134 if !is_convex {
135 continue;
136 }
137 let contains_other = indices.iter().any(|&iq| {
142 iq != ip
143 && iq != ic
144 && iq != inext
145 && poly[iq] != poly[ip]
146 && poly[iq] != poly[ic]
147 && poly[iq] != poly[inext]
148 && point_in_triangle(poly[iq], poly[ip], poly[ic], poly[inext])
149 });
150 if contains_other {
151 continue;
152 }
153 triangles.push([ip, ic, inext]);
154 indices.remove(k);
155 clipped = true;
156 break;
157 }
158 if !clipped {
159 let m = indices.len();
160 triangles.push([indices[m - 1], indices[0], indices[1]]);
161 indices.remove(0);
162 }
163 }
164 triangles.push([indices[0], indices[1], indices[2]]);
165 triangles
166}
167
168#[cfg(test)]
169mod tests {
170 use super::*;
171
172 fn v(x: f64, y: f64) -> Vector2 {
173 Vector2::from_array([Scalar::from_f64(x), Scalar::from_f64(y)])
174 }
175
176 #[test]
179 fn convex_polygons_either_way_round() {
180 let square = [v(0.0, 0.0), v(2.0, 0.0), v(2.0, 1.0), v(0.0, 1.0)];
181 let mut reversed = square;
182 reversed.reverse();
183 for corners in [square, reversed] {
184 let convex = Convex::new(&corners).unwrap();
185 assert!(convex.could_contain(&v(1.0, 0.5)));
186 assert!(convex.could_contain(&v(2.0, 1.0)));
187 assert!(!convex.could_contain(&v(2.5, 0.5)));
188 assert!(!convex.could_contain(&v(1.0, -0.1)));
189 }
190 assert!(Convex::new(&[v(0.0, 0.0), v(1.0, 1.0), v(2.0, 2.0)]).is_none());
191 }
192
193 #[test]
194 fn ear_clip_triangulates_convex_polygon_fully() {
195 let poly = vec![[0.0, 0.0], [1.0, 0.0], [1.0, 1.0], [0.0, 1.0]];
196 let tris = ear_clip(&poly);
197 let total: f64 = tris
198 .iter()
199 .map(|t| cross2(sub(poly[t[1]], poly[t[0]]), sub(poly[t[2]], poly[t[0]])).abs() / 2.0)
200 .sum();
201 assert!((total - 1.0).abs() < 1e-9);
202 }
203}