physdes/
rpolygon.rs

1#![allow(clippy::type_complexity)]
2
3use num_traits::Num;
4use std::cmp::Ord;
5use std::cmp::Ordering;
6use std::ops::{AddAssign, SubAssign};
7
8use crate::point::Point;
9use crate::vector2::Vector2;
10
11/// The `RPolygon` struct represents a rectilinear polygon with an origin point and a vector of 2D
12/// vectors.
13///
14/// Properties:
15///
16/// * `origin`: The origin property represents the starting point or the reference point of the
17///             rectilinear polygon. It is of type `Point<T, T>`, where T is the type of the coordinates of the point
18///             (e.g., integer or floating-point).
19/// * `vecs`: vecs is a vector that stores the vectors representing the sides of the rectilinear
20///             polygon.
21#[derive(Eq, Clone, Debug, Default)]
22pub struct RPolygon<T> {
23    pub origin: Point<T, T>,
24    vecs: Vec<Vector2<T, T>>,
25}
26
27impl<T: Clone + Num + Copy + std::ops::AddAssign + Ord> RPolygon<T> {
28    /// The `new` function constructs a new `RPolygon` object by calculating the origin and vectors
29    /// based on the given coordinates.
30    ///
31    /// Arguments:
32    ///
33    /// * `coords`: The `coords` parameter is an array of `Point<T, T>` objects. It represents the
34    ///             coordinates of the points that define the polygon. The first element of the array (`coords[0]`)
35    ///             is considered as the origin of the polygon, and the remaining elements represent the vectors
36    ///             from the origin to the
37    ///
38    /// Returns:
39    ///
40    /// The `new` function is returning an instance of the `RPolygon` struct.
41    pub fn new(coords: &[Point<T, T>]) -> Self {
42        // let origin = coords[0];
43        // let mut vecs = vec![];
44        // for pt in coords.iter().skip(1) {
45        //     vecs.push(pt - origin);
46        // }
47        let (&origin, coords) = coords.split_first().unwrap();
48        let vecs = coords.iter().map(|pt| *pt - origin).collect();
49        RPolygon { origin, vecs }
50    }
51
52    /// Constructs a new Polygon from origin and displacement vectors
53    ///
54    /// # Arguments
55    ///
56    /// * `origin` - The origin point of the polygon
57    /// * `vecs` - Vector of displacement vectors from origin
58    pub fn from_origin_and_vectors(origin: Point<T, T>, vecs: Vec<Vector2<T, T>>) -> Self {
59        RPolygon { origin, vecs }
60    }
61
62    /// Constructs a new Polygon from a point set
63    ///
64    /// The first point in the set is used as the origin, and the remaining points
65    /// are used to construct displacement vectors relative to the origin.
66    pub fn from_pointset(pointset: &[Point<T, T>]) -> Self {
67        Self::new(pointset)
68    }
69
70    /// Translates the polygon by adding a vector to its origin
71    pub fn add_assign(&mut self, rhs: Vector2<T, T>)
72    where
73        T: AddAssign,
74    {
75        self.origin += rhs;
76    }
77
78    /// Translates the polygon by subtracting a vector from its origin
79    pub fn sub_assign(&mut self, rhs: Vector2<T, T>)
80    where
81        T: SubAssign,
82    {
83        self.origin -= rhs;
84    }
85
86    /// The `signed_area` function calculates the signed area of a polygon.
87    ///
88    /// Returns:
89    ///
90    /// The function `signed_area` returns a value of type `T`.
91    pub fn signed_area(&self) -> T {
92        // assert!(self.vecs.len() >= 1);
93        // let (mut vec0, vecs) = self.vecs.split_first().unwrap();
94        let mut itr = self.vecs.iter();
95        let mut vec0 = itr.next().unwrap();
96        let mut res = vec0.x_ * vec0.y_;
97        for vec1 in itr {
98            res += vec1.x_ * (vec1.y_ - vec0.y_);
99            vec0 = vec1;
100        }
101        res
102    }
103
104    /// Gets all vertices of the polygon as points
105    pub fn vertices(&self) -> Vec<Point<T, T>> {
106        let mut result = Vec::with_capacity(self.vecs.len() + 1);
107        result.push(self.origin);
108
109        for vec in &self.vecs {
110            result.push(self.origin + *vec);
111        }
112
113        result
114    }
115
116    /// Gets the bounding box of the polygon
117    pub fn bounding_box(&self) -> (Point<T, T>, Point<T, T>) {
118        let mut min_x = T::zero();
119        let mut min_y = T::zero();
120        let mut max_x = T::zero();
121        let mut max_y = T::zero();
122
123        for vec in &self.vecs {
124            if vec.x_ < min_x {
125                min_x = vec.x_;
126            }
127            if vec.y_ < min_y {
128                min_y = vec.y_;
129            }
130            if vec.x_ > max_x {
131                max_x = vec.x_;
132            }
133            if vec.y_ > max_y {
134                max_y = vec.y_;
135            }
136        }
137
138        (
139            Point::new(self.origin.xcoord + min_x, self.origin.ycoord + min_y),
140            Point::new(self.origin.xcoord + max_x, self.origin.ycoord + max_y),
141        )
142    }
143
144    /// Checks if the polygon is rectilinear
145    ///
146    /// A polygon is rectilinear if all its edges are either horizontal or vertical.
147    ///
148    /// # Returns
149    ///
150    /// `true` if the polygon is rectilinear, `false` otherwise
151    ///
152    /// # Examples
153    ///
154    /// ```
155    /// use physdes::point::Point;
156    /// use physdes::rpolygon::RPolygon;
157    ///
158    /// let p1 = Point::new(0, 0);
159    /// let p2 = Point::new(0, 1);
160    /// let p3 = Point::new(1, 1);
161    /// let p4 = Point::new(1, 0);
162    /// let poly = RPolygon::new(&[p1, p2, p3, p4]);
163    /// assert!(poly.is_rectilinear());
164    ///
165    /// let p5 = Point::new(0, 0);
166    /// let p6 = Point::new(1, 1);
167    /// let p7 = Point::new(0, 2);
168    /// let poly2 = RPolygon::new(&[p5, p6, p7]);
169    /// assert!(poly2.is_rectilinear());
170    /// ```
171    pub fn is_rectilinear(&self) -> bool {
172        true
173    }
174
175    /// Checks if the polygon is oriented anticlockwise
176    pub fn is_anticlockwise(&self) -> bool
177    where
178        T: PartialOrd,
179    {
180        let mut pointset = Vec::with_capacity(self.vecs.len() + 1);
181        pointset.push(Vector2::new(T::zero(), T::zero()));
182        pointset.extend(self.vecs.iter().cloned());
183
184        if pointset.len() < 2 {
185            panic!("Polygon must have at least 2 points");
186        }
187
188        // Find the point with minimum coordinates
189        let (min_index, _) = pointset
190            .iter()
191            .enumerate()
192            .min_by(|(_, a), (_, b)| {
193                a.x_.partial_cmp(&b.x_)
194                    .unwrap_or(Ordering::Equal)
195                    .then(a.y_.partial_cmp(&b.y_).unwrap_or(Ordering::Equal))
196            })
197            .unwrap();
198
199        // Get previous and next points with wrap-around
200        let n = pointset.len();
201        let prev_point = pointset[(min_index + n - 1) % n];
202        let current_point = pointset[min_index];
203
204        prev_point.y_ > current_point.y_
205    }
206}
207
208// Implement PartialEq for RPolygon
209impl<T: PartialEq> PartialEq for RPolygon<T> {
210    fn eq(&self, other: &Self) -> bool {
211        self.origin == other.origin && self.vecs == other.vecs
212    }
213}
214
215impl<T: Clone + Num + Ord + Copy> RPolygon<T> {
216    /// The `create_mono_rpolygon` function creates a monotone polygon from a given set of points based
217    /// on a provided comparison function.
218    ///
219    /// Arguments:
220    ///
221    /// * `pointset`: `pointset` is a slice of `Point<T, T>` elements. It represents a set of points in a
222    ///             two-dimensional space.
223    /// * `f`: The parameter `f` is a closure that takes a reference to a reference of a `Point<T, T>` and
224    ///             returns a tuple of two values of type `T`. The closure is used to determine the ordering of the
225    ///             points in the `pointset`. The first value of the tuple represents the x-coordinate
226    pub fn create_mono_rpolygon<F>(pointset: &[Point<T, T>], f: F) -> (Vec<Point<T, T>>, bool)
227    where
228        F: Fn(&Point<T, T>) -> (T, T),
229    {
230        // Use x-mono as model
231        let rightmost = pointset
232            .iter()
233            .max_by(|a, b| f(a).partial_cmp(&f(b)).unwrap())
234            .unwrap();
235        let leftmost = pointset
236            .iter()
237            .min_by(|a, b| f(a).partial_cmp(&f(b)).unwrap())
238            .unwrap();
239        let is_anticlockwise = f(rightmost).1 <= f(leftmost).1;
240        let (mut lst1, mut lst2): (Vec<Point<T, T>>, Vec<Point<T, T>>) = if is_anticlockwise {
241            pointset.iter().partition(|pt| (f(pt).1 <= f(leftmost).1))
242        } else {
243            pointset.iter().partition(|pt| (f(pt).1 >= f(leftmost).1))
244        };
245        lst1.sort_by_key(|a| f(a));
246        lst2.sort_by_key(|a| f(a));
247        lst2.reverse();
248        lst1.append(&mut lst2);
249        (lst1, is_anticlockwise) // is_clockwise if y-mono
250    }
251
252    /// The function `create_xmono_rpolygon` creates a monotone RPolygon object using a given point set,
253    /// with the x-coordinate as the primary sorting criterion.
254    ///
255    /// Arguments:
256    ///
257    /// * `pointset`: A slice of Point objects
258    #[inline]
259    pub fn create_xmono_rpolygon(pointset: &[Point<T, T>]) -> (Vec<Point<T, T>>, bool) {
260        Self::create_mono_rpolygon(pointset, |a| (a.xcoord, a.ycoord))
261    }
262
263    /// The function `create_ymono_rpolygon` creates a y-monotone RPolygon object from a given point
264    /// set.
265    ///
266    /// Arguments:
267    ///
268    /// * `pointset`: A slice of Point objects, where each Point object has two fields: ycoord and
269    ///               xcoord.
270    #[inline]
271    pub fn create_ymono_rpolygon(pointset: &[Point<T, T>]) -> (Vec<Point<T, T>>, bool) {
272        Self::create_mono_rpolygon(pointset, |a| (a.ycoord, a.xcoord))
273    }
274
275    /// The function `point_in_rpolygon` determines if a given point is within a polygon.
276    ///
277    /// The code below is from Wm. Randolph Franklin <wrf@ecse.rpi.edu>
278    /// (see URL below) with some minor modifications for integer. It returns
279    /// true for strictly interior points, false for strictly exterior, and ub
280    /// for points on the boundary.  The boundary behavior is complex but
281    /// determined; in particular, for a partition of a region into polygons,
282    /// each Point is "in" exactly one Polygon.
283    /// (See p.243 of [O'Rourke (C)] for a discussion of boundary behavior.)
284    ///
285    /// See <http://www.faqs.org/faqs/graphics/algorithms-faq/> Subject 2.03
286    ///
287    /// Arguments:
288    ///
289    /// * `pointset`: A slice of points representing the vertices of the polygon. Each point has x and y
290    ///             coordinates.
291    /// * `q`: The parameter `q` represents the point that we want to determine if it is within the
292    ///             polygon or not.
293    ///
294    /// Returns:
295    ///
296    /// The function `point_in_polygon` returns a boolean value. It returns `true` if the given point
297    /// `q` is strictly inside the polygon defined by the `pointset` array, `false` if the point is
298    /// strictly outside the polygon, and `ub` (undefined behavior) if the point lies on the boundary of
299    /// the polygon.
300    pub fn point_in_rpolygon(pointset: &[Point<T, T>], q: &Point<T, T>) -> bool {
301        let mut res = false;
302        let n = pointset.len();
303        let mut p0 = &pointset[n - 1];
304        for p1 in pointset.iter() {
305            if ((p1.ycoord <= q.ycoord && q.ycoord < p0.ycoord)
306                || (p0.ycoord <= q.ycoord && q.ycoord < p1.ycoord))
307                && p1.xcoord > q.xcoord
308            {
309                res = !res;
310            }
311            p0 = p1;
312        }
313        res
314    }
315}
316
317/// Checks if a polygon is monotone in a given direction
318pub fn rpolygon_is_monotone<T, F>(lst: &[Point<T, T>], dir: F) -> bool
319where
320    T: Clone + Num + Ord + Copy + PartialOrd,
321    F: Fn(&Point<T, T>) -> (T, T),
322{
323    if lst.len() <= 3 {
324        return true;
325    }
326
327    let (min_index, _) = lst
328        .iter()
329        .enumerate()
330        .min_by_key(|(_, pt)| dir(pt))
331        .unwrap();
332
333    let (max_index, _) = lst
334        .iter()
335        .enumerate()
336        .max_by_key(|(_, pt)| dir(pt))
337        .unwrap();
338
339    let n = lst.len();
340
341    // Chain from min to max
342    let mut i = min_index;
343    while i != max_index {
344        let next_i = (i + 1) % n;
345        if dir(&lst[i]).0 > dir(&lst[next_i]).0 {
346            return false;
347        }
348        i = next_i;
349    }
350
351    // Chain from max to min
352    let mut i = max_index;
353    while i != min_index {
354        let next_i = (i + 1) % n;
355        if dir(&lst[i]).0 < dir(&lst[next_i]).0 {
356            return false;
357        }
358        i = next_i;
359    }
360
361    true
362}
363
364/// Checks if a polygon is x-monotone
365pub fn rpolygon_is_xmonotone<T>(lst: &[Point<T, T>]) -> bool
366where
367    T: Clone + Num + Ord + Copy + PartialOrd,
368{
369    rpolygon_is_monotone(lst, |pt| (pt.xcoord, pt.ycoord))
370}
371
372/// Checks if a polygon is y-monotone
373pub fn rpolygon_is_ymonotone<T>(lst: &[Point<T, T>]) -> bool
374where
375    T: Clone + Num + Ord + Copy + PartialOrd,
376{
377    rpolygon_is_monotone(lst, |pt| (pt.ycoord, pt.xcoord))
378}
379
380/// Checks if a polygon is rectilinearly convex
381pub fn rpolygon_is_convex<T>(lst: &[Point<T, T>]) -> bool
382where
383    T: Clone + Num + Ord + Copy + PartialOrd,
384{
385    rpolygon_is_xmonotone(lst) && rpolygon_is_ymonotone(lst)
386}
387
388/// Determines if a polygon represented by points is oriented anticlockwise
389pub fn rpolygon_is_anticlockwise<T>(pointset: &[Point<T, T>]) -> bool
390where
391    T: Clone + Num + Ord + Copy + PartialOrd,
392{
393    if pointset.len() < 2 {
394        panic!("Polygon must have at least 2 points");
395    }
396
397    // Find the point with minimum coordinates
398    let (min_index, min_point) = pointset
399        .iter()
400        .enumerate()
401        .min_by(|(_, a), (_, b)| {
402            a.xcoord
403                .partial_cmp(&b.xcoord)
404                .unwrap_or(Ordering::Equal)
405                .then(a.ycoord.partial_cmp(&b.ycoord).unwrap_or(Ordering::Equal))
406        })
407        .unwrap();
408
409    // Get previous and next points with wrap-around
410    let n = pointset.len();
411    let prev_index = (min_index + n - 1) % n;
412
413    let prev_point = pointset[prev_index];
414    let current_point = *min_point;
415
416    prev_point.ycoord() > current_point.ycoord()
417}
418
419#[cfg(test)]
420mod test {
421    #![allow(non_upper_case_globals)]
422
423    use super::*;
424
425    #[test]
426    pub fn test_ymono_rpolygon() {
427        let coords = [
428            (-2, 2),
429            (0, -1),
430            (-5, 1),
431            (-2, 4),
432            (0, -4),
433            (-4, 3),
434            (-6, -2),
435            (5, 1),
436            (2, 2),
437            (3, -3),
438            (-3, -4),
439            (1, 4),
440        ];
441        let mut pointset = vec![];
442        for (x, y) in coords.iter() {
443            pointset.push(Point::<i32, i32>::new(*x, *y));
444        }
445        let (pointset, is_cw) = RPolygon::<i32>::create_ymono_rpolygon(&pointset);
446        assert!(rpolygon_is_anticlockwise(&pointset));
447        assert!(rpolygon_is_ymonotone(&pointset));
448        assert!(!rpolygon_is_xmonotone(&pointset));
449        for p in pointset.iter() {
450            print!("({}, {}) ", p.xcoord, p.ycoord);
451        }
452        let poly = RPolygon::<i32>::new(&pointset);
453        assert!(!is_cw);
454        assert_eq!(poly.signed_area(), 45);
455    }
456
457    #[test]
458    pub fn test_xmono_rpolygon() {
459        let coords = [
460            (-2, 2),
461            (0, -1),
462            (-5, 1),
463            (-2, 4),
464            (0, -4),
465            (-4, 3),
466            (-6, -2),
467            (5, 1),
468            (2, 2),
469            (3, -3),
470            (-3, -4),
471            (1, 4),
472        ];
473        let mut pointset = vec![];
474        for (x, y) in coords.iter() {
475            pointset.push(Point::<i32, i32>::new(*x, *y));
476        }
477        let (pointset, is_anticw) = RPolygon::<i32>::create_xmono_rpolygon(&pointset);
478        assert!(!rpolygon_is_anticlockwise(&pointset));
479        assert!(rpolygon_is_xmonotone(&pointset));
480        assert!(!rpolygon_is_ymonotone(&pointset));
481        for p in pointset.iter() {
482            print!("({}, {}) ", p.xcoord, p.ycoord);
483        }
484        let poly = RPolygon::<i32>::new(&pointset);
485        assert!(!is_anticw);
486        assert_eq!(poly.signed_area(), -53);
487        assert!(!poly.is_anticlockwise())
488    }
489
490    #[test]
491    pub fn test_point_in_rpolygon() {
492        let coords = [
493            (-2, 2),
494            (0, -1),
495            (-5, 1),
496            (-2, 4),
497            (0, -4),
498            (-4, 3),
499            (-6, -2),
500            (5, 1),
501            (2, 2),
502            (3, -3),
503            (-3, -4),
504            (1, 4),
505        ];
506        let mut pointset = vec![];
507        for (x, y) in coords.iter() {
508            pointset.push(Point::<i32, i32>::new(*x, *y));
509        }
510        let q = Point::<i32, i32>::new(0, -3);
511        // let poly = RPolygon::<i32>::new(&pointset);
512        assert!(!RPolygon::<i32>::point_in_rpolygon(&pointset, &q));
513    }
514
515    #[test]
516    fn test_signed_area_more_cases() {
517        let p1 = Point::new(0, 0);
518        let p2 = Point::new(1, 0);
519        let p3 = Point::new(1, 1);
520        let p4 = Point::new(0, 1);
521        let poly = RPolygon::new(&[p1, p2, p3, p4]);
522        assert_eq!(poly.signed_area(), 1);
523
524        let p5 = Point::new(0, 0);
525        let p6 = Point::new(0, 1);
526        let p7 = Point::new(1, 1);
527        let p8 = Point::new(1, 0);
528        let poly2 = RPolygon::new(&[p5, p6, p7, p8]);
529        assert_eq!(poly2.signed_area(), -1);
530    }
531
532    #[test]
533    fn test_point_in_rpolygon_more_cases() {
534        let p1 = Point::new(0, 0);
535        let p2 = Point::new(1, 0);
536        let p3 = Point::new(1, 1);
537        let p4 = Point::new(0, 1);
538        let pointset = &[p1, p2, p3, p4];
539
540        let q1 = Point::new(0, 0);
541        assert!(RPolygon::<i32>::point_in_rpolygon(pointset, &q1));
542
543        let q2 = Point::new(1, 1);
544        assert!(!RPolygon::<i32>::point_in_rpolygon(pointset, &q2));
545
546        let q3 = Point::new(0, 1);
547        assert!(!RPolygon::<i32>::point_in_rpolygon(pointset, &q3));
548
549        let q4 = Point::new(1, 0);
550        assert!(!RPolygon::<i32>::point_in_rpolygon(pointset, &q4));
551    }
552}