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 mut min_x = self.origin.xcoord;
296        let mut min_y = self.origin.ycoord;
297        let mut max_x = self.origin.xcoord;
298        let mut max_y = self.origin.ycoord;
299
300        for vec in &self.vecs {
301            let x = self.origin.xcoord + vec.x_;
302            let y = self.origin.ycoord + vec.y_;
303            if x < min_x {
304                min_x = x;
305            }
306            if y < min_y {
307                min_y = y;
308            }
309            if x > max_x {
310                max_x = x;
311            }
312            if y > max_y {
313                max_y = y;
314            }
315        }
316
317        (Point::new(min_x, min_y), Point::new(max_x, max_y))
318    }
319
320    /// Checks if the polygon is rectilinear
321    ///
322    /// A polygon is rectilinear if all its edges are either horizontal or vertical.
323    ///
324    /// # Returns
325    ///
326    /// `true` if the polygon is rectilinear, `false` otherwise
327    ///
328    /// # Examples
329    ///
330    /// ```
331    /// use physdes::point::Point;
332    /// use physdes::polygon::Polygon;
333    ///
334    /// let p1 = Point::new(0, 0);
335    /// let p2 = Point::new(0, 1);
336    /// let p3 = Point::new(1, 1);
337    /// let p4 = Point::new(1, 0);
338    /// let poly = Polygon::new(&[p1, p2, p3, p4]);
339    /// assert!(poly.is_rectilinear());
340    ///
341    /// let p5 = Point::new(0, 0);
342    /// let p6 = Point::new(1, 1);
343    /// let p7 = Point::new(0, 2);
344    /// let poly2 = Polygon::new(&[p5, p6, p7]);
345    /// assert!(!poly2.is_rectilinear());
346    /// ```
347    pub fn is_rectilinear(&self) -> bool {
348        if self.vecs.is_empty() {
349            return true;
350        }
351
352        // Check from origin to vecs[0]
353        if self.vecs[0].x_ != T::zero() && self.vecs[0].y_ != T::zero() {
354            return false;
355        }
356
357        // Check between vecs
358        for i in 0..self.vecs.len() - 1 {
359            let v1 = self.vecs[i];
360            let v2 = self.vecs[i + 1];
361            if v1.x_ != v2.x_ && v1.y_ != v2.y_ {
362                return false;
363            }
364        }
365
366        // Check from vecs[-1] to origin
367        let last_vec = self.vecs.last().unwrap();
368        last_vec.x_ == T::zero() || last_vec.y_ == T::zero()
369    }
370
371    /// Checks if the polygon is oriented anticlockwise
372    ///
373    /// Uses the cross product sign at the minimum-coordinate vertex:
374    ///
375    /// $$\vec{p_{prev}} \times \vec{p_{next}} > 0$$
376    /// where $\vec{p} = (current - prev)$ and $\vec{q} = (next - current)$
377    pub fn is_anticlockwise(&self) -> bool
378    where
379        T: PartialOrd,
380    {
381        let n = self.vecs.len() + 1;
382        if n < 3 {
383            panic!("Polygon must have at least 3 points");
384        }
385
386        let get_pt = |i: usize| -> Vector2<T, T> {
387            if i == 0 {
388                Vector2::new(T::zero(), T::zero())
389            } else {
390                self.vecs[i - 1]
391            }
392        };
393
394        // Find the point with minimum coordinates
395        let (min_index, _) = (0..n)
396            .map(|i| (i, get_pt(i)))
397            .min_by(|(_, a), (_, b)| {
398                a.x_.partial_cmp(&b.x_)
399                    .unwrap_or(Ordering::Equal)
400                    .then(a.y_.partial_cmp(&b.y_).unwrap_or(Ordering::Equal))
401            })
402            .unwrap();
403
404        // Get previous and next points with wrap-around
405        let prev_point = get_pt((min_index + n - 1) % n);
406        let current_point = get_pt(min_index);
407        let next_point = get_pt((min_index + 1) % n);
408
409        // Calculate cross product
410        (current_point - prev_point).cross(&(next_point - current_point)) > T::zero()
411    }
412
413    /// Checks if the polygon is convex
414    ///
415    /// A polygon is convex if all its interior angles are less than 180 degrees
416    /// and no edges bend inward. All consecutive cross products must have the same sign:
417    ///
418    /// $$(v_i - v_{i-1}) \times (v_{i+1} - v_i) \text{ has consistent sign for all } i$$
419    ///
420    /// # Returns
421    ///
422    /// `true` if the polygon is convex, `false` otherwise
423    ///
424    /// # Examples
425    ///
426    /// ```
427    /// use physdes::point::Point;
428    /// use physdes::polygon::Polygon;
429    ///
430    /// // Convex square
431    /// let convex_points = vec![
432    ///     Point::new(0, 0),
433    ///     Point::new(1, 0),
434    ///     Point::new(1, 1),
435    ///     Point::new(0, 1),
436    /// ];
437    /// let convex_poly = Polygon::new(&convex_points);
438    /// assert!(convex_poly.is_convex());
439    ///
440    /// // Concave polygon (L-shape)
441    /// let concave_points = vec![
442    ///     Point::new(0, 0),
443    ///     Point::new(2, 0),
444    ///     Point::new(2, 1),
445    ///     Point::new(1, 1),
446    ///     Point::new(1, 2),
447    ///     Point::new(0, 2),
448    /// ];
449    /// let concave_poly = Polygon::new(&concave_points);
450    /// assert!(!concave_poly.is_convex());
451    /// ```
452    pub fn is_convex(&self) -> bool
453    where
454        T: PartialOrd,
455    {
456        let n = self.vecs.len();
457        if n < 2 {
458            return false;
459        }
460        if n == 2 {
461            return true;
462        }
463
464        // Compute initial cross product sign using vecs[N-2] and vecs[0].
465        // In pointset terms: pointset[N-2] = vecs[N-2], pointset[1] = vecs[0].
466        // cross = -a.x*b.y + a.y*b.x  (rearranged to avoid unary negation)
467        let cross_product_sign =
468            self.vecs[n - 2].y_ * self.vecs[0].x_ - self.vecs[n - 2].x_ * self.vecs[0].y_;
469
470        for i in 0..n - 1 {
471            let v0 = if i == 0 {
472                Vector2::new(T::zero(), T::zero())
473            } else {
474                self.vecs[i - 1]
475            };
476            let v1 = self.vecs[i];
477            let v2 = self.vecs[i + 1];
478
479            let current_cross =
480                (v1.x_ - v0.x_) * (v2.y_ - v1.y_) - (v1.y_ - v0.y_) * (v2.x_ - v1.x_);
481
482            if (cross_product_sign > T::zero()) != (current_cross > T::zero()) {
483                return false;
484            }
485        }
486
487        true
488    }
489}
490
491/// Creates a monotone polygon from a set of points using a custom comparison function
492///
493/// Partitions points using a cross product test relative to the extremal points:
494///
495/// $$\vec{diff} \times (p - min) \le 0$$
496///
497/// # Arguments
498///
499/// * `pointset` - A slice of points to create the polygon from
500/// * `f` - A closure that defines the ordering of points
501pub fn create_mono_polygon<T, F>(pointset: &[Point<T, T>], func: F) -> Vec<Point<T, T>>
502where
503    T: Clone + Num + Ord + Copy + PartialOrd,
504    F: Fn(&Point<T, T>) -> (T, T),
505{
506    let max_pt = pointset
507        .iter()
508        .max_by(|a, b| func(a).partial_cmp(&func(b)).unwrap())
509        .unwrap();
510    let min_pt = pointset
511        .iter()
512        .min_by(|a, b| func(a).partial_cmp(&func(b)).unwrap())
513        .unwrap();
514    let diff = *max_pt - *min_pt;
515
516    let (mut lst1, mut lst2): (Vec<Point<T, T>>, Vec<Point<T, T>>) = pointset
517        .iter()
518        .partition(|&a| diff.cross(&(*a - *min_pt)) <= T::zero());
519
520    lst1.sort_by_key(|a| func(a));
521    lst2.sort_by_key(|a| func(a));
522    lst2.reverse();
523    lst1.append(&mut lst2);
524    lst1
525}
526
527/// Creates an x-monotone polygon from a set of points
528///
529/// Points are ordered primarily by x-coordinate, secondarily by y-coordinate
530///
531/// # Arguments
532///
533/// * `pointset` - A slice of points to order
534///
535/// # Returns
536///
537/// A vector of points ordered to form an x-monotone polygon
538///
539/// # Examples
540///
541/// ```
542/// use physdes::point::Point;
543/// use physdes::polygon::create_xmono_polygon;
544///
545/// let points = vec![
546///     Point::new(1, 1),
547///     Point::new(3, 2),
548///     Point::new(2, 0),
549///     Point::new(0, 2),
550/// ];
551/// let ordered = create_xmono_polygon(&points);
552/// // Result is ordered to create x-monotone polygon
553/// assert_eq!(ordered.len(), 4);
554/// ```
555#[inline]
556pub fn create_xmono_polygon<T>(pointset: &[Point<T, T>]) -> Vec<Point<T, T>>
557where
558    T: Clone + Num + Ord + Copy + PartialOrd,
559{
560    create_mono_polygon(pointset, |a| (a.xcoord, a.ycoord))
561}
562
563/// Creates a y-monotone polygon from a set of points
564///
565/// Points are ordered primarily by y-coordinate, secondarily by x-coordinate
566///
567/// # Arguments
568///
569/// * `pointset` - A slice of points to order
570///
571/// # Returns
572///
573/// A vector of points ordered to form a y-monotone polygon
574///
575/// # Examples
576///
577/// ```
578/// use physdes::point::Point;
579/// use physdes::polygon::create_ymono_polygon;
580///
581/// let points = vec![
582///     Point::new(1, 1),
583///     Point::new(2, 3),
584///     Point::new(0, 2),
585///     Point::new(2, 0),
586/// ];
587/// let ordered = create_ymono_polygon(&points);
588/// // Result is ordered to create y-monotone polygon
589/// assert_eq!(ordered.len(), 4);
590/// ```
591#[inline]
592pub fn create_ymono_polygon<T>(pointset: &[Point<T, T>]) -> Vec<Point<T, T>>
593where
594    T: Clone + Num + Ord + Copy + PartialOrd,
595{
596    create_mono_polygon(pointset, |a| (a.ycoord, a.xcoord))
597}
598
599/// Checks if a polygon is monotone in a given direction
600///
601/// A polygon is monotone in direction $d$ if the two chains from the minimum
602/// to maximum vertex in direction $d$ are both monotonic (non-decreasing in $d$).
603///
604/// For the chain from min to max: $d(v_i) \le d(v_{i+1})$ for all consecutive vertices.
605pub fn polygon_is_monotone<T, F>(lst: &[Point<T, T>], dir: F) -> bool
606where
607    T: Clone + Num + Ord + Copy + PartialOrd,
608    F: Fn(&Point<T, T>) -> (T, T),
609{
610    if lst.len() <= 3 {
611        return true;
612    }
613
614    let (min_index, _) = lst
615        .iter()
616        .enumerate()
617        .min_by(|(_, a), (_, b)| dir(a).partial_cmp(&dir(b)).unwrap())
618        .unwrap();
619
620    let (max_index, _) = lst
621        .iter()
622        .enumerate()
623        .max_by(|(_, a), (_, b)| dir(a).partial_cmp(&dir(b)).unwrap())
624        .unwrap();
625
626    let n = lst.len();
627
628    // Chain from min to max
629    let mut i = min_index;
630    while i != max_index {
631        let next_i = (i + 1) % n;
632        if dir(&lst[i]).0 > dir(&lst[next_i]).0 {
633            return false;
634        }
635        i = next_i;
636    }
637
638    // Chain from max to min
639    let mut i = max_index;
640    while i != min_index {
641        let next_i = (i + 1) % n;
642        if dir(&lst[i]).0 < dir(&lst[next_i]).0 {
643            return false;
644        }
645        i = next_i;
646    }
647
648    true
649}
650
651/// Checks if a polygon is x-monotone
652pub fn polygon_is_xmonotone<T>(lst: &[Point<T, T>]) -> bool
653where
654    T: Clone + Num + Ord + Copy + PartialOrd,
655{
656    polygon_is_monotone(lst, |pt| (pt.xcoord, pt.ycoord))
657}
658
659/// Checks if a polygon is y-monotone
660pub fn polygon_is_ymonotone<T>(lst: &[Point<T, T>]) -> bool
661where
662    T: Clone + Num + Ord + Copy + PartialOrd,
663{
664    polygon_is_monotone(lst, |pt| (pt.ycoord, pt.xcoord))
665}
666
667/// Determines if a point is inside a polygon using the winding number algorithm
668///
669/// Counts edge crossings using a cross product test:
670///
671/// $$\vec{(q-p_0)} \times \vec{(p_1-p_0)} \text{ determines which side of the edge the point lies on}$$
672pub fn point_in_polygon<T>(pointset: &[Point<T, T>], ptq: &Point<T, T>) -> bool
673where
674    T: Clone + Num + Ord + Copy + PartialOrd,
675{
676    let n = pointset.len();
677    if n == 0 {
678        return false;
679    }
680
681    let mut pt0 = &pointset[n - 1];
682    let mut res = false;
683
684    for pt1 in pointset.iter() {
685        if (pt1.ycoord <= ptq.ycoord && ptq.ycoord < pt0.ycoord)
686            || (pt0.ycoord <= ptq.ycoord && ptq.ycoord < pt1.ycoord)
687        {
688            let det = (*ptq - *pt0).cross(&(*pt1 - *pt0));
689            if pt1.ycoord > pt0.ycoord {
690                if det < T::zero() {
691                    res = !res;
692                }
693            } else if det > T::zero() {
694                res = !res;
695            }
696        }
697        pt0 = pt1;
698    }
699
700    res
701}
702
703/// Determines if a polygon represented by points is oriented anticlockwise
704///
705/// $$\vec{p_{prev}} \times \vec{p_{next}} > 0$$
706/// at the minimum-coordinate vertex.
707pub fn polygon_is_anticlockwise<T>(pointset: &[Point<T, T>]) -> bool
708where
709    T: Clone + Num + Ord + Copy + PartialOrd,
710{
711    if pointset.len() < 3 {
712        panic!("Polygon must have at least 3 points");
713    }
714
715    // Find the point with minimum coordinates
716    let (min_index, _) = pointset
717        .iter()
718        .enumerate()
719        .min_by(|(_, a), (_, b)| {
720            a.xcoord
721                .partial_cmp(&b.xcoord)
722                .unwrap_or(Ordering::Equal)
723                .then(a.ycoord.partial_cmp(&b.ycoord).unwrap_or(Ordering::Equal))
724        })
725        .unwrap();
726
727    // Get previous and next points with wrap-around
728    let n = pointset.len();
729    let prev_point = pointset[(min_index + n - 1) % n];
730    let current_point = pointset[min_index];
731    let next_point = pointset[(min_index + 1) % n];
732
733    // Calculate cross product
734    (current_point - prev_point).cross(&(next_point - current_point)) > T::zero()
735}
736
737// Implement PartialEq for Polygon
738impl<T: PartialEq> PartialEq for Polygon<T> {
739    /// Returns `true` if two polygons have the same origin and vectors.
740    fn eq(&self, other: &Self) -> bool {
741        self.origin == other.origin && self.vecs == other.vecs
742    }
743}
744
745// Implement AddAssign and SubAssign for Polygon
746impl<T: AddAssign + Clone + Num> AddAssign<Vector2<T, T>> for Polygon<T> {
747    /// Translates the polygon by adding a vector to its origin.
748    fn add_assign(&mut self, rhs: Vector2<T, T>) {
749        self.origin += rhs;
750    }
751}
752
753impl<T: SubAssign + Clone + Num> SubAssign<Vector2<T, T>> for Polygon<T> {
754    /// Translates the polygon by subtracting a vector from its origin.
755    fn sub_assign(&mut self, rhs: Vector2<T, T>) {
756        self.origin -= rhs;
757    }
758}
759
760#[cfg(test)]
761mod tests {
762    use super::*;
763    use crate::point::Point;
764    use crate::vector2::Vector2;
765
766    #[test]
767    fn test_polygon() {
768        let coords = [
769            (-2, 2),
770            (0, -1),
771            (-5, 1),
772            (-2, 4),
773            (0, -4),
774            (-4, 3),
775            (-6, -2),
776            (5, 1),
777            (2, 2),
778            (3, -3),
779            (-3, -3),
780            (3, 3),
781            (-3, -4),
782            (1, 4),
783        ];
784
785        let mut pointset = Vec::new();
786        for (x_coord, y_coord) in coords.iter() {
787            pointset.push(Point::new(*x_coord, *y_coord));
788        }
789
790        let poly_points = create_xmono_polygon(&pointset);
791        assert!(polygon_is_anticlockwise(&poly_points));
792
793        let poly = Polygon::from_pointset(&poly_points);
794        let mut poly2 = Polygon::from_pointset(&poly_points);
795        poly2.add_assign(Vector2::new(4, 5));
796        poly2.sub_assign(Vector2::new(4, 5));
797        assert_eq!(poly2, poly);
798    }
799
800    #[test]
801    fn test_ymono_polygon() {
802        let coords = [
803            (-2, 2),
804            (0, -1),
805            (-5, 1),
806            (-2, 4),
807            (0, -4),
808            (-4, 3),
809            (-6, -2),
810            (5, 1),
811            (2, 2),
812            (3, -3),
813            (-3, -3),
814            (3, 3),
815            (-3, -4),
816            (1, 4),
817        ];
818
819        let mut pointset = Vec::new();
820        for (x_coord, y_coord) in coords.iter() {
821            pointset.push(Point::new(*x_coord, *y_coord));
822        }
823
824        let poly_points = create_ymono_polygon(&pointset);
825        assert!(polygon_is_ymonotone(&poly_points));
826        assert!(!polygon_is_xmonotone(&poly_points));
827        assert!(polygon_is_anticlockwise(&poly_points));
828
829        let poly = Polygon::from_pointset(&poly_points);
830        assert_eq!(poly.signed_area_x2(), 102);
831        assert!(poly.is_anticlockwise());
832    }
833
834    #[test]
835    fn test_xmono_polygon() {
836        let coords = [
837            (-2, 2),
838            (0, -1),
839            (-5, 1),
840            (-2, 4),
841            (0, -4),
842            (-4, 3),
843            (-6, -2),
844            (5, 1),
845            (2, 2),
846            (3, -3),
847            (-3, -3),
848            (3, 3),
849            (-3, -4),
850            (1, 4),
851        ];
852
853        let mut pointset = Vec::new();
854        for (x_coord, y_coord) in coords.iter() {
855            pointset.push(Point::new(*x_coord, *y_coord));
856        }
857
858        let poly_points = create_xmono_polygon(&pointset);
859        assert!(polygon_is_xmonotone(&poly_points));
860        assert!(!polygon_is_ymonotone(&poly_points));
861        assert!(polygon_is_anticlockwise(&poly_points));
862
863        let poly = Polygon::from_pointset(&poly_points);
864        assert_eq!(poly.signed_area_x2(), 111);
865        assert!(poly.is_anticlockwise());
866    }
867
868    #[test]
869    fn test_is_rectilinear() {
870        // Create a rectilinear polygon
871        let rectilinear_coords = [(0, 0), (0, 1), (1, 1), (1, 0)];
872        let rectilinear_points: Vec<Point<i32, i32>> = rectilinear_coords
873            .iter()
874            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
875            .collect();
876        let rectilinear_polygon = Polygon::from_pointset(&rectilinear_points);
877        assert!(rectilinear_polygon.is_rectilinear());
878
879        // Create a non-rectilinear polygon
880        let non_rectilinear_coords = [(0, 0), (1, 1), (2, 0)];
881        let non_rectilinear_points: Vec<Point<i32, i32>> = non_rectilinear_coords
882            .iter()
883            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
884            .collect();
885        let non_rectilinear_polygon = Polygon::from_pointset(&non_rectilinear_points);
886        assert!(!non_rectilinear_polygon.is_rectilinear());
887    }
888
889    #[test]
890    fn test_is_convex() {
891        // Test case 1: Convex polygon
892        let convex_coords = [(0, 0), (2, 0), (2, 2), (0, 2)];
893        let convex_points: Vec<Point<i32, i32>> = convex_coords
894            .iter()
895            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
896            .collect();
897        let convex_polygon = Polygon::from_pointset(&convex_points);
898        assert!(convex_polygon.is_convex());
899
900        // Test case 2: Non-convex polygon
901        let non_convex_coords = [(0, 0), (2, 0), (1, 1), (2, 2), (0, 2)];
902        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
903            .iter()
904            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
905            .collect();
906        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
907        assert!(!non_convex_polygon.is_convex());
908
909        // Test case 3: Triangle (always convex)
910        let triangle_coords = [(0, 0), (2, 0), (1, 2)];
911        let triangle_points: Vec<Point<i32, i32>> = triangle_coords
912            .iter()
913            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
914            .collect();
915        let triangle = Polygon::from_pointset(&triangle_points);
916        assert!(triangle.is_convex());
917    }
918
919    #[test]
920    fn test_is_anticlockwise() {
921        // Clockwise polygon
922        let clockwise_coords = [(0, 0), (0, 1), (1, 1), (1, 0)];
923        let clockwise_points: Vec<Point<i32, i32>> = clockwise_coords
924            .iter()
925            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
926            .collect();
927        let clockwise_polygon = Polygon::from_pointset(&clockwise_points);
928        assert!(!clockwise_polygon.is_anticlockwise());
929
930        // Counter-clockwise polygon
931        let counter_clockwise_coords = [(0, 0), (1, 0), (1, 1), (0, 1)];
932        let counter_clockwise_points: Vec<Point<i32, i32>> = counter_clockwise_coords
933            .iter()
934            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
935            .collect();
936        let counter_clockwise_polygon = Polygon::from_pointset(&counter_clockwise_points);
937        assert!(counter_clockwise_polygon.is_anticlockwise());
938    }
939
940    #[test]
941    fn test_is_convex_clockwise() {
942        // Convex clockwise polygon
943        let convex_coords = [(0, 0), (0, 2), (2, 2), (2, 0)];
944        let convex_points: Vec<Point<i32, i32>> = convex_coords
945            .iter()
946            .map(|(x, y)| Point::new(*x, *y))
947            .collect();
948        let convex_polygon = Polygon::from_pointset(&convex_points);
949        assert!(convex_polygon.is_convex());
950
951        // Non-convex clockwise polygon
952        let non_convex_coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0)];
953        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
954            .iter()
955            .map(|(x, y)| Point::new(*x, *y))
956            .collect();
957        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
958        assert!(!non_convex_polygon.is_convex());
959    }
960
961    #[test]
962    fn test_point_in_polygon_missed_branches() {
963        let coords = [(0, 0), (10, 0), (10, 10), (0, 10)];
964        let pointset: Vec<Point<i32, i32>> = coords
965            .iter()
966            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
967            .collect();
968
969        // Test case where ptq.ycoord == pt0.ycoord
970        assert!(!point_in_polygon(&pointset, &Point::new(5, 10)));
971
972        // Test case where ptq.ycoord == pt1.ycoord
973        assert!(point_in_polygon(&pointset, &Point::new(5, 0)));
974
975        // Test case where det == 0 (point on edge)
976        assert!(point_in_polygon(&pointset, &Point::new(5, 0)));
977    }
978
979    #[test]
980    #[should_panic(expected = "Polygon must have at least 3 points")]
981    fn test_polygon_is_anticlockwise_less_than_3_points() {
982        let coords = [(0, 0), (0, 1)];
983        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
984        polygon_is_anticlockwise(&points);
985    }
986
987    #[test]
988    #[should_panic(expected = "Polygon must have at least 3 points")]
989    fn test_is_anticlockwise_less_than_3_points() {
990        let coords = [(0, 0), (0, 1)];
991        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
992        let polygon = Polygon::from_pointset(&points);
993        polygon.is_anticlockwise();
994    }
995
996    #[test]
997    fn test_is_convex_more() {
998        // Non-convex anti-clockwise polygon
999        let non_convex_coords = [(0, 0), (2, 0), (1, 1), (2, 2), (0, 2)];
1000        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
1001            .iter()
1002            .map(|(x, y)| Point::new(*x, *y))
1003            .collect();
1004        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
1005        assert!(!non_convex_polygon.is_convex());
1006
1007        // Convex anti-clockwise polygon
1008        let convex_coords = [(0, 0), (2, 0), (2, 2), (0, 2)];
1009        let convex_points: Vec<Point<i32, i32>> = convex_coords
1010            .iter()
1011            .map(|(x, y)| Point::new(*x, *y))
1012            .collect();
1013        let convex_polygon = Polygon::from_pointset(&convex_points);
1014        assert!(convex_polygon.is_convex());
1015    }
1016
1017    #[test]
1018    fn test_point_in_polygon_more() {
1019        // Create a polygon that will trigger the missed branches
1020        let coords = [(0, 0), (10, 5), (0, 10)];
1021        let pointset: Vec<Point<i32, i32>> = coords
1022            .iter()
1023            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
1024            .collect();
1025
1026        // This should trigger `det > 0`
1027        assert!(point_in_polygon(&pointset, &Point::new(1, 5)));
1028
1029        // Create a clockwise polygon to trigger `det < 0`
1030        let coords_cw = [(0, 0), (0, 10), (10, 5)];
1031        let pointset_cw: Vec<Point<i32, i32>> = coords_cw
1032            .iter()
1033            .map(|(x_coord, y_coord)| Point::new(*x_coord, *y_coord))
1034            .collect();
1035        assert!(point_in_polygon(&pointset_cw, &Point::new(1, 5)));
1036    }
1037
1038    #[test]
1039    fn test_is_rectilinear_non_rectilinear_last_edge() {
1040        // Last edge from last vec to origin is diagonal, not axis-aligned
1041        let coords = [(0, 0), (4, 0), (4, 4), (1, 3)];
1042        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1043        let poly = Polygon::from_pointset(&points);
1044        assert!(!poly.is_rectilinear());
1045    }
1046
1047    #[test]
1048    fn test_is_convex_less_than_two_vecs() {
1049        // Polygon with only 1 vec (2 vertices), is_convex should return false
1050        let origin = Point::new(0, 0);
1051        let vecs = vec![Vector2::new(4, 0)];
1052        let poly = Polygon::from_origin_and_vectors(origin, vecs);
1053        assert!(!poly.is_convex());
1054    }
1055
1056    #[test]
1057    fn test_bounding_box_branches() {
1058        // Polygon where y varies both decreasing and increasing to test min/max branches
1059        let coords = [(3, 5), (8, 2), (10, 7), (6, 9), (1, 4)];
1060        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1061        let poly = Polygon::from_pointset(&points);
1062        let (min_pt, max_pt) = poly.bounding_box();
1063        assert_eq!(min_pt, Point::new(1, 2));
1064        assert_eq!(max_pt, Point::new(10, 9));
1065    }
1066}
1067
1068#[test]
1069fn test_polygon_signed_area_x2() {
1070    // Test signed_area_x2 for different polygons
1071    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1072    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1073    let poly = Polygon::from_pointset(&pointset);
1074    assert_eq!(poly.signed_area_x2(), 24); // 2 * (4*3) = 24
1075}
1076
1077#[test]
1078fn test_polygon_vertices() {
1079    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1080    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1081    let poly = Polygon::from_pointset(&pointset);
1082    let vertices = poly.vertices();
1083    assert_eq!(vertices.len(), 4);
1084}
1085
1086#[test]
1087fn test_polygon_bounding_box() {
1088    let coords = [(1, 1), (5, 1), (5, 4), (1, 4)];
1089    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1090    let poly = Polygon::from_pointset(&pointset);
1091    let (min, max) = poly.bounding_box();
1092    assert_eq!(min, Point::new(1, 1));
1093    assert_eq!(max, Point::new(5, 4));
1094}
1095
1096#[test]
1097fn test_polygon_add_assign() {
1098    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1099    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1100    let mut poly = Polygon::from_pointset(&pointset);
1101    poly.add_assign(Vector2::new(1, 2));
1102    assert_eq!(poly.origin, Point::new(1, 2));
1103}
1104
1105#[test]
1106fn test_polygon_sub_assign() {
1107    let coords = [(1, 2), (5, 2), (5, 5), (1, 5)];
1108    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1109    let mut poly = Polygon::from_pointset(&pointset);
1110    poly.sub_assign(Vector2::new(1, 2));
1111    assert_eq!(poly.origin, Point::new(0, 0));
1112}
1113
1114#[test]
1115fn test_polygon_partial_eq() {
1116    let coords = [(0, 0), (4, 0), (4, 3), (0, 3)];
1117    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1118    let poly1 = Polygon::from_pointset(&pointset);
1119    let poly2 = Polygon::from_pointset(&pointset);
1120    assert_eq!(poly1, poly2);
1121
1122    let coords2 = [(0, 0), (5, 0), (5, 3), (0, 3)];
1123    let pointset2: Vec<Point<i32, i32>> = coords2.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1124    let poly3 = Polygon::from_pointset(&pointset2);
1125    assert_ne!(poly1, poly3);
1126}
1127
1128#[test]
1129fn test_polygon_from_origin_and_vectors() {
1130    let origin = Point::new(0, 0);
1131    let vecs = vec![Vector2::new(4, 0), Vector2::new(0, 3), Vector2::new(-4, 0)];
1132    let poly = Polygon::from_origin_and_vectors(origin, vecs);
1133    assert_eq!(poly.origin, Point::new(0, 0));
1134    assert_eq!(poly.vecs.len(), 3);
1135}
1136
1137#[test]
1138fn test_polygon_default() {
1139    let poly: Polygon<i32> = Polygon::default();
1140    assert_eq!(poly.origin, Point::new(0, 0));
1141}
1142
1143#[test]
1144fn test_polygon_is_monotone_custom() {
1145    // Test polygon_is_monotone with custom direction
1146    let coords = [(0, 0), (1, 0), (2, 1), (1, 2), (0, 2)];
1147    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1148    // x-monotone
1149    assert!(polygon_is_monotone(&pointset, |pt| (pt.xcoord, pt.ycoord)));
1150}
1151
1152#[test]
1153fn test_create_mono_polygon_custom() {
1154    // Test create_mono_polygon with custom function
1155    let coords = [(0, 0), (2, 1), (1, 2), (3, 2)];
1156    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1157    let result = create_mono_polygon(&pointset, |pt| (pt.xcoord, pt.ycoord));
1158    assert!(!result.is_empty());
1159}
1160
1161#[test]
1162fn test_polygon_empty_vecs_is_rectilinear() {
1163    // Edge case: polygon with no vecs should be rectilinear
1164    let origin = Point::new(0, 0);
1165    let poly = Polygon::from_origin_and_vectors(origin, vec![]);
1166    assert!(poly.is_rectilinear());
1167}
1168
1169#[test]
1170fn test_polygon_signed_area_x2_triangle() {
1171    // Triangle area calculation
1172    let coords = [(0, 0), (3, 0), (0, 4)];
1173    let pointset: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1174    let poly = Polygon::from_pointset(&pointset);
1175    // Area = 0.5 * |0*(0-4) + 3*(4-0) + 0*(0-0)| = 0.5 * 12 = 6
1176    // signed_area_x2 = 2 * area = 12
1177    assert_eq!(poly.signed_area_x2(), 12);
1178}
1179
1180#[test]
1181fn test_polygon_signed_area_x2_single_vec() {
1182    // Polygon with just 1 vec (2 vertices), signed_area_x2 should return 0 (n < 2)
1183    let origin = Point::new(0, 0);
1184    let vecs = vec![Vector2::new(4, 0)];
1185    let poly = Polygon::from_origin_and_vectors(origin, vecs);
1186    assert_eq!(poly.signed_area_x2(), 0);
1187}
1188
1189#[test]
1190fn test_polygon_area_multi_vertex() {
1191    // Pentagon to exercise the area() calculation loop body
1192    let coords = [(0, 0), (4, 0), (5, 3), (2, 5), (-1, 2)];
1193    let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
1194    let poly = Polygon::from_pointset(&points);
1195    // area is signed, should be nonzero for a valid polygon
1196    assert_ne!(poly.area(), 0);
1197}