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/// Properties:
17///
18/// * `origin`: The origin point of the polygon
19/// * `vecs`: Vector of displacement vectors from origin to other vertices
20#[derive(Eq, Clone, Debug, Default)]
21pub struct Polygon<T> {
22    pub origin: Point<T, T>,
23    pub vecs: Vec<Vector2<T, T>>,
24}
25
26impl<T: Clone + Num + Ord + Copy + std::ops::AddAssign> Polygon<T> {
27    /// Constructs a new Polygon from a set of points
28    ///
29    /// The first point in the slice is used as the origin, and the remaining points
30    /// are used to construct displacement vectors relative to the origin.
31    ///
32    /// # Arguments
33    ///
34    /// * `coords` - A slice of points representing the vertices of the polygon
35    ///
36    /// # Examples
37    ///
38    /// ```
39    /// use physdes::point::Point;
40    /// use physdes::polygon::Polygon;
41    /// use physdes::vector2::Vector2;
42    ///
43    /// let p1 = Point::new(1, 1);
44    /// let p2 = Point::new(2, 2);
45    /// let p3 = Point::new(3, 3);
46    /// let p4 = Point::new(4, 4);
47    /// let p5 = Point::new(5, 5);
48    /// let poly = Polygon::new(&[p1, p2, p3, p4, p5]);
49    /// assert_eq!(poly.origin, Point::new(1, 1));
50    /// assert_eq!(poly.vecs.len(), 4);
51    /// assert_eq!(poly.vecs[0], Vector2::new(1, 1));
52    /// ```
53    pub fn new(coords: &[Point<T, T>]) -> Self {
54        let (&origin, coords) = coords.split_first().unwrap();
55        let vecs = coords.iter().map(|pt| *pt - origin).collect();
56        Polygon { origin, vecs }
57    }
58
59    /// Constructs a new Polygon from origin and displacement vectors
60    ///
61    /// # Arguments
62    ///
63    /// * `origin` - The origin point of the polygon
64    /// * `vecs` - Vector of displacement vectors from origin
65    pub fn from_origin_and_vectors(origin: Point<T, T>, vecs: Vec<Vector2<T, T>>) -> Self {
66        Polygon { origin, vecs }
67    }
68
69    /// Constructs a new Polygon from a point set
70    ///
71    /// The first point in the set is used as the origin, and the remaining points
72    /// are used to construct displacement vectors relative to the origin.
73    pub fn from_pointset(pointset: &[Point<T, T>]) -> Self {
74        Self::new(pointset)
75    }
76
77    /// Translates the polygon by adding a vector to its origin
78    pub fn add_assign(&mut self, rhs: Vector2<T, T>)
79    where
80        T: AddAssign,
81    {
82        self.origin += rhs;
83    }
84
85    /// Translates the polygon by subtracting a vector from its origin
86    pub fn sub_assign(&mut self, rhs: Vector2<T, T>)
87    where
88        T: SubAssign,
89    {
90        self.origin -= rhs;
91    }
92
93    /// Calculates the signed area of the polygon multiplied by 2
94    ///
95    /// This function calculates the signed area by summing the cross products
96    /// of adjacent edges. The result is multiplied by 2 to avoid the need for
97    /// floating-point arithmetic.
98    ///
99    /// # Returns
100    ///
101    /// The signed area of the polygon multiplied by 2
102    ///
103    /// # Examples
104    ///
105    /// ```
106    /// use physdes::point::Point;
107    /// use physdes::polygon::Polygon;
108    /// use physdes::vector2::Vector2;
109    ///
110    /// let p1 = Point::new(1, 1);
111    /// let p2 = Point::new(2, 2);
112    /// let p3 = Point::new(3, 3);
113    /// let p4 = Point::new(4, 4);
114    /// let p5 = Point::new(5, 5);
115    /// let poly = Polygon::new(&[p1, p2, p3, p4, p5]);
116    /// assert_eq!(poly.signed_area_x2(), 0);
117    /// ```
118    pub fn signed_area_x2(&self) -> T {
119        let vecs = &self.vecs;
120        let n = vecs.len();
121        if n < 2 {
122            return T::zero();
123        }
124
125        let mut itr = vecs.iter();
126        let vec0 = itr.next().unwrap();
127        let vec1 = itr.next().unwrap();
128        let mut res = vec0.x_ * vec1.y_ - vecs[n - 1].x_ * vecs[n - 2].y_;
129
130        let mut vec0 = vec0;
131        let mut vec1 = vec1;
132
133        for vec2 in itr {
134            res += vec1.x_ * (vec2.y_ - vec0.y_);
135            vec0 = vec1;
136            vec1 = vec2;
137        }
138
139        res
140    }
141
142    /// Gets all vertices of the polygon as points
143    pub fn vertices(&self) -> Vec<Point<T, T>> {
144        let mut result = Vec::with_capacity(self.vecs.len() + 1);
145        result.push(self.origin);
146
147        for vec in &self.vecs {
148            result.push(self.origin + *vec);
149        }
150
151        result
152    }
153
154    /// Checks if the polygon is rectilinear
155    ///
156    /// A polygon is rectilinear if all its edges are either horizontal or vertical.
157    ///
158    /// # Returns
159    ///
160    /// `true` if the polygon is rectilinear, `false` otherwise
161    ///
162    /// # Examples
163    ///
164    /// ```
165    /// use physdes::point::Point;
166    /// use physdes::polygon::Polygon;
167    ///
168    /// let p1 = Point::new(0, 0);
169    /// let p2 = Point::new(0, 1);
170    /// let p3 = Point::new(1, 1);
171    /// let p4 = Point::new(1, 0);
172    /// let poly = Polygon::new(&[p1, p2, p3, p4]);
173    /// assert!(poly.is_rectilinear());
174    ///
175    /// let p5 = Point::new(0, 0);
176    /// let p6 = Point::new(1, 1);
177    /// let p7 = Point::new(0, 2);
178    /// let poly2 = Polygon::new(&[p5, p6, p7]);
179    /// assert!(!poly2.is_rectilinear());
180    /// ```
181    pub fn is_rectilinear(&self) -> bool {
182        if self.vecs.is_empty() {
183            return true;
184        }
185
186        // Check from origin to vecs[0]
187        if self.vecs[0].x_ != T::zero() && self.vecs[0].y_ != T::zero() {
188            return false;
189        }
190
191        // Check between vecs
192        for i in 0..self.vecs.len() - 1 {
193            let v1 = self.vecs[i];
194            let v2 = self.vecs[i + 1];
195            if v1.x_ != v2.x_ && v1.y_ != v2.y_ {
196                return false;
197            }
198        }
199
200        // Check from vecs[-1] to origin
201        let last_vec = self.vecs.last().unwrap();
202        last_vec.x_ == T::zero() || last_vec.y_ == T::zero()
203    }
204
205    /// Checks if the polygon is oriented anticlockwise
206    pub fn is_anticlockwise(&self) -> bool
207    where
208        T: PartialOrd,
209    {
210        let mut pointset = Vec::with_capacity(self.vecs.len() + 1);
211        pointset.push(Vector2::new(T::zero(), T::zero()));
212        pointset.extend(self.vecs.iter().cloned());
213
214        if pointset.len() < 3 {
215            panic!("Polygon must have at least 3 points");
216        }
217
218        // Find the point with minimum coordinates
219        let (min_index, _) = pointset
220            .iter()
221            .enumerate()
222            .min_by(|(_, a), (_, b)| {
223                a.x_.partial_cmp(&b.x_)
224                    .unwrap_or(Ordering::Equal)
225                    .then(a.y_.partial_cmp(&b.y_).unwrap_or(Ordering::Equal))
226            })
227            .unwrap();
228
229        // Get previous and next points with wrap-around
230        let n = pointset.len();
231        let prev_point = pointset[(min_index + n - 1) % n];
232        let current_point = pointset[min_index];
233        let next_point = pointset[(min_index + 1) % n];
234
235        // Calculate cross product
236        (current_point - prev_point).cross(&(next_point - current_point)) > T::zero()
237    }
238
239    /// Checks if the polygon is convex
240    pub fn is_convex(&self) -> bool
241    where
242        T: PartialOrd,
243    {
244        let n = self.vecs.len() + 1;
245        if n < 3 {
246            return false;
247        }
248        if n == 3 {
249            return true;
250        }
251
252        let is_anticlockwise = self.is_anticlockwise();
253
254        // Create extended pointset for easier edge traversal
255        let mut pointset = Vec::with_capacity(n + 2);
256        pointset.push(*self.vecs.last().unwrap());
257        pointset.push(Vector2::new(T::zero(), T::zero()));
258        pointset.extend(self.vecs.iter().cloned());
259        pointset.push(Vector2::new(T::zero(), T::zero()));
260
261        if is_anticlockwise {
262            for i in 1..pointset.len() - 1 {
263                let v1 = pointset[i] - pointset[i - 1];
264                let v2 = pointset[i + 1] - pointset[i];
265                if v1.cross(&v2) < T::zero() {
266                    return false;
267                }
268            }
269        } else {
270            for i in 1..pointset.len() - 1 {
271                let v1 = pointset[i] - pointset[i - 1];
272                let v2 = pointset[i + 1] - pointset[i];
273                if v1.cross(&v2) > T::zero() {
274                    return false;
275                }
276            }
277        }
278
279        true
280    }
281
282    /// Gets the bounding box of the polygon
283    pub fn bounding_box(&self) -> (Point<T, T>, Point<T, T>) {
284        let mut min_x = T::zero();
285        let mut min_y = T::zero();
286        let mut max_x = T::zero();
287        let mut max_y = T::zero();
288
289        for vec in &self.vecs {
290            if vec.x_ < min_x {
291                min_x = vec.x_;
292            }
293            if vec.y_ < min_y {
294                min_y = vec.y_;
295            }
296            if vec.x_ > max_x {
297                max_x = vec.x_;
298            }
299            if vec.y_ > max_y {
300                max_y = vec.y_;
301            }
302        }
303
304        (
305            Point::new(self.origin.xcoord + min_x, self.origin.ycoord + min_y),
306            Point::new(self.origin.xcoord + max_x, self.origin.ycoord + max_y),
307        )
308    }
309}
310
311/// Creates a monotone polygon from a set of points using a custom comparison function
312///
313/// # Arguments
314///
315/// * `pointset` - A slice of points to create the polygon from
316/// * `f` - A closure that defines the ordering of points
317pub fn create_mono_polygon<T, F>(pointset: &[Point<T, T>], f: F) -> Vec<Point<T, T>>
318where
319    T: Clone + Num + Ord + Copy + PartialOrd,
320    F: Fn(&Point<T, T>) -> (T, T),
321{
322    let max_pt = pointset
323        .iter()
324        .max_by(|a, b| f(a).partial_cmp(&f(b)).unwrap())
325        .unwrap();
326    let min_pt = pointset
327        .iter()
328        .min_by(|a, b| f(a).partial_cmp(&f(b)).unwrap())
329        .unwrap();
330    let d = *max_pt - *min_pt;
331
332    let (mut lst1, mut lst2): (Vec<Point<T, T>>, Vec<Point<T, T>>) = pointset
333        .iter()
334        .partition(|&a| d.cross(&(*a - *min_pt)) <= T::zero());
335
336    lst1.sort_by_key(|a| f(a));
337    lst2.sort_by_key(|a| f(a));
338    lst2.reverse();
339    lst1.append(&mut lst2);
340    lst1
341}
342
343/// Creates an x-monotone polygon from a set of points
344///
345/// Points are ordered primarily by x-coordinate, secondarily by y-coordinate
346#[inline]
347pub fn create_xmono_polygon<T>(pointset: &[Point<T, T>]) -> Vec<Point<T, T>>
348where
349    T: Clone + Num + Ord + Copy + PartialOrd,
350{
351    create_mono_polygon(pointset, |a| (a.xcoord, a.ycoord))
352}
353
354/// Creates a y-monotone polygon from a set of points
355///
356/// Points are ordered primarily by y-coordinate, secondarily by x-coordinate
357#[inline]
358pub fn create_ymono_polygon<T>(pointset: &[Point<T, T>]) -> Vec<Point<T, T>>
359where
360    T: Clone + Num + Ord + Copy + PartialOrd,
361{
362    create_mono_polygon(pointset, |a| (a.ycoord, a.xcoord))
363}
364
365/// Checks if a polygon is monotone in a given direction
366pub fn polygon_is_monotone<T, F>(lst: &[Point<T, T>], dir: F) -> bool
367where
368    T: Clone + Num + Ord + Copy + PartialOrd,
369    F: Fn(&Point<T, T>) -> (T, T),
370{
371    if lst.len() <= 3 {
372        return true;
373    }
374
375    let (min_index, _) = lst
376        .iter()
377        .enumerate()
378        .min_by(|(_, a), (_, b)| dir(a).partial_cmp(&dir(b)).unwrap())
379        .unwrap();
380
381    let (max_index, _) = lst
382        .iter()
383        .enumerate()
384        .max_by(|(_, a), (_, b)| dir(a).partial_cmp(&dir(b)).unwrap())
385        .unwrap();
386
387    let n = lst.len();
388
389    // Chain from min to max
390    let mut i = min_index;
391    while i != max_index {
392        let next_i = (i + 1) % n;
393        if dir(&lst[i]).0 > dir(&lst[next_i]).0 {
394            return false;
395        }
396        i = next_i;
397    }
398
399    // Chain from max to min
400    let mut i = max_index;
401    while i != min_index {
402        let next_i = (i + 1) % n;
403        if dir(&lst[i]).0 < dir(&lst[next_i]).0 {
404            return false;
405        }
406        i = next_i;
407    }
408
409    true
410}
411
412/// Checks if a polygon is x-monotone
413pub fn polygon_is_xmonotone<T>(lst: &[Point<T, T>]) -> bool
414where
415    T: Clone + Num + Ord + Copy + PartialOrd,
416{
417    polygon_is_monotone(lst, |pt| (pt.xcoord, pt.ycoord))
418}
419
420/// Checks if a polygon is y-monotone
421pub fn polygon_is_ymonotone<T>(lst: &[Point<T, T>]) -> bool
422where
423    T: Clone + Num + Ord + Copy + PartialOrd,
424{
425    polygon_is_monotone(lst, |pt| (pt.ycoord, pt.xcoord))
426}
427
428/// Determines if a point is inside a polygon using the winding number algorithm
429pub fn point_in_polygon<T>(pointset: &[Point<T, T>], ptq: &Point<T, T>) -> bool
430where
431    T: Clone + Num + Ord + Copy + PartialOrd,
432{
433    let n = pointset.len();
434    if n == 0 {
435        return false;
436    }
437
438    let mut pt0 = &pointset[n - 1];
439    let mut res = false;
440
441    for pt1 in pointset.iter() {
442        if (pt1.ycoord <= ptq.ycoord && ptq.ycoord < pt0.ycoord)
443            || (pt0.ycoord <= ptq.ycoord && ptq.ycoord < pt1.ycoord)
444        {
445            let det = (*ptq - *pt0).cross(&(*pt1 - *pt0));
446            if pt1.ycoord > pt0.ycoord {
447                if det < T::zero() {
448                    res = !res;
449                }
450            } else if det > T::zero() {
451                res = !res;
452            }
453        }
454        pt0 = pt1;
455    }
456
457    res
458}
459
460/// Determines if a polygon represented by points is oriented anticlockwise
461pub fn polygon_is_anticlockwise<T>(pointset: &[Point<T, T>]) -> bool
462where
463    T: Clone + Num + Ord + Copy + PartialOrd,
464{
465    if pointset.len() < 3 {
466        panic!("Polygon must have at least 3 points");
467    }
468
469    // Find the point with minimum coordinates
470    let (min_index, _) = pointset
471        .iter()
472        .enumerate()
473        .min_by(|(_, a), (_, b)| {
474            a.xcoord
475                .partial_cmp(&b.xcoord)
476                .unwrap_or(Ordering::Equal)
477                .then(a.ycoord.partial_cmp(&b.ycoord).unwrap_or(Ordering::Equal))
478        })
479        .unwrap();
480
481    // Get previous and next points with wrap-around
482    let n = pointset.len();
483    let prev_point = pointset[(min_index + n - 1) % n];
484    let current_point = pointset[min_index];
485    let next_point = pointset[(min_index + 1) % n];
486
487    // Calculate cross product
488    (current_point - prev_point).cross(&(next_point - current_point)) > T::zero()
489}
490
491// Implement PartialEq for Polygon
492impl<T: PartialEq> PartialEq for Polygon<T> {
493    fn eq(&self, other: &Self) -> bool {
494        self.origin == other.origin && self.vecs == other.vecs
495    }
496}
497
498// Implement AddAssign and SubAssign for Polygon
499impl<T: AddAssign + Clone + Num> AddAssign<Vector2<T, T>> for Polygon<T> {
500    fn add_assign(&mut self, rhs: Vector2<T, T>) {
501        self.origin += rhs;
502    }
503}
504
505impl<T: SubAssign + Clone + Num> SubAssign<Vector2<T, T>> for Polygon<T> {
506    fn sub_assign(&mut self, rhs: Vector2<T, T>) {
507        self.origin -= rhs;
508    }
509}
510
511#[cfg(test)]
512mod tests {
513    use super::*;
514    use crate::point::Point;
515    use crate::vector2::Vector2;
516
517    #[test]
518    fn test_polygon() {
519        let coords = [
520            (-2, 2),
521            (0, -1),
522            (-5, 1),
523            (-2, 4),
524            (0, -4),
525            (-4, 3),
526            (-6, -2),
527            (5, 1),
528            (2, 2),
529            (3, -3),
530            (-3, -3),
531            (3, 3),
532            (-3, -4),
533            (1, 4),
534        ];
535
536        let mut pointset = Vec::new();
537        for (x, y) in coords.iter() {
538            pointset.push(Point::new(*x, *y));
539        }
540
541        let s = create_xmono_polygon(&pointset);
542        assert!(polygon_is_anticlockwise(&s));
543
544        let p = Polygon::from_pointset(&s);
545        let mut q = Polygon::from_pointset(&s);
546        q.add_assign(Vector2::new(4, 5));
547        q.sub_assign(Vector2::new(4, 5));
548        assert_eq!(q, p);
549    }
550
551    #[test]
552    fn test_ymono_polygon() {
553        let coords = [
554            (-2, 2),
555            (0, -1),
556            (-5, 1),
557            (-2, 4),
558            (0, -4),
559            (-4, 3),
560            (-6, -2),
561            (5, 1),
562            (2, 2),
563            (3, -3),
564            (-3, -3),
565            (3, 3),
566            (-3, -4),
567            (1, 4),
568        ];
569
570        let mut pointset = Vec::new();
571        for (x, y) in coords.iter() {
572            pointset.push(Point::new(*x, *y));
573        }
574
575        let s = create_ymono_polygon(&pointset);
576        assert!(polygon_is_ymonotone(&s));
577        assert!(!polygon_is_xmonotone(&s));
578        assert!(polygon_is_anticlockwise(&s));
579
580        let p = Polygon::from_pointset(&s);
581        assert_eq!(p.signed_area_x2(), 102);
582        assert!(p.is_anticlockwise());
583    }
584
585    #[test]
586    fn test_xmono_polygon() {
587        let coords = [
588            (-2, 2),
589            (0, -1),
590            (-5, 1),
591            (-2, 4),
592            (0, -4),
593            (-4, 3),
594            (-6, -2),
595            (5, 1),
596            (2, 2),
597            (3, -3),
598            (-3, -3),
599            (3, 3),
600            (-3, -4),
601            (1, 4),
602        ];
603
604        let mut pointset = Vec::new();
605        for (x, y) in coords.iter() {
606            pointset.push(Point::new(*x, *y));
607        }
608
609        let s = create_xmono_polygon(&pointset);
610        assert!(polygon_is_xmonotone(&s));
611        assert!(!polygon_is_ymonotone(&s));
612        assert!(polygon_is_anticlockwise(&s));
613
614        let p = Polygon::from_pointset(&s);
615        assert_eq!(p.signed_area_x2(), 111);
616        assert!(p.is_anticlockwise());
617    }
618
619    #[test]
620    fn test_is_rectilinear() {
621        // Create a rectilinear polygon
622        let rectilinear_coords = [(0, 0), (0, 1), (1, 1), (1, 0)];
623        let rectilinear_points: Vec<Point<i32, i32>> = rectilinear_coords
624            .iter()
625            .map(|(x, y)| Point::new(*x, *y))
626            .collect();
627        let rectilinear_polygon = Polygon::from_pointset(&rectilinear_points);
628        assert!(rectilinear_polygon.is_rectilinear());
629
630        // Create a non-rectilinear polygon
631        let non_rectilinear_coords = [(0, 0), (1, 1), (2, 0)];
632        let non_rectilinear_points: Vec<Point<i32, i32>> = non_rectilinear_coords
633            .iter()
634            .map(|(x, y)| Point::new(*x, *y))
635            .collect();
636        let non_rectilinear_polygon = Polygon::from_pointset(&non_rectilinear_points);
637        assert!(!non_rectilinear_polygon.is_rectilinear());
638    }
639
640    #[test]
641    fn test_is_convex() {
642        // Test case 1: Convex polygon
643        let convex_coords = [(0, 0), (2, 0), (2, 2), (0, 2)];
644        let convex_points: Vec<Point<i32, i32>> = convex_coords
645            .iter()
646            .map(|(x, y)| Point::new(*x, *y))
647            .collect();
648        let convex_polygon = Polygon::from_pointset(&convex_points);
649        assert!(convex_polygon.is_convex());
650
651        // Test case 2: Non-convex polygon
652        let non_convex_coords = [(0, 0), (2, 0), (1, 1), (2, 2), (0, 2)];
653        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
654            .iter()
655            .map(|(x, y)| Point::new(*x, *y))
656            .collect();
657        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
658        assert!(!non_convex_polygon.is_convex());
659
660        // Test case 3: Triangle (always convex)
661        let triangle_coords = [(0, 0), (2, 0), (1, 2)];
662        let triangle_points: Vec<Point<i32, i32>> = triangle_coords
663            .iter()
664            .map(|(x, y)| Point::new(*x, *y))
665            .collect();
666        let triangle = Polygon::from_pointset(&triangle_points);
667        assert!(triangle.is_convex());
668    }
669
670    #[test]
671    fn test_is_anticlockwise() {
672        // Clockwise polygon
673        let clockwise_coords = [(0, 0), (0, 1), (1, 1), (1, 0)];
674        let clockwise_points: Vec<Point<i32, i32>> = clockwise_coords
675            .iter()
676            .map(|(x, y)| Point::new(*x, *y))
677            .collect();
678        let clockwise_polygon = Polygon::from_pointset(&clockwise_points);
679        assert!(!clockwise_polygon.is_anticlockwise());
680
681        // Counter-clockwise polygon
682        let counter_clockwise_coords = [(0, 0), (1, 0), (1, 1), (0, 1)];
683        let counter_clockwise_points: Vec<Point<i32, i32>> = counter_clockwise_coords
684            .iter()
685            .map(|(x, y)| Point::new(*x, *y))
686            .collect();
687        let counter_clockwise_polygon = Polygon::from_pointset(&counter_clockwise_points);
688        assert!(counter_clockwise_polygon.is_anticlockwise());
689    }
690
691    #[test]
692    fn test_is_convex_clockwise() {
693        // Convex clockwise polygon
694        let convex_coords = [(0, 0), (0, 2), (2, 2), (2, 0)];
695        let convex_points: Vec<Point<i32, i32>> = convex_coords
696            .iter()
697            .map(|(x, y)| Point::new(*x, *y))
698            .collect();
699        let convex_polygon = Polygon::from_pointset(&convex_points);
700        assert!(convex_polygon.is_convex());
701
702        // Non-convex clockwise polygon
703        let non_convex_coords = [(0, 0), (0, 2), (1, 1), (2, 2), (2, 0)];
704        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
705            .iter()
706            .map(|(x, y)| Point::new(*x, *y))
707            .collect();
708        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
709        assert!(!non_convex_polygon.is_convex());
710    }
711
712    #[test]
713    fn test_point_in_polygon_missed_branches() {
714        let coords = [(0, 0), (10, 0), (10, 10), (0, 10)];
715        let pointset: Vec<Point<i32, i32>> =
716            coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
717
718        // Test case where ptq.ycoord == pt0.ycoord
719        assert!(!point_in_polygon(&pointset, &Point::new(5, 10)));
720
721        // Test case where ptq.ycoord == pt1.ycoord
722        assert!(point_in_polygon(&pointset, &Point::new(5, 0)));
723
724        // Test case where det == 0 (point on edge)
725        assert!(point_in_polygon(&pointset, &Point::new(5, 0)));
726    }
727
728    #[test]
729    #[should_panic(expected = "Polygon must have at least 3 points")]
730    fn test_polygon_is_anticlockwise_less_than_3_points() {
731        let coords = [(0, 0), (0, 1)];
732        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
733        polygon_is_anticlockwise(&points);
734    }
735
736    #[test]
737    #[should_panic(expected = "Polygon must have at least 3 points")]
738    fn test_is_anticlockwise_less_than_3_points() {
739        let coords = [(0, 0), (0, 1)];
740        let points: Vec<Point<i32, i32>> = coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
741        let polygon = Polygon::from_pointset(&points);
742        polygon.is_anticlockwise();
743    }
744
745    #[test]
746    fn test_is_convex_more() {
747        // Non-convex anti-clockwise polygon
748        let non_convex_coords = [(0, 0), (2, 0), (1, 1), (2, 2), (0, 2)];
749        let non_convex_points: Vec<Point<i32, i32>> = non_convex_coords
750            .iter()
751            .map(|(x, y)| Point::new(*x, *y))
752            .collect();
753        let non_convex_polygon = Polygon::from_pointset(&non_convex_points);
754        assert!(!non_convex_polygon.is_convex());
755
756        // Convex anti-clockwise polygon
757        let convex_coords = [(0, 0), (2, 0), (2, 2), (0, 2)];
758        let convex_points: Vec<Point<i32, i32>> = convex_coords
759            .iter()
760            .map(|(x, y)| Point::new(*x, *y))
761            .collect();
762        let convex_polygon = Polygon::from_pointset(&convex_points);
763        assert!(convex_polygon.is_convex());
764    }
765
766    #[test]
767    fn test_point_in_polygon_more() {
768        // Create a polygon that will trigger the missed branches
769        let coords = [(0, 0), (10, 5), (0, 10)];
770        let pointset: Vec<Point<i32, i32>> =
771            coords.iter().map(|(x, y)| Point::new(*x, *y)).collect();
772
773        // This should trigger `det > 0`
774        assert!(point_in_polygon(&pointset, &Point::new(1, 5)));
775
776        // Create a clockwise polygon to trigger `det < 0`
777        let coords_cw = [(0, 0), (0, 10), (10, 5)];
778        let pointset_cw: Vec<Point<i32, i32>> =
779            coords_cw.iter().map(|(x, y)| Point::new(*x, *y)).collect();
780        assert!(point_in_polygon(&pointset_cw, &Point::new(1, 5)));
781    }
782}