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 {
43 let pts: Vec<&P> = ring.points().collect();
44 let len = pts.len();
45 if len < 3 {
46 return true;
47 }
48
49 let zero = P::Scalar::ZERO;
50 let mut sign: Option<bool> = None; for index in 0..len {
52 let prev = pts[index];
53 let curr = pts[(index + 1) % len];
54 let next = pts[(index + 2) % len];
55 let edge_in_x = curr.get::<0>() - prev.get::<0>();
56 let edge_in_y = curr.get::<1>() - prev.get::<1>();
57 let edge_out_x = next.get::<0>() - curr.get::<0>();
58 let edge_out_y = next.get::<1>() - curr.get::<1>();
59 let cross = edge_in_x * edge_out_y - edge_in_y * edge_out_x;
60 if cross == zero {
61 continue;
62 }
63 let positive = cross > zero;
64 match sign {
65 None => sign = Some(positive),
66 Some(previous) if previous != positive => return false,
67 _ => {}
68 }
69 }
70 true
71}
72
73impl<P: PointTrait, const CW: bool, const CL: bool> IsConvex for Ring<P, CW, CL> {
74 fn is_convex(&self) -> bool {
75 ring_is_convex(self)
76 }
77}
78
79impl<P: PointTrait, const CW: bool, const CL: bool> IsConvex for Polygon<P, CW, CL> {
80 fn is_convex(&self) -> bool {
81 self.interiors().count() == 0 && ring_is_convex(self.exterior())
82 }
83}
84
85impl<Pg: IsConvex + geometry_trait::Polygon> IsConvex for MultiPolygon<Pg> {
86 fn is_convex(&self) -> bool {
87 self.0.iter().all(IsConvex::is_convex)
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use super::is_convex;
99 use geometry_cs::Cartesian;
100 use geometry_model::{MultiPolygon, Point2D, Polygon, Ring, polygon};
101
102 type Pt = Point2D<f64, Cartesian>;
103
104 #[test]
105 fn triangle_is_convex() {
106 let pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (2., 3.), (0., 0.)]];
107 assert!(is_convex(&pg));
108 }
109
110 #[test]
111 fn square_is_convex() {
112 let pg: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (4., 4.), (0., 4.), (0., 0.)]];
113 assert!(is_convex(&pg));
114 }
115
116 #[test]
117 fn reflex_polygon_is_not_convex() {
118 let pg: Polygon<Pt> =
119 polygon![[(0., 0.), (4., 0.), (2., 1.), (4., 4.), (0., 4.), (0., 0.)]];
120 assert!(!is_convex(&pg));
121 }
122
123 #[test]
124 fn polygon_with_hole_is_not_convex() {
125 let pg: Polygon<Pt> = polygon![
126 [(0., 0.), (4., 0.), (4., 4.), (0., 4.), (0., 0.)],
127 [(1., 1.), (2., 1.), (2., 2.), (1., 2.), (1., 1.)],
128 ];
129 assert!(!is_convex(&pg));
130 }
131
132 #[test]
135 fn two_point_ring_is_trivially_convex() {
136 let r: Ring<Pt> = Ring::from_vec(alloc::vec![Pt::new(0., 0.), Pt::new(1., 0.)]);
137 assert!(is_convex(&r));
138 }
139
140 #[test]
144 fn collinear_ring_is_convex() {
145 let r: Ring<Pt> = Ring::from_vec(alloc::vec![
146 Pt::new(0., 0.),
147 Pt::new(1., 1.),
148 Pt::new(2., 2.)
149 ]);
150 assert!(is_convex(&r));
151 }
152
153 #[test]
156 fn convex_ring_direct() {
157 let r: Ring<Pt> = Ring::from_vec(alloc::vec![
158 Pt::new(0., 0.),
159 Pt::new(4., 0.),
160 Pt::new(4., 4.),
161 Pt::new(0., 4.),
162 ]);
163 assert!(is_convex(&r));
164 }
165
166 #[test]
168 fn multi_polygon_all_members_must_be_convex() {
169 let convex: Polygon<Pt> = polygon![[(0., 0.), (4., 0.), (2., 3.), (0., 0.)]];
170 let reflex: Polygon<Pt> =
171 polygon![[(0., 0.), (4., 0.), (2., 1.), (4., 4.), (0., 4.), (0., 0.)]];
172 let all_convex = MultiPolygon(alloc::vec![convex.clone(), convex.clone()]);
173 assert!(is_convex(&all_convex));
174 let mixed = MultiPolygon(alloc::vec![convex, reflex]);
175 assert!(!is_convex(&mixed));
176 }
177}