graphics_shapes/
polygon.rs

1use crate::prelude::*;
2use crate::shape_box::ShapeBox;
3#[cfg(feature = "serde")]
4use serde::{Deserialize, Serialize};
5
6/// Shape with any number of points/line
7#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
8#[derive(Debug, Clone, PartialEq)]
9pub struct Polygon {
10    points: Vec<Coord>,
11    fpoints: Vec<(f32, f32)>,
12    is_regular: bool,
13    center: Coord,
14    is_convex: bool,
15}
16
17impl IntersectsContains for Polygon {}
18
19impl Polygon {
20    #[must_use]
21    pub fn new<'a, P: Into<Coord>>(points: &'a [P]) -> Self
22    where
23        Coord: From<&'a P>,
24    {
25        let points: Vec<Coord> = points.iter().map(|p| p.into()).collect();
26        let fpoints = points.iter().map(|p| (p.x as f32, p.y as f32)).collect();
27        let is_convex = is_convex(&points);
28        let mut poly = Self {
29            points: points.clone(),
30            fpoints,
31            center: Coord::default(),
32            is_regular: false,
33            is_convex,
34        };
35        poly.center = poly.top_left().mid_point(poly.bottom_right());
36        let dists: Vec<usize> = points.iter().map(|p| p.distance(poly.center)).collect();
37        poly.is_regular = dists.iter().all(|dist| dist == &dists[0]);
38        poly
39    }
40}
41
42impl Polygon {
43    #[inline]
44    #[must_use]
45    pub fn fpoints(&self) -> &Vec<(f32, f32)> {
46        &self.fpoints
47    }
48
49    /// Returns true if all sides are the same length
50    #[inline]
51    #[must_use]
52    pub fn is_regular(&self) -> bool {
53        self.is_regular
54    }
55
56    /// Returns the closest corner point to the center
57    #[must_use]
58    pub fn point_closest_to_center(&self) -> Coord {
59        let mut list = self.points.clone();
60        list.sort_by_key(|p| p.distance(self.center));
61        list[0]
62    }
63
64    /// Returns the furthest corner point to the center
65    #[must_use]
66    pub fn point_farthest_from_center(&self) -> Coord {
67        let mut list = self.points.clone();
68        list.sort_by_key(|p| p.distance(self.center));
69        *list.last().unwrap()
70    }
71
72    /// Returns true if any sides point inwards
73    #[inline]
74    #[must_use]
75    pub fn is_convex(&self) -> bool {
76        self.is_convex
77    }
78}
79
80impl Shape for Polygon {
81    fn from_points(points: &[Coord]) -> Self
82    where
83        Self: Sized,
84    {
85        Polygon::new(points)
86    }
87
88    fn rebuild(&self, points: &[Coord]) -> Self
89    where
90        Self: Sized,
91    {
92        Polygon::from_points(points)
93    }
94
95    fn contains(&self, point: Coord) -> bool {
96        let mut j = self.fpoints.len() - 1;
97        let mut odd_number_of_nodes = false;
98        let fpoint = (point.x as f32, point.y as f32);
99
100        for i in 0..self.fpoints.len() {
101            if (self.fpoints[i].1 < fpoint.1 && self.fpoints[j].1 >= fpoint.1
102                || self.fpoints[j].1 < fpoint.1 && self.fpoints[i].1 >= fpoint.1)
103                && (self.fpoints[i].0 <= fpoint.0 || self.fpoints[j].0 <= fpoint.0)
104            {
105                odd_number_of_nodes ^= self.fpoints[i].0
106                    + (fpoint.1 - self.fpoints[i].1) / (self.fpoints[j].1 - self.fpoints[i].1)
107                        * (self.fpoints[j].0 - self.fpoints[i].0)
108                    < fpoint.0;
109            }
110            j = i;
111        }
112
113        odd_number_of_nodes
114    }
115
116    fn points(&self) -> Vec<Coord> {
117        self.points.clone()
118    }
119
120    #[inline]
121    fn center(&self) -> Coord {
122        self.center
123    }
124
125    fn outline_pixels(&self) -> Vec<Coord> {
126        self.as_lines()
127            .iter()
128            .flat_map(|line| line.outline_pixels())
129            .collect()
130    }
131
132    fn filled_pixels(&self) -> Vec<Coord> {
133        let mut output = vec![];
134        let poly: Vec<(f32, f32)> = self
135            .points
136            .iter()
137            .map(|c| (c.x as f32, c.y as f32))
138            .collect();
139        let y_start = self.top();
140        let y_end = self.bottom();
141        for y in y_start..y_end {
142            let mut node = vec![];
143            let mut node_count = 0;
144            let y = y as f32;
145            let mut j = poly.len() - 1;
146            for i in 0..poly.len() {
147                if poly[i].1 < y && poly[j].1 >= y || poly[j].1 < y && poly[i].1 >= y {
148                    node.push(
149                        poly[i].0
150                            + (y - poly[i].1) / (poly[j].1 - poly[i].1) * (poly[j].0 - poly[i].0),
151                    );
152                    node_count += 1;
153                }
154                j = i;
155            }
156            let mut i = 0;
157            if node_count > 0 {
158                while i < (node_count - 1) {
159                    if node[i] > node[i + 1] {
160                        node.swap(i, i + 1);
161                        i = i.saturating_sub(1);
162                    } else {
163                        i += 1;
164                    }
165                }
166                for i in (0..node_count - 1).step_by(2) {
167                    for x in (node[i] as isize)..(node[i + 1] as isize) {
168                        output.push(coord!(x + 1, y as isize));
169                    }
170                }
171            }
172        }
173        output
174    }
175
176    fn to_shape_box(&self) -> ShapeBox {
177        ShapeBox::Polygon(self.clone())
178    }
179}
180
181impl Polygon {
182    /// Creates a circle using the point closest to the center
183    #[must_use]
184    pub fn as_inner_circle(&self) -> Circle {
185        Circle::from_points(&[self.center, self.point_closest_to_center()])
186    }
187
188    /// Creates a circle using the point farthest to the center
189    #[must_use]
190    pub fn as_outer_circle(&self) -> Circle {
191        Circle::from_points(&[self.center, self.point_farthest_from_center()])
192    }
193
194    /// Creates a circle using the average point distance from the center
195    #[must_use]
196    pub fn as_avg_circle(&self) -> Circle {
197        let total: usize = self.points.iter().map(|p| p.distance(self.center)).sum();
198        let radius = total / self.points.len();
199        Circle::new(self.center, radius)
200    }
201
202    /// If the polygon is regular then it returns a circle from center to the first point
203    #[must_use]
204    pub fn as_circle(&self) -> Option<Circle> {
205        if self.is_regular {
206            Some(Circle::from_points(&[self.center, self.points[0]]))
207        } else {
208            None
209        }
210    }
211
212    /// Creates rect that contains the whole shape
213    #[must_use]
214    pub fn as_rect(&self) -> Rect {
215        Rect::new((self.left(), self.top()), (self.right(), self.bottom()))
216    }
217
218    #[must_use]
219    pub fn as_lines(&self) -> Vec<Line> {
220        let mut lines = vec![];
221        let poly = self.points.clone();
222        for i in 0..poly.len() - 1 {
223            lines.push(Line::new(poly[i], poly[i + 1]));
224        }
225        lines.push(Line::new(poly[poly.len() - 1], poly[0]));
226        lines
227    }
228
229    /// Cuts shape into triangles, triangles will be from the center to the edge
230    /// This only works on convex polygons
231    #[must_use]
232    pub fn as_triangles(&self) -> Option<Vec<Triangle>> {
233        if !self.is_convex {
234            return None;
235        }
236        let mut output = vec![];
237        for coords in self.points.windows(2) {
238            output.push(Triangle::new(coords[0], coords[1], self.center));
239        }
240        output.push(Triangle::new(
241            *self.points.last().unwrap(),
242            self.points[0],
243            self.center,
244        ));
245
246        Some(output)
247    }
248}
249
250fn is_convex(points: &[Coord]) -> bool {
251    let mut prev = 0;
252    for i in 0..points.len() {
253        let product = (points[(i + 1) % points.len()] - points[i])
254            .cross_product(points[(i + 2) % points.len()] - points[i]);
255        if product != 0 {
256            if product * prev < 0 {
257                return false;
258            } else {
259                prev = product;
260            }
261        }
262    }
263    true
264}