physdes/
rpolygon.rs

1#![allow(clippy::type_complexity)]
2
3use super::{Point, Vector2};
4use num_traits::Num;
5// use core::ops::{Add, Neg, Sub};
6
7/// The `RPolygon` struct represents a rectilinear polygon with an origin point and a vector of 2D
8/// vectors.
9///
10/// Properties:
11///
12/// * `origin`: The origin property represents the starting point or the reference point of the
13///             rectilinear polygon. It is of type `Point<T, T>`, where T is the type of the coordinates of the point
14///             (e.g., integer or floating-point).
15/// * `vecs`: vecs is a vector that stores the vectors representing the sides of the rectilinear
16///             polygon.
17pub struct RPolygon<T> {
18    pub origin: Point<T, T>,
19    vecs: Vec<Vector2<T, T>>,
20}
21
22impl<T: Clone + Num + Copy + std::ops::AddAssign> RPolygon<T> {
23    /// The `new` function constructs a new `RPolygon` object by calculating the origin and vectors
24    /// based on the given coordinates.
25    ///
26    /// Arguments:
27    ///
28    /// * `coords`: The `coords` parameter is an array of `Point<T, T>` objects. It represents the
29    ///             coordinates of the points that define the polygon. The first element of the array (`coords[0]`)
30    ///             is considered as the origin of the polygon, and the remaining elements represent the vectors
31    ///             from the origin to the
32    ///
33    /// Returns:
34    ///
35    /// The `new` function is returning an instance of the `RPolygon` struct.
36    pub fn new(coords: &[Point<T, T>]) -> Self {
37        // let origin = coords[0];
38        // let mut vecs = vec![];
39        // for pt in coords.iter().skip(1) {
40        //     vecs.push(pt - origin);
41        // }
42        let (&origin, coords) = coords.split_first().unwrap();
43        let vecs = coords.iter().map(|pt| pt - origin).collect();
44        RPolygon { origin, vecs }
45    }
46
47    /// The `signed_area` function calculates the signed area of a polygon.
48    ///
49    /// Returns:
50    ///
51    /// The function `signed_area` returns a value of type `T`.
52    pub fn signed_area(&self) -> T {
53        // assert!(self.vecs.len() >= 1);
54        // let (mut vec0, vecs) = self.vecs.split_first().unwrap();
55        let mut itr = self.vecs.iter();
56        let mut vec0 = itr.next().unwrap();
57        let mut res = vec0.x_ * vec0.y_;
58        for vec1 in itr {
59            res += vec1.x_ * (vec1.y_ - vec0.y_);
60            vec0 = vec1;
61        }
62        res
63    }
64
65    /**
66     * @brief
67     *
68     * @return `Point<T, T>`
69     */
70    pub fn lb(&self) -> Point<T, T> {
71        unimplemented!()
72    }
73
74    /**
75     * @brief
76     *
77     * @return `Point<T, T>`
78     */
79    pub fn ub(&self) -> Point<T, T> {
80        unimplemented!()
81    }
82}
83
84impl<T: Clone + Num + Ord + Copy> RPolygon<T> {
85    /// The `create_mono_rpolygon` function creates a monotone polygon from a given set of points based
86    /// on a provided comparison function.
87    ///
88    /// Arguments:
89    ///
90    /// * `pointset`: `pointset` is a slice of `Point<T, T>` elements. It represents a set of points in a
91    ///             two-dimensional space.
92    /// * `f`: The parameter `f` is a closure that takes a reference to a reference of a `Point<T, T>` and
93    ///             returns a tuple of two values of type `T`. The closure is used to determine the ordering of the
94    ///             points in the `pointset`. The first value of the tuple represents the x-coordinate
95    pub fn create_mono_rpolygon<F>(pointset: &[Point<T, T>], f: F) -> (Vec<Point<T, T>>, bool)
96    where
97        F: Fn(&&Point<T, T>) -> (T, T),
98    {
99        // Use x-mono as model
100        let rightmost = pointset.iter().max_by_key(&f).unwrap();
101        let leftmost = pointset.iter().min_by_key(&f).unwrap();
102        let is_anticlockwise = f(&rightmost).1 <= f(&leftmost).1;
103        let (mut lst1, mut lst2): (Vec<Point<T, T>>, Vec<Point<T, T>>) = if is_anticlockwise {
104            pointset.iter().partition(|pt| (f(pt).1 <= f(&leftmost).1))
105        } else {
106            pointset.iter().partition(|pt| (f(pt).1 >= f(&leftmost).1))
107        };
108        lst1.sort_by_key(|a| f(&a));
109        lst2.sort_by_key(|a| f(&a));
110        lst2.reverse();
111        lst1.append(&mut lst2);
112        (lst1, is_anticlockwise) // is_clockwise if y-mono
113    }
114
115    /// The function `create_xmono_rpolygon` creates a monotone RPolygon object using a given point set,
116    /// with the x-coordinate as the primary sorting criterion.
117    ///
118    /// Arguments:
119    ///
120    /// * `pointset`: A slice of Point objects
121    #[inline]
122    pub fn create_xmono_rpolygon(pointset: &[Point<T, T>]) -> (Vec<Point<T, T>>, bool) {
123        Self::create_mono_rpolygon(pointset, |a| (a.xcoord, a.ycoord))
124    }
125
126    /// The function `create_ymono_rpolygon` creates a y-monotone RPolygon object from a given point
127    /// set.
128    ///
129    /// Arguments:
130    ///
131    /// * `pointset`: A slice of Point objects, where each Point object has two fields: ycoord and
132    ///               xcoord.
133    #[inline]
134    pub fn create_ymono_rpolygon(pointset: &[Point<T, T>]) -> (Vec<Point<T, T>>, bool) {
135        Self::create_mono_rpolygon(pointset, |a| (a.ycoord, a.xcoord))
136    }
137
138    /// The function `point_in_rpolygon` determines if a given point is within a polygon.
139    ///
140    /// The code below is from Wm. Randolph Franklin <wrf@ecse.rpi.edu>
141    /// (see URL below) with some minor modifications for integer. It returns
142    /// true for strictly interior points, false for strictly exterior, and ub
143    /// for points on the boundary.  The boundary behavior is complex but
144    /// determined; in particular, for a partition of a region into polygons,
145    /// each Point is "in" exactly one Polygon.
146    /// (See p.243 of [O'Rourke (C)] for a discussion of boundary behavior.)
147    ///
148    /// See <http://www.faqs.org/faqs/graphics/algorithms-faq/> Subject 2.03
149    ///
150    /// Arguments:
151    ///
152    /// * `pointset`: A slice of points representing the vertices of the polygon. Each point has x and y
153    ///             coordinates.
154    /// * `q`: The parameter `q` represents the point that we want to determine if it is within the
155    ///             polygon or not.
156    ///
157    /// Returns:
158    ///
159    /// The function `point_in_polygon` returns a boolean value. It returns `true` if the given point
160    /// `q` is strictly inside the polygon defined by the `pointset` array, `false` if the point is
161    /// strictly outside the polygon, and `ub` (undefined behavior) if the point lies on the boundary of
162    /// the polygon.
163    pub fn point_in_rpolygon(pointset: &[Point<T, T>], q: &Point<T, T>) -> bool {
164        let mut res = false;
165        let n = pointset.len();
166        let mut p0 = &pointset[n - 1];
167        for p1 in pointset.iter() {
168            if ((p1.ycoord <= q.ycoord && q.ycoord < p0.ycoord)
169                || (p0.ycoord <= q.ycoord && q.ycoord < p1.ycoord))
170                && p1.xcoord > q.xcoord
171            {
172                res = !res;
173            }
174            p0 = p1;
175        }
176        res
177    }
178}
179
180#[cfg(test)]
181mod test {
182    #![allow(non_upper_case_globals)]
183
184    use super::*;
185
186    #[test]
187    pub fn test_ymono_rpolygon() {
188        let coords = [
189            (-2, 2),
190            (0, -1),
191            (-5, 1),
192            (-2, 4),
193            (0, -4),
194            (-4, 3),
195            (-6, -2),
196            (5, 1),
197            (2, 2),
198            (3, -3),
199            (-3, -4),
200            (1, 4),
201        ];
202        let mut pointset = vec![];
203        for (x, y) in coords.iter() {
204            pointset.push(Point::<i32, i32>::new(*x, *y));
205        }
206        let (pointset, is_cw) = RPolygon::<i32>::create_ymono_rpolygon(&pointset);
207        for p in pointset.iter() {
208            print!("({}, {}) ", p.xcoord, p.ycoord);
209        }
210        let poly = RPolygon::<i32>::new(&pointset);
211        assert!(!is_cw);
212        assert_eq!(poly.signed_area(), 45);
213    }
214
215    #[test]
216    pub fn test_xmono_rpolygon() {
217        let coords = [
218            (-2, 2),
219            (0, -1),
220            (-5, 1),
221            (-2, 4),
222            (0, -4),
223            (-4, 3),
224            (-6, -2),
225            (5, 1),
226            (2, 2),
227            (3, -3),
228            (-3, -4),
229            (1, 4),
230        ];
231        let mut pointset = vec![];
232        for (x, y) in coords.iter() {
233            pointset.push(Point::<i32, i32>::new(*x, *y));
234        }
235        let (pointset, is_anticw) = RPolygon::<i32>::create_xmono_rpolygon(&pointset);
236        for p in pointset.iter() {
237            print!("({}, {}) ", p.xcoord, p.ycoord);
238        }
239        let poly = RPolygon::<i32>::new(&pointset);
240        assert!(!is_anticw);
241        assert_eq!(poly.signed_area(), -53);
242    }
243
244    #[test]
245    pub fn test_point_in_rpolygon() {
246        let coords = [
247            (-2, 2),
248            (0, -1),
249            (-5, 1),
250            (-2, 4),
251            (0, -4),
252            (-4, 3),
253            (-6, -2),
254            (5, 1),
255            (2, 2),
256            (3, -3),
257            (-3, -4),
258            (1, 4),
259        ];
260        let mut pointset = vec![];
261        for (x, y) in coords.iter() {
262            pointset.push(Point::<i32, i32>::new(*x, *y));
263        }
264        let q = Point::<i32, i32>::new(0, -3);
265        // let poly = RPolygon::<i32>::new(&pointset);
266        assert!(!RPolygon::<i32>::point_in_rpolygon(&pointset, &q));
267    }
268}