rgeometry/algorithms/polygonization/
monotone.rs1use crate::data::{Cursor, Point, Polygon, Vector};
3use crate::{Error, Orientation, PolygonScalar, TotalOrd};
4
5use std::cmp::Ordering;
6use std::collections::VecDeque;
7use std::ops::Bound::*;
8
9pub fn is_monotone<T>(poly: &Polygon<T>, direction: &Vector<T, 2>) -> bool
11where
12 T: PolygonScalar,
13{
14 assert_eq!(poly.rings.len(), 1);
16
17 let cmp_cursors =
18 |a: &Cursor<'_, T>, b: &Cursor<'_, T>| direction.cmp_along(a, b).then_with(|| a.total_cmp(b));
19 let max_cursor = {
21 match poly.iter_boundary().max_by(cmp_cursors) {
22 Some(c) => c,
23 None => return false,
24 }
25 };
26 let min_cursor = {
27 match poly.iter_boundary().min_by(cmp_cursors) {
28 Some(c) => c,
29 None => return false,
30 }
31 };
32
33 for pt in min_cursor.to(Excluded(max_cursor)) {
36 if direction.cmp_along(&pt, &pt.next()) == Ordering::Greater {
37 return false;
38 }
39 }
40
41 for pt in max_cursor.to(Excluded(min_cursor)) {
44 if direction.cmp_along(&pt, &pt.next()) == Ordering::Less {
45 return false;
46 }
47 }
48
49 true
50}
51
52pub fn new_monotone_polygon<T>(
54 mut points: Vec<Point<T, 2>>,
55 direction: &Vector<T, 2>,
56) -> Result<Polygon<T>, Error>
57where
58 T: PolygonScalar,
59{
60 points.sort_by(|prev, curr| {
63 direction
64 .cmp_along(prev, curr)
65 .then_with(|| prev.total_cmp(curr))
66 });
67
68 points.dedup();
69 if points.len() < 3 {
70 return Err(Error::InsufficientVertices);
71 }
72
73 let (min_point, max_point) = (
74 points.first().unwrap().clone(),
75 points.last().unwrap().clone(),
76 );
77
78 let mut polygon_points: VecDeque<Point<T, 2>> = VecDeque::new();
79
80 while let Some(curr) = points.pop() {
81 match Orientation::new(&min_point, &max_point, &curr) {
82 Orientation::ClockWise => polygon_points.push_front(curr),
83 _ => polygon_points.push_back(curr),
84 }
85 }
86 let vec = Vec::from(polygon_points);
87
88 Polygon::new(vec)
89}
90
91#[cfg(test)]
93mod monotone_testing {
94 use super::*;
95 use crate::data::{Point, Polygon, PolygonConvex, Vector};
96 use crate::Orientation;
97 use proptest::prelude::*;
98 use std::collections::BTreeSet;
99 use test_strategy::proptest;
100
101 #[proptest]
102 fn convex_polygon_is_montone(convex_polygon: PolygonConvex<i8>, direction: Vector<i8, 2>) {
103 prop_assert!(is_monotone(convex_polygon.polygon(), &direction));
104 }
105
106 #[test]
107 fn non_y_monotone() {
109 let polygon = Polygon::new(vec![
110 Point::new([0, 1]),
111 Point::new([1, 2]),
112 Point::new([1, -2]),
113 Point::new([0, -1]),
114 Point::new([-1, -2]),
115 Point::new([-1, 2]),
116 ])
117 .unwrap();
118 assert!(!is_monotone(&polygon, &Vector::from(Point::new([0, 1]))));
119 }
120
121 #[test]
122 fn monotone_mountain() {
123 let polygon = Polygon::new(vec![
124 Point::new([0, 3]),
125 Point::new([1, 2]),
126 Point::new([1, -2]),
127 Point::new([0, -3]),
128 ])
129 .unwrap();
130 assert!(is_monotone(&polygon, &Vector::from(Point::new([0, 1]))));
131 }
132
133 #[proptest]
134 fn monotone_is_monotone_prop(points: Vec<Point<i8, 2>>, direction: Vector<i8, 2>) {
135 if let Ok(p) = new_monotone_polygon(points, &direction) {
136 prop_assert!(is_monotone(&p, &direction));
137 prop_assert_eq!(p.validate().err(), None);
138 }
139 }
140
141 #[proptest]
142 fn valid_monotone(points: Vec<Point<i8, 2>>, direction: Vector<i8, 2>) {
143 let mut points = points;
145 let mut set = BTreeSet::new();
146 points.retain(|pt| set.insert(*pt));
147 if !points
150 .windows(3)
151 .all(|window| Orientation::new(&window[0], &window[1], &window[2]).is_colinear())
152 {
153 let p = new_monotone_polygon(points, &direction).unwrap();
154 prop_assert!(is_monotone(&p, &direction));
155 prop_assert_eq!(p.validate().err(), None);
156 }
157 }
158}