Skip to main content

physdes/
polygon.rs

1#![allow(clippy::type_complexity)]
2
3use num_traits::Num;
4use std::cmp::Ordering;
5use std::ops::{AddAssign, SubAssign};
6
7use crate::point::Point;
8use crate::vector2::Vector2;
9
10/// Represents an arbitrary polygon with coordinates of type T
11///
12/// The `Polygon` struct stores the origin point and a vector of edges that define the polygon.
13/// It provides various operations and functionalities for working with polygons, such as
14/// area calculation, point containment checks, and geometric property verification.
15///
16/// ```svgbob
17///       *-----*-----*
18///      /     / \   \
19///     /     /   \   \
20///    *-----*     *---*
21///    |  origin
22///    *--> vecs[0]
23/// ```
24///
25/// Properties:
26///
27/// * `origin`: The origin point of the polygon
28/// * `vecs`: Vector of displacement vectors from origin to other vertices
29///
30/// # Examples
31///
32/// ```
33/// use physdes::point::Point;
34/// use physdes::polygon::Polygon;
35/// use physdes::vector2::Vector2;
36///
37/// let origin = Point::new(0, 0);
38/// let vecs = vec![Vector2::new(1, 0), Vector2::new(1, 1), Vector2::new(0, 1)];
39/// let poly = Polygon::from_origin_and_vectors(origin, vecs);
40/// assert_eq!(poly.origin, Point::new(0, 0));
41/// assert_eq!(poly.vecs.len(), 3);
42/// ```
43#[derive(Eq, Clone, Debug, Default)]
44#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
45pub struct Polygon<T> {
46    pub origin: Point<T, T>,
47    pub vecs: Vec<Vector2<T, T>>,
48}
49
50impl<T: Clone + Num + Ord + Copy + std::ops::AddAssign> Polygon<T> {
51    /// Constructs a new Polygon from a set of points
52    ///
53    /// The first point in the slice is used as the origin, and the remaining points
54    /// are used to construct displacement vectors relative to the origin.
55    ///
56    /// # Arguments
57    ///
58    /// * `coords` - A slice of points representing the vertices of the polygon
59    ///
60    /// # Examples
61    ///
62    /// ```
63    /// use physdes::point::Point;
64    /// use physdes::polygon::Polygon;
65    /// use physdes::vector2::Vector2;
66    ///
67    /// let p1 = Point::new(1, 1);
68    /// let p2 = Point::new(2, 2);
69    /// let p3 = Point::new(3, 3);
70    /// let p4 = Point::new(4, 4);
71    /// let p5 = Point::new(5, 5);
72    /// let poly = Polygon::new(&[p1, p2, p3, p4, p5]);
73    /// assert_eq!(poly.origin, Point::new(1, 1));
74    /// assert_eq!(poly.vecs.len(), 4);
75    /// assert_eq!(poly.vecs[0], Vector2::new(1, 1));
76    /// ```
77    pub fn new(coords: &[Point<T, T>]) -> Self {
78        let (&origin, coords) = coords.split_first().unwrap();
79        let vecs = coords.iter().map(|pt| *pt - origin).collect();
80        Polygon { origin, vecs }
81    }
82
83    /// Constructs a new Polygon from origin and displacement vectors
84    ///
85    /// # Arguments
86    ///
87    /// * `origin` - The origin point of the polygon
88    /// * `vecs` - Vector of displacement vectors from origin
89    pub fn from_origin_and_vectors(origin: Point<T, T>, vecs: Vec<Vector2<T, T>>) -> Self {
90        Polygon { origin, vecs }
91    }
92
93    /// Constructs a new Polygon from a point set
94    ///
95    /// The first point in the set is used as the origin, and the remaining points
96    /// are used to construct displacement vectors relative to the origin.
97    pub fn from_pointset(pointset: &[Point<T, T>]) -> Self {
98        Self::new(pointset)
99    }
100
101    /// Calculates the area of the polygon
102    ///
103    /// Uses the shoelace formula (signed area):
104    ///
105    /// $$A = \frac{1}{2} \sum_{i=0}^{n-1} (x_i y_{i+1} - x_{i+1} y_i)$$
106    ///
107    /// # Returns
108    ///
109    /// The area of the polygon as a value of type T
110    ///
111    /// # Examples
112    ///
113    /// ```
114    /// use physdes::point::Point;
115    /// use physdes::polygon::Polygon;
116    ///
117    /// // Create a unit square
118    /// let points = vec![
119    ///     Point::new(0, 0),
120    ///     Point::new(1, 0),
121    ///     Point::new(1, 1),
122    ///     Point::new(0, 1),
123    /// ];
124    /// let poly = Polygon::new(&points);
125    /// let area = poly.area();
126    /// // Note: area returns signed area (may be negative depending on vertex order)
127    /// ```
128    pub fn area(&self) -> T
129    where
130        T: std::ops::Sub<Output = T> + std::ops::AddAssign + std::ops::Mul<Output = T> + Copy,
131    {
132        let n = self.vecs.len();
133        if n < 2 {
134            return T::zero();
135        }
136
137        let vec0 = self.vecs[0];
138        let vec1 = self.vecs[1];
139        let itr = self.vecs.iter().skip(2);
140
141        let mut res = vec0.x_ * vec1.y_ - self.vecs[n - 1].x_ * self.vecs[n - 2].y_;
142
143        let mut vec0 = vec0;
144        let mut vec1 = vec1;
145
146        for vec2 in itr {
147            res += vec1.x_ * (vec2.y_ - vec0.y_);
148            vec0 = vec1;
149            vec1 = *vec2;
150        }
151
152        res
153    }
154
155    /// Gets all vertices of the polygon as points
156    ///
157    /// # Returns
158    ///
159    /// A vector of all polygon vertices in order
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use physdes::point::Point;
165    /// use physdes::polygon::Polygon;
166    ///
167    /// let points = vec![
168    ///     Point::new(0, 0),
169    ///     Point::new(1, 0),
170    ///     Point::new(1, 1),
171    ///     Point::new(0, 1),
172    /// ];
173    /// let poly = Polygon::new(&points);
174    /// let vertices = poly.vertices();
175    /// assert_eq!(vertices.len(), 4);
176    /// assert_eq!(vertices[0], Point::new(0, 0));
177    /// ```
178    pub fn vertices(&self) -> Vec<Point<T, T>> {
179        let mut result = Vec::with_capacity(self.vecs.len() + 1);
180        result.push(self.origin);
181
182        for vec in &self.vecs {
183            result.push(self.origin + *vec);
184        }
185
186        result
187    }
188
189    /// Translates the polygon by adding a vector to its origin.
190    pub fn add_assign(&mut self, rhs: Vector2<T, T>)
191    where
192        T: AddAssign,
193    {
194        self.origin += rhs;
195    }
196
197    /// Translates the polygon by subtracting a vector from its origin.
198    pub fn sub_assign(&mut self, rhs: Vector2<T, T>)
199    where
200        T: SubAssign,
201    {
202        self.origin -= rhs;
203    }
204
205    /// Calculates the signed area of the polygon multiplied by 2
206    ///
207    /// Computes twice the signed area via the shoelace formula:
208    ///
209    /// $$2A = \sum_{i=0}^{n-1} (x_i y_{i+1} - x_{i+1} y_i)$$
210    ///
211    /// This avoids the need for floating-point arithmetic by omitting the $\frac{1}{2}$ factor.
212    ///
213    /// # Returns
214    ///
215    /// The signed area of the polygon multiplied by 2
216    ///
217    /// # Examples
218    ///
219    /// ```
220    /// use physdes::point::Point;
221    /// use physdes::polygon::Polygon;
222    /// use physdes::vector2::Vector2;
223    ///
224    /// let p1 = Point::new(1, 1);
225    /// let p2 = Point::new(2, 2);
226    /// let p3 = Point::new(3, 3);
227    /// let p4 = Point::new(4, 4);
228    /// let p5 = Point::new(5, 5);
229    /// let poly = Polygon::new(&[p1, p2, p3, p4, p5]);
230    /// assert_eq!(poly.signed_area_x2(), 0);
231    /// ```
232    pub fn signed_area_x2(&self) -> T {
233        let vecs = &self.vecs;
234        let n = vecs.len();
235        if n < 2 {
236            return T::zero();
237        }
238
239        let mut itr = vecs.iter();
240        let vec0 = itr.next().unwrap();
241        let vec1 = itr.next().unwrap();
242        let mut res = vec0.x_ * vec1.y_ - vecs[n - 1].x_ * vecs[n - 2].y_;
243
244        let mut vec0 = vec0;
245        let mut vec1 = vec1;
246
247        for vec2 in itr {
248            res += vec1.x_ * (vec2.y_ - vec0.y_);
249            vec0 = vec1;
250            vec1 = vec2;
251        }
252
253        res
254    }
255
256    /// Returns all vertices of the polygon as points.
257    pub fn get_vertices(&self) -> Vec<Point<T, T>> {
258        let mut result = Vec::with_capacity(self.vecs.len() + 1);
259        result.push(self.origin);
260
261        for vec in &self.vecs {
262            result.push(self.origin + *vec);
263        }
264
265        result
266    }
267
268    /// Gets the bounding box of the polygon
269    ///
270    /// # Returns
271    ///
272    /// A tuple of (min_point, max_point) representing the bounding box
273    ///
274    /// # Examples
275    ///
276    /// ```
277    /// use physdes::point::Point;
278    /// use physdes::polygon::Polygon;
279    ///
280    /// let points = vec![
281    ///     Point::new(0, 0),
282    ///     Point::new(2, 0),
283    ///     Point::new(2, 2),
284    ///     Point::new(0, 2),
285    /// ];
286    /// let poly = Polygon::new(&points);
287    /// let (min_pt, max_pt) = poly.bounding_box();
288    /// assert_eq!(min_pt, Point::new(0, 0));
289    /// assert_eq!(max_pt, Point::new(2, 2));
290    /// ```
291    pub fn bounding_box(&self) -> (Point<T, T>, Point<T, T>)
292    where
293        T: Ord + Copy,
294    {
295        let vertices = self.vertices();
296        let mut min_x = vertices[0].xcoord;
297        let mut min_y = vertices[0].ycoord;
298        let mut max_x = vertices[0].xcoord;
299        let mut max_y = vertices[0].ycoord;
300
301        for pt in &vertices {
302            if pt.xcoord < min_x {
303                min_x = pt.xcoord;
304            }
305            if pt.ycoord < min_y {
306                min_y = pt.ycoord;
307            }
308            if pt.xcoord > max_x {
309                max_x = pt.xcoord;
310            }
311            if pt.ycoord > max_y {
312                max_y = pt.ycoord;
313            }
314        }
315
316        (Point::new(min_x, min_y), Point::new(max_x, max_y))
317    }
318
319    /// Checks if the polygon is rectilinear
320    ///
321    /// A polygon is rectilinear if all its edges are either horizontal or vertical.
322    ///
323    /// # Returns
324    ///
325    /// `true` if the polygon is rectilinear, `false` otherwise
326    ///
327    /// # Examples
328    ///
329    /// ```
330    /// use physdes::point::Point;
331    /// use physdes::polygon::Polygon;
332    ///
333    /// let p1 = Point::new(0, 0);
334    /// let p2 = Point::new(0, 1);
335    /// let p3 = Point::new(1, 1);
336    /// let p4 = Point::new(1, 0);
337    /// let poly = Polygon::new(&[p1, p2, p3, p4]);
338    /// assert!(poly.is_rectilinear());
339    ///
340    /// let p5 = Point::new(0, 0);
341    /// let p6 = Point::new(1, 1);
342    /// let p7 = Point::new(0, 2);
343    /// let poly2 = Polygon::new(&[p5, p6, p7]);
344    /// assert!(!poly2.is_rectilinear());
345    /// ```
346    pub fn is_rectilinear(&self) -> bool {
347        if self.vecs.is_empty() {
348            return true;
349        }
350
351        // Check from origin to vecs[0]
352        if self.vecs[0].x_ != T::zero() && self.vecs[0].y_ != T::zero() {
353            return false;
354        }
355
356        // Check between vecs
357        for i in 0..self.vecs.len() - 1 {
358            let v1 = self.vecs[i];
359            let v2 = self.vecs[i + 1];
360            if v1.x_ != v2.x_ && v1.y_ != v2.y_ {
361                return false;
362            }
363        }
364
365        // Check from vecs[-1] to origin
366        let last_vec = self.vecs.last().unwrap();
367        last_vec.x_ == T::zero() || last_vec.y_ == T::zero()
368    }
369
370    /// Checks if the polygon is oriented anticlockwise
371    ///
372    /// Uses the cross product sign at the minimum-coordinate vertex:
373    ///
374    /// $$\vec{p_{prev}} \times \vec{p_{next}} > 0$$
375    /// where $\vec{p} = (current - prev)$ and $\vec{q} = (next - current)$
376    pub fn is_anticlockwise(&self) -> bool
377    where
378        T: PartialOrd,
379    {
380        let mut pointset = Vec::with_capacity(self.vecs.len() + 1);
381        pointset.push(Vector2::new(T::zero(), T::zero()));
382        pointset.extend(self.vecs.iter().cloned());
383
384        if pointset.len() < 3 {
385            panic!("Polygon must have at least 3 points");
386        }
387
388        // Find the point with minimum coordinates
389        let (min_index, _) = pointset
390            .iter()
391            .enumerate()
392            .min_by(|(_, a), (_, b)| {
393                a.x_.partial_cmp(&b.x_)
394                    .unwrap_or(Ordering::Equal)
395                    .then(a.y_.partial_cmp(&b.y_).unwrap_or(Ordering::Equal))
396            })
397            .unwrap();
398
399        // Get previous and next points with wrap-around
400        let n = pointset.len();
401        let prev_point = pointset[(min_index + n - 1) % n];
402        let current_point = pointset[min_index];
403        let next_point = pointset[(min_index + 1) % n];
404
405        // Calculate cross product
406        (current_point - prev_point).cross(&(next_point - current_point)) > T::zero()
407    }
408
409    /// Checks if the polygon is convex
410    ///
411    /// A polygon is convex if all its interior angles are less than 180 degrees
412    /// and no edges bend inward. All consecutive cross products must have the same sign:
413    ///
414    /// $$(v_i - v_{i-1}) \times (v_{i+1} - v_i) \text{ has consistent sign for all } i$$
415    ///
416    /// # Returns
417    ///
418    /// `true` if the polygon is convex, `false` otherwise
419    ///
420    /// # Examples
421    ///
422    /// ```
423    /// use physdes::point::Point;
424    /// use physdes::polygon::Polygon;
425    ///
426    /// // Convex square
427    /// let convex_points = vec![
428    ///     Point::new(0, 0),
429    ///     Point::new(1, 0),
430    ///     Point::new(1, 1),
431    ///     Point::new(0, 1),
432    /// ];
433    /// let convex_poly = Polygon::new(&convex_points);
434    /// assert!(convex_poly.is_convex());
435    ///
436    /// // Concave polygon (L-shape)
437    /// let concave_points = vec![
438    ///     Point::new(0, 0),
439    ///     Point::new(2, 0),
440    ///     Point::new(2, 1),
441    ///     Point::new(1, 1),
442    ///     Point::new(1, 2),
443    ///     Point::new(0, 2),
444    /// ];
445    /// let concave_poly = Polygon::new(&concave_points);
446    /// assert!(!concave_poly.is_convex());
447    /// ```
448    pub fn is_convex(&self) -> bool
449    where
450        T: PartialOrd,
451    {
452        let n = self.vecs.len();
453        if n < 2 {
454            return false;
455        }
456        if n == 2 {
457            return true;
458        }
459
460        // Compute initial cross product sign using vecs[N-2] and vecs[0].
461        // In pointset terms: pointset[N-2] = vecs[N-2], pointset[1] = vecs[0].
462        // cross = -a.x*b.y + a.y*b.x  (rearranged to avoid unary negation)
463        let cross_product_sign =
464            self.vecs[n - 2].y_ * self.vecs[0].x_ - self.vecs[n - 2].x_ * self.vecs[0].y_;
465
466        for i in 0..n - 1 {
467            let v0 = if i == 0 {
468                Vector2::new(T::zero(), T::zero())
469            } else {
470                self.vecs[i - 1]
471            };
472            let v1 = self.vecs[i];
473            let v2 = self.vecs[i + 1];
474
475            let current_cross =
476                (v1.x_ - v0.x_) * (v2.y_ - v1.y_) - (v1.y_ - v0.y_) * (v2.x_ - v1.x_);
477
478            if (cross_product_sign > T::zero()) != (current_cross > T::zero()) {
479                return false;
480            }
481        }
482
483        true
484    }
485}
486
487/// Creates a monotone polygon from a set of points using a custom comparison function
488///
489/// Partitions points using a cross product test relative to the extremal points:
490///
491/// $$\vec{diff} \times (p - min) \le 0$$
492///
493/// # Arguments
494///
495/// * `pointset` - A slice of points to create the polygon from
496/// * `f` - A closure that defines the ordering of points
497pub fn create_mono_polygon<T, F>(pointset: &[Point<T, T>], func: F) -> Vec<Point<T, T>>
498where
499    T: Clone + Num + Ord + Copy + PartialOrd,
500    F: Fn(&Point<T, T>) -> (T, T),
501{
502    let max_pt = pointset
503        .iter()
504        .max_by(|a, b| func(a).partial_cmp(&func(b)).unwrap())
505        .unwrap();
506    let min_pt = pointset
507        .iter()
508        .min_by(|a, b| func(a).partial_cmp(&func(b)).unwrap())
509        .unwrap();
510    let diff = *max_pt - *min_pt;
511
512    let (mut lst1, mut lst2): (Vec<Point<T, T>>, Vec<Point<T, T>>) = pointset
513        .iter()
514        .partition(|&a| diff.cross(&(*a - *min_pt)) <= T::zero());
515
516    lst1.sort_by_key(|a| func(a));
517    lst2.sort_by_key(|a| func(a));
518    lst2.reverse();
519    lst1.append(&mut lst2);
520    lst1
521}
522
523/// Creates an x-monotone polygon from a set of points
524///
525/// Points are ordered primarily by x-coordinate, secondarily by y-coordinate
526///
527/// # Arguments
528///
529/// * `pointset` - A slice of points to order
530///
531/// # Returns
532///
533/// A vector of points ordered to form an x-monotone polygon
534///
535/// # Examples
536///
537/// ```
538/// use physdes::point::Point;
539/// use physdes::polygon::create_xmono_polygon;
540///
541/// let points = vec![
542///     Point::new(1, 1),
543///     Point::new(3, 2),
544///     Point::new(2, 0),
545///     Point::new(0, 2),
546/// ];
547/// let ordered = create_xmono_polygon(&points);
548/// // Result is ordered to create x-monotone polygon
549/// assert_eq!(ordered.len(), 4);
550/// ```
551#[inline]
552pub fn create_xmono_polygon<T>(pointset: &[Point<T, T>]) -> Vec<Point<T, T>>
553where
554    T: Clone + Num + Ord + Copy + PartialOrd,
555{
556    create_mono_polygon(pointset, |a| (a.xcoord, a.ycoord))
557}
558
559/// Creates a y-monotone polygon from a set of points
560///
561/// Points are ordered primarily by y-coordinate, secondarily by x-coordinate
562///
563/// # Arguments
564///
565/// * `pointset` - A slice of points to order
566///
567/// # Returns
568///
569/// A vector of points ordered to form a y-monotone polygon
570///
571/// # Examples
572///
573/// ```
574/// use physdes::point::Point;
575/// use physdes::polygon::create_ymono_polygon;
576///
577/// let points = vec![
578///     Point::new(1, 1),
579///     Point::new(2, 3),
580///     Point::new(0, 2),
581///     Point::new(2, 0),
582/// ];
583/// let ordered = create_ymono_polygon(&points);
584/// // Result is ordered to create y-monotone polygon
585/// assert_eq!(ordered.len(), 4);
586/// ```
587#[inline]
588pub fn create_ymono_polygon<T>(pointset: &[Point<T, T>]) -> Vec<Point<T, T>>
589where
590    T: Clone + Num + Ord + Copy + PartialOrd,
591{
592    create_mono_polygon(pointset, |a| (a.ycoord, a.xcoord))
593}
594
595/// Checks if a polygon is monotone in a given direction
596///
597/// A polygon is monotone in direction $d$ if the two chains from the minimum
598/// to maximum vertex in direction $d$ are both monotonic (non-decreasing in $d$).
599pub fn polygon_is_monotone<T, F>(lst: &[Point<T, T>], dir: F) -> bool
600where
601    T: Clone + Num + Ord + Copy + PartialOrd,
602    F: Fn(&Point<T, T>) -> (T, T),
603{
604    if lst.len() <= 3 {
605        return true;
606    }
607
608    let (min_index, _) = lst
609        .iter()
610        .enumerate()
611        .min_by(|(_, a), (_, b)| dir(a).partial_cmp(&dir(b)).unwrap())
612        .unwrap();
613
614    let (max_index, _) = lst
615        .iter()
616        .enumerate()
617        .max_by(|(_, a), (_, b)| dir(a).partial_cmp(&dir(b)).unwrap())
618        .unwrap();
619
620    let n = lst.len();
621
622    // Chain from min to max
623    let mut i = min_index;
624    while i != max_index {
625        let next_i = (i + 1) % n;
626        if dir(&lst[i]).0 > dir(&lst[next_i]).0 {
627            return false;
628        }
629        i = next_i;
630    }
631
632    // Chain from max to min
633    let mut i = max_index;
634    while i != min_index {
635        let next_i = (i + 1) % n;
636        if dir(&lst[i]).0 < dir(&lst[next_i]).0 {
637            return false;
638        }
639        i = next_i;
640    }
641
642    true
643}
644
645/// Checks if a polygon is x-monotone
646pub fn polygon_is_xmonotone<T>(lst: &[Point<T, T>]) -> bool
647where
648    T: Clone + Num + Ord + Copy + PartialOrd,
649{
650    polygon_is_monotone(lst, |pt| (pt.xcoord, pt.ycoord))
651}
652
653/// Checks if a polygon is y-monotone
654pub fn polygon_is_ymonotone<T>(lst: &[Point<T, T>]) -> bool
655where
656    T: Clone + Num + Ord + Copy + PartialOrd,
657{
658    polygon_is_monotone(lst, |pt| (pt.ycoord, pt.xcoord))
659}
660
661/// Determines if a point is inside a polygon using the winding number algorithm
662///
663/// Counts edge crossings using a cross product test:
664///
665/// $$\vec{(q-p_0)} \times \vec{(p_1-p_0)} \text{ determines which side of the edge the point lies on}$$
666pub fn point_in_polygon<T>(pointset: &[Point<T, T>], ptq: &Point<T, T>) -> bool
667where
668    T: Clone + Num + Ord + Copy + PartialOrd,
669{
670    let n = pointset.len();
671    if n == 0 {
672        return false;
673    }
674
675    let mut pt0 = &pointset[n - 1];
676    let mut res = false;
677
678    for pt1 in pointset.iter() {
679        if (pt1.ycoord <= ptq.ycoord && ptq.ycoord < pt0.ycoord)
680            || (pt0.ycoord <= ptq.ycoord && ptq.ycoord < pt1.ycoord)
681        {
682            let det = (*ptq - *pt0).cross(&(*pt1 - *pt0));
683            if pt1.ycoord > pt0.ycoord {
684                if det < T::zero() {
685                    res = !res;
686                }
687            } else if det > T::zero() {
688                res = !res;
689            }
690        }
691        pt0 = pt1;
692    }
693
694    res
695}
696
697/// Determines if a polygon represented by points is oriented anticlockwise
698///
699/// $$\vec{p_{prev}} \times \vec{p_{next}} > 0$$
700/// at the minimum-coordinate vertex.
701pub fn polygon_is_anticlockwise<T>(pointset: &[Point<T, T>]) -> bool
702where
703    T: Clone + Num + Ord + Copy + PartialOrd,
704{
705    if pointset.len() < 3 {
706        panic!("Polygon must have at least 3 points");
707    }
708
709    // Find the point with minimum coordinates
710    let (min_index, _) = pointset
711        .iter()
712        .enumerate()
713        .min_by(|(_, a), (_, b)| {
714            a.xcoord
715                .partial_cmp(&b.xcoord)
716                .unwrap_or(Ordering::Equal)
717                .then(a.ycoord.partial_cmp(&b.ycoord).unwrap_or(Ordering::Equal))
718        })
719        .unwrap();
720
721    // Get previous and next points with wrap-around
722    let n = pointset.len();
723    let prev_point = pointset[(min_index + n - 1) % n];
724    let current_point = pointset[min_index];
725    let next_point = pointset[(min_index + 1) % n];
726
727    // Calculate cross product
728    (current_point - prev_point).cross(&(next_point - current_point)) > T::zero()
729}
730
731// Implement PartialEq for Polygon
732impl<T: PartialEq> PartialEq for Polygon<T> {
733    /// Returns `true` if two polygons have the same origin and vectors.
734    fn eq(&self, other: &Self) -> bool {
735        self.origin == other.origin && self.vecs == other.vecs
736    }
737}
738
739// Implement AddAssign and SubAssign for Polygon
740impl<T: AddAssign + Clone + Num> AddAssign<Vector2<T, T>> for Polygon<T> {
741    /// Translates the polygon by adding a vector to its origin.
742    fn add_assign(&mut self, rhs: Vector2<T, T>) {
743        self.origin += rhs;
744    }
745}
746
747impl<T: SubAssign + Clone + Num> SubAssign<Vector2<T, T>> for Polygon<T> {
748    /// Translates the polygon by subtracting a vector from its origin.
749    fn sub_assign(&mut self, rhs: Vector2<T, T>) {
750        self.origin -= rhs;
751    }
752}
753
754#[cfg(test)]
755mod tests {
756    use super::*;
757    use crate::point::Point;
758    use crate::vector2::Vector2;
759
760    #[test]
761    fn test_polygon() {
762        let coords = [
763            (-2, 2),
764            (0, -1),
765            (-5, 1),
766            (-2, 4),
767            (0, -4),
768            (-4, 3),
769            (-6, -2),
770            (5, 1),
771            (2, 2),
772            (3, -3),
773            (-3, -3),
774            (3, 3),
775            (-3, -4),
776            (1, 4),
777        ];
778
779        let mut pointset = Vec::new();
780        for (x_coord, y_coord) in coords.iter() {
781            pointset.push(Point::new(*x_coord, *y_coord));
782        }
783
784        let poly_points = create_xmono_polygon(&pointset);
785        assert!(polygon_is_anticlockwise(&poly_points));
786
787        let poly = Polygon::from_pointset(&poly_points);
788        let mut poly2 = Polygon::from_pointset(&poly_points);
789        poly2.add_assign(Vector2::new(4, 5));
790        poly2.sub_assign(Vector2::new(4, 5));
791        assert_eq!(poly2, poly);
792    }
793
794    #[test]
795    fn test_ymono_polygon() {
796        let coords = [
797            (-2, 2),
798            (0, -1),
799            (-5, 1),
800            (-2, 4),
801            (0, -4),
802            (-4, 3),
803            (-6, -2),
804            (5, 1),
805            (2, 2),
806            (3, -3),
807            (-3, -3),
808            (3, 3),
809            (-3, -4),
810            (1, 4),
811        ];
812
813        let mut pointset = Vec::new();
814        for (x_coord, y_coord) in coords.iter() {
815            pointset.push(Point::new(*x_coord, *y_coord));
816        }
817
818        let poly_points = create_ymono_polygon(&pointset);
819        assert!(polygon_is_ymonotone(&poly_points));
820        assert!(!polygon_is_xmonotone(&poly_points));
821        assert!(polygon_is_anticlockwise(&poly_points));
822
823        let poly = Polygon::from_pointset(&poly_points);
824        assert_eq!(poly.signed_area_x2(), 102);
825        assert!(poly.is_anticlockwise());
826    }
827
828    #[test]
829    fn test_xmono_polygon() {
830        let coords = [
831            (-2, 2),
832            (0, -1),
833            (-5, 1),
834            (-2, 4),
835            (0, -4),
836            (-4, 3),
837            (-6, -2),
838            (5, 1),
839            (2, 2),
840            (3, -3),
841            (-3, -3),
842            (3, 3),
843            (-3, -4),
844            (1, 4),
845        ];
846
847        let mut pointset = Vec::new();
848        for (x_coord, y_coord) in coords.iter() {
849            pointset.push(Point::new(*x_coord, *y_coord));
850        }
851
852        let poly_points = create_xmono_polygon(&pointset);
853        assert!(polygon_is_xmonotone(&poly_points));
854        assert!(!polygon_is_ymonotone(&poly_points));
855        assert!(polygon_is_anticlockwise(&poly_points));
856
857        let poly = Polygon::from_pointset(&poly_points);
858        assert_eq!(poly.signed_area_x2(), 111);
859        assert!(poly.is_anticlockwise());
860    }
861
862    #[test]
863    fn test_is_rectilinear() {
864        // Create a rectilinear polygon
865        let rectilinear_coords = [(0, 0), (0, 1), (1, 1), (1, 0)];
866        let rectilinear_points: Vec<Point<i32, i32>> = rectilinear_coords
867            .iter()
868            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
869            .collect();
870        let rectilinear_polygon = Polygon::from_pointset(&rectilinear_points);
871        assert!(rectilinear_polygon.is_rectilinear());
872
873        // Create a non-rectilinear polygon
874        let non_rectilinear_coords = [(0, 0), (1, 1), (2, 0)];
875        let non_rectilinear_points: Vec<Point<i32, i32>> = non_rectilinear_coords
876            .iter()
877            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
878            .collect();
879        let non_rectilinear_polygon = Polygon::from_pointset(&non_rectilinear_points);
880        assert!(!non_rectilinear_polygon.is_rectilinear());
881    }
882
883    #[test]
884    fn test_is_convex() {
885        // Test case 1: Convex polygon
886        let convex_coords = [(0, 0), (2, 0), (2, 2), (0, 2)];
887        let convex_points: Vec<Point<i32, i32>> = convex_coords
888            .iter()
889            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
890            .collect();
891        let convex_polygon = Polygon::from_pointset(&convex_points);
892        assert!(convex_polygon.is_convex());
893
894        // Test case 2: Non-convex polygon
895        let non_convex_coords = [(0, 0), (2, 0), (1, 1), (2, 2), (0, 2)];
896        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
897            .iter()
898            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
899            .collect();
900        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
901        assert!(!non_convex_polygon.is_convex());
902
903        // Test case 3: Triangle (always convex)
904        let triangle_coords = [(0, 0), (2, 0), (1, 2)];
905        let triangle_points: Vec<Point<i32, i32>> = triangle_coords
906            .iter()
907            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
908            .collect();
909        let triangle = Polygon::from_pointset(&triangle_points);
910        assert!(triangle.is_convex());
911    }
912
913    #[test]
914    fn test_is_anticlockwise() {
915        // Clockwise polygon
916        let clockwise_coords = [(0, 0), (0, 1), (1, 1), (1, 0)];
917        let clockwise_points: Vec<Point<i32, i32>> = clockwise_coords
918            .iter()
919            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
920            .collect();
921        let clockwise_polygon = Polygon::from_pointset(&clockwise_points);
922        assert!(!clockwise_polygon.is_anticlockwise());
923
924        // Counter-clockwise polygon
925        let counter_clockwise_coords = [(0, 0), (1, 0), (1, 1), (0, 1)];
926        let counter_clockwise_points: Vec<Point<i32, i32>> = counter_clockwise_coords
927            .iter()
928            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
929            .collect();
930        let counter_clockwise_polygon = Polygon::from_pointset(&counter_clockwise_points);
931        assert!(counter_clockwise_polygon.is_anticlockwise());
932    }
933
934    #[test]
935    fn test_is_convex_clockwise() {
936        // Convex clockwise polygon
937        let convex_coords = [(0, 0), (0, 2), (2, 2), (2, 0)];
938        let convex_points: Vec<Point<i32, i32>> = convex_coords
939            .iter()
940            .map(|(x, y)| Point::new(*x, *y))
941            .collect();
942        let convex_polygon = Polygon::from_pointset(&convex_points);
943        assert!(convex_polygon.is_convex());
944
945        // Non-convex clockwise polygon
946        let non_convex_coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0)];
947        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
948            .iter()
949            .map(|(x, y)| Point::new(*x, *y))
950            .collect();
951        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
952        assert!(!non_convex_polygon.is_convex());
953    }
954
955    #[test]
956    fn test_point_in_polygon_missed_branches() {
957        let coords = [(0, 0), (10, 0), (10, 10), (0, 10)];
958        let pointset: Vec<Point<i32, i32>> = coords
959            .iter()
960            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
961            .collect();
962
963        // Test case where ptq.ycoord == pt0.ycoord
964        assert!(!point_in_polygon(&pointset, &Point::new(5, 10)));
965
966        // Test case where ptq.ycoord == pt1.ycoord
967        assert!(point_in_polygon(&pointset, &Point::new(5, 0)));
968
969        // Test case where det == 0 (point on edge)
970        assert!(point_in_polygon(&pointset, &Point::new(5, 0)));
971    }
972
973    #[test]
974    #[should_panic(expected = "Polygon must have at least 3 points")]
975    fn test_polygon_is_anticlockwise_less_than_3_points() {
976        let coords = [(0, 0), (0, 1)];
977        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
978        polygon_is_anticlockwise(&points);
979    }
980
981    #[test]
982    #[should_panic(expected = "Polygon must have at least 3 points")]
983    fn test_is_anticlockwise_less_than_3_points() {
984        let coords = [(0, 0), (0, 1)];
985        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
986        let polygon = Polygon::from_pointset(&points);
987        polygon.is_anticlockwise();
988    }
989
990    #[test]
991    fn test_is_convex_more() {
992        // Non-convex anti-clockwise polygon
993        let non_convex_coords = [(0, 0), (2, 0), (1, 1), (2, 2), (0, 2)];
994        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
995            .iter()
996            .map(|(x, y)| Point::new(*x, *y))
997            .collect();
998        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
999        assert!(!non_convex_polygon.is_convex());
1000
1001        // Convex anti-clockwise polygon
1002        let convex_coords = [(0, 0), (2, 0), (2, 2), (0, 2)];
1003        let convex_points: Vec<Point<i32, i32>> = convex_coords
1004            .iter()
1005            .map(|(x, y)| Point::new(*x, *y))
1006            .collect();
1007        let convex_polygon = Polygon::from_pointset(&convex_points);
1008        assert!(convex_polygon.is_convex());
1009    }
1010
1011    #[test]
1012    fn test_point_in_polygon_more() {
1013        // Create a polygon that will trigger the missed branches
1014        let coords = [(0, 0), (10, 5), (0, 10)];
1015        let pointset: Vec<Point<i32, i32>> = coords
1016            .iter()
1017            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
1018            .collect();
1019
1020        // This should trigger `det > 0`
1021        assert!(point_in_polygon(&pointset, &Point::new(1, 5)));
1022
1023        // Create a clockwise polygon to trigger `det < 0`
1024        let coords_cw = [(0, 0), (0, 10), (10, 5)];
1025        let pointset_cw: Vec<Point<i32, i32>> = coords_cw
1026            .iter()
1027            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
1028            .collect();
1029        assert!(point_in_polygon(&pointset_cw, &Point::new(1, 5)));
1030    }
1031
1032    #[test]
1033    fn test_is_rectilinear_non_rectilinear_last_edge() {
1034        // Last edge from last vec to origin is diagonal, not axis-aligned
1035        let coords = [(0, 0), (4, 0), (4, 4), (1, 3)];
1036        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1037        let poly = Polygon::from_pointset(&points);
1038        assert!(!poly.is_rectilinear());
1039    }
1040
1041    #[test]
1042    fn test_is_convex_less_than_two_vecs() {
1043        // Polygon with only 1 vec (2 vertices), is_convex should return false
1044        let origin = Point::new(0, 0);
1045        let vecs = vec![Vector2::new(4, 0)];
1046        let poly = Polygon::from_origin_and_vectors(origin, vecs);
1047        assert!(!poly.is_convex());
1048    }
1049
1050    #[test]
1051    fn test_bounding_box_branches() {
1052        // Polygon where y varies both decreasing and increasing to test min/max branches
1053        let coords = [(3, 5), (8, 2), (10, 7), (6, 9), (1, 4)];
1054        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1055        let poly = Polygon::from_pointset(&points);
1056        let (min_pt, max_pt) = poly.bounding_box();
1057        assert_eq!(min_pt, Point::new(1, 2));
1058        assert_eq!(max_pt, Point::new(10, 9));
1059    }
1060}
1061
1062#[test]
1063fn test_polygon_signed_area_x2() {
1064    // Test signed_area_x2 for different polygons
1065    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1066    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1067    let poly = Polygon::from_pointset(&pointset);
1068    assert_eq!(poly.signed_area_x2(), 24); // 2 * (4*3) = 24
1069}
1070
1071#[test]
1072fn test_polygon_vertices() {
1073    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1074    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1075    let poly = Polygon::from_pointset(&pointset);
1076    let vertices = poly.vertices();
1077    assert_eq!(vertices.len(), 4);
1078}
1079
1080#[test]
1081fn test_polygon_bounding_box() {
1082    let coords = [(1, 1), (5, 1), (5, 4), (1, 4)];
1083    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1084    let poly = Polygon::from_pointset(&pointset);
1085    let (min, max) = poly.bounding_box();
1086    assert_eq!(min, Point::new(1, 1));
1087    assert_eq!(max, Point::new(5, 4));
1088}
1089
1090#[test]
1091fn test_polygon_add_assign() {
1092    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1093    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1094    let mut poly = Polygon::from_pointset(&pointset);
1095    poly.add_assign(Vector2::new(1, 2));
1096    assert_eq!(poly.origin, Point::new(1, 2));
1097}
1098
1099#[test]
1100fn test_polygon_sub_assign() {
1101    let coords = [(1, 2), (5, 2), (5, 5), (1, 5)];
1102    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1103    let mut poly = Polygon::from_pointset(&pointset);
1104    poly.sub_assign(Vector2::new(1, 2));
1105    assert_eq!(poly.origin, Point::new(0, 0));
1106}
1107
1108#[test]
1109fn test_polygon_partial_eq() {
1110    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1111    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1112    let poly1 = Polygon::from_pointset(&pointset);
1113    let poly2 = Polygon::from_pointset(&pointset);
1114    assert_eq!(poly1, poly2);
1115
1116    let coords2 = [(0, 0), (5, 0), (5, 3), (0, 3)];
1117    let pointset2: Vec<Point<i32, i32>> = coords2.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1118    let poly3 = Polygon::from_pointset(&pointset2);
1119    assert_ne!(poly1, poly3);
1120}
1121
1122#[test]
1123fn test_polygon_from_origin_and_vectors() {
1124    let origin = Point::new(0, 0);
1125    let vecs = vec![Vector2::new(4, 0), Vector2::new(0, 3), Vector2::new(-4, 0)];
1126    let poly = Polygon::from_origin_and_vectors(origin, vecs);
1127    assert_eq!(poly.origin, Point::new(0, 0));
1128    assert_eq!(poly.vecs.len(), 3);
1129}
1130
1131#[test]
1132fn test_polygon_default() {
1133    let poly: Polygon<i32> = Polygon::default();
1134    assert_eq!(poly.origin, Point::new(0, 0));
1135}
1136
1137#[test]
1138fn test_polygon_is_monotone_custom() {
1139    // Test polygon_is_monotone with custom direction
1140    let coords = [(0, 0), (1, 0), (2, 1), (1, 2), (0, 2)];
1141    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1142    // x-monotone
1143    assert!(polygon_is_monotone(&pointset, |pt| (pt.xcoord, pt.ycoord)));
1144}
1145
1146#[test]
1147fn test_create_mono_polygon_custom() {
1148    // Test create_mono_polygon with custom function
1149    let coords = [(0, 0), (2, 1), (1, 2), (3, 2)];
1150    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1151    let result = create_mono_polygon(&pointset, |pt| (pt.xcoord, pt.ycoord));
1152    assert!(!result.is_empty());
1153}
1154
1155#[test]
1156fn test_polygon_empty_vecs_is_rectilinear() {
1157    // Edge case: polygon with no vecs should be rectilinear
1158    let origin = Point::new(0, 0);
1159    let poly = Polygon::from_origin_and_vectors(origin, vec![]);
1160    assert!(poly.is_rectilinear());
1161}
1162
1163#[test]
1164fn test_polygon_signed_area_x2_triangle() {
1165    // Triangle area calculation
1166    let coords = [(0, 0), (3, 0), (0, 4)];
1167    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1168    let poly = Polygon::from_pointset(&pointset);
1169    // Area = 0.5 * |0*(0-4) + 3*(4-0) + 0*(0-0)| = 0.5 * 12 = 6
1170    // signed_area_x2 = 2 * area = 12
1171    assert_eq!(poly.signed_area_x2(), 12);
1172}
1173
1174#[test]
1175fn test_polygon_signed_area_x2_single_vec() {
1176    // Polygon with just 1 vec (2 vertices), signed_area_x2 should return 0 (n < 2)
1177    let origin = Point::new(0, 0);
1178    let vecs = vec![Vector2::new(4, 0)];
1179    let poly = Polygon::from_origin_and_vectors(origin, vecs);
1180    assert_eq!(poly.signed_area_x2(), 0);
1181}
1182
1183#[test]
1184fn test_polygon_area_multi_vertex() {
1185    // Pentagon to exercise the area() calculation loop body
1186    let coords = [(0, 0), (4, 0), (5, 3), (2, 5), (-1, 2)];
1187    let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1188    let poly = Polygon::from_pointset(&points);
1189    // area is signed, should be nonzero for a valid polygon
1190    assert_ne!(poly.area(), 0);
1191}