1use alloc::vec::Vec;
17
18use geometry_coords::CoordinateScalar;
19use geometry_model::{MultiPolygon, Polygon, Ring};
20use geometry_trait::{Point as PointTrait, Polygon as _, Ring as RingTrait};
21
22#[must_use]
27pub fn is_convex<G: IsConvex>(g: &G) -> bool {
28 g.is_convex()
29}
30
31#[doc(hidden)]
34pub trait IsConvex {
35 fn is_convex(&self) -> bool;
37}
38
39fn ring_is_convex<P: PointTrait, const CW: bool, const CL: bool>(ring: &Ring<P, CW, CL>) -> bool {
45 let mut pts: Vec<&P> = ring.points().collect();
46 if pts.len() >= 2 {
47 let (first, last) = (pts[0], pts[pts.len() - 1]);
48 if first.get::<0>() == last.get::<0>() && first.get::<1>() == last.get::<1>() {
49 pts.pop();
50 }
51 }
52 let len = pts.len();
53 if len < 3 {
54 return true;
55 }
56
57 let zero = P::Scalar::ZERO;
58 let mut sign: Option<bool> = None; for index in 0..len {
60 let prev = pts[index];
61 let curr = pts[(index + 1) % len];
62 let next = pts[(index + 2) % len];
63 let edge_in_x = curr.get::<0>() - prev.get::<0>();
64 let edge_in_y = curr.get::<1>() - prev.get::<1>();
65 let edge_out_x = next.get::<0>() - curr.get::<0>();
66 let edge_out_y = next.get::<1>() - curr.get::<1>();
67 let cross = edge_in_x * edge_out_y - edge_in_y * edge_out_x;
68 if cross == zero {
69 continue;
70 }
71 let positive = cross > zero;
72 match sign {
73 None => sign = Some(positive),
74 Some(previous) if previous != positive => return false,
75 _ => {}
76 }
77 }
78 true
79}
80
81impl<P: PointTrait, const CW: bool, const CL: bool> IsConvex for Ring<P, CW, CL> {
82 fn is_convex(&self) -> bool {
83 ring_is_convex(self)
84 }
85}
86
87impl<P: PointTrait, const CW: bool, const CL: bool> IsConvex for Polygon<P, CW, CL> {
88 fn is_convex(&self) -> bool {
89 self.interiors().count() == 0 && ring_is_convex(self.exterior())
90 }
91}
92
93impl<Pg: IsConvex + geometry_trait::Polygon> IsConvex for MultiPolygon<Pg> {
94 fn is_convex(&self) -> bool {
95 self.0.iter().all(IsConvex::is_convex)
96 }
97}
98
99#[cfg(test)]
100mod tests {
101 use super::is_convex;
107 use geometry_cs::Cartesian;
108 use geometry_model::{MultiPolygon, Point2D, Polygon, Ring, polygon};
109
110 type Pt = Point2D<f64, Cartesian>;
111
112 #[test]
113 fn triangle_is_convex() {
114 let pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (2., 3.), (0., 0.)]];
115 assert!(is_convex(&pg));
116 }
117
118 #[test]
119 fn square_is_convex() {
120 let pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (4., 4.), (0., 4.), (0., 0.)]];
121 assert!(is_convex(&pg));
122 }
123
124 #[test]
125 fn reflex_polygon_is_not_convex() {
126 let pg: Polygon<Pt> =
127 polygon![[(0., 0.), (4., 0.), (2., 1.), (4., 4.), (0., 4.), (0., 0.)]];
128 assert!(!is_convex(&pg));
129 }
130
131 #[test]
132 fn polygon_with_hole_is_not_convex() {
133 let pg: Polygon<Pt> = polygon![
134 [(0., 0.), (4., 0.), (4., 4.), (0., 4.), (0., 0.)],
135 [(1., 1.), (2., 1.), (2., 2.), (1., 2.), (1., 1.)],
136 ];
137 assert!(!is_convex(&pg));
138 }
139
140 #[test]
143 fn two_point_ring_is_trivially_convex() {
144 let r: Ring<Pt> = Ring::from_vec(alloc::vec![Pt::new(0., 0.), Pt::new(1., 0.)]);
145 assert!(is_convex(&r));
146 }
147
148 #[test]
152 fn collinear_ring_is_convex() {
153 let r: Ring<Pt> = Ring::from_vec(alloc::vec![
154 Pt::new(0., 0.),
155 Pt::new(1., 1.),
156 Pt::new(2., 2.)
157 ]);
158 assert!(is_convex(&r));
159 }
160
161 #[test]
164 fn convex_ring_direct() {
165 let r: Ring<Pt> = Ring::from_vec(alloc::vec![
166 Pt::new(0., 0.),
167 Pt::new(4., 0.),
168 Pt::new(4., 4.),
169 Pt::new(0., 4.),
170 ]);
171 assert!(is_convex(&r));
172 }
173
174 #[test]
176 fn multi_polygon_all_members_must_be_convex() {
177 let convex: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (2., 3.), (0., 0.)]];
178 let reflex: Polygon<Pt> =
179 polygon![[(0., 0.), (4., 0.), (2., 1.), (4., 4.), (0., 4.), (0., 0.)]];
180 let all_convex = MultiPolygon(alloc::vec![convex.clone(), convex.clone()]);
181 assert!(is_convex(&all_convex));
182 let mixed = MultiPolygon(alloc::vec![convex, reflex]);
183 assert!(!is_convex(&mixed));
184 }
185
186 #[test]
189 fn reflex_vertex_at_closed_ring_seam_is_not_convex() {
190 let pg: Polygon<Pt> =
191 polygon![[(2., 1.), (4., 4.), (0., 4.), (0., 0.), (4., 0.), (2., 1.)]];
192 assert!(!is_convex(&pg));
193 let r: Ring<Pt> = Ring::from_vec(alloc::vec![
194 Pt::new(2., 1.),
195 Pt::new(4., 4.),
196 Pt::new(0., 4.),
197 Pt::new(0., 0.),
198 Pt::new(4., 0.),
199 Pt::new(2., 1.),
200 ]);
201 assert!(!is_convex(&r));
202 }
203
204 #[test]
210 fn rings_below_two_vertices_are_trivially_convex() {
211 let empty: Ring<Pt> = Ring::from_vec(alloc::vec![]);
212 assert!(is_convex(&empty));
213
214 let single: Ring<Pt> = Ring::from_vec(alloc::vec![Pt::new(3., 7.)]);
215 assert!(is_convex(&single));
216 }
217}