Skip to main content

use_polygon/
lib.rs

1#![forbid(unsafe_code)]
2#![doc = include_str!("../README.md")]
3
4use use_point::Point2;
5
6/// An ordered planar polygon boundary.
7#[derive(Debug, Clone, PartialEq)]
8pub struct Polygon {
9    vertices: Vec<Point2>,
10}
11
12impl Polygon {
13    /// Creates a polygon boundary from ordered vertices.
14    #[must_use]
15    pub const fn new(vertices: Vec<Point2>) -> Self {
16        Self { vertices }
17    }
18
19    /// Returns the vertices.
20    #[must_use]
21    pub fn vertices(&self) -> &[Point2] {
22        &self.vertices
23    }
24
25    /// Returns the vertex count.
26    #[must_use]
27    pub fn vertex_count(&self) -> usize {
28        self.vertices.len()
29    }
30
31    /// Returns `true` when there are no vertices.
32    #[must_use]
33    pub fn is_empty(&self) -> bool {
34        self.vertices.is_empty()
35    }
36
37    /// Returns twice the signed area by the shoelace formula.
38    #[must_use]
39    pub fn twice_signed_area(&self) -> f64 {
40        if self.vertices.len() < 3 {
41            return 0.0;
42        }
43
44        self.vertices
45            .iter()
46            .zip(self.vertices.iter().cycle().skip(1))
47            .take(self.vertices.len())
48            .map(|(a, b)| a.x() * b.y() - a.y() * b.x())
49            .sum()
50    }
51
52    /// Returns the unsigned polygon area.
53    #[must_use]
54    pub fn area(&self) -> f64 {
55        self.twice_signed_area().abs() * 0.5
56    }
57}
58
59#[cfg(test)]
60mod tests {
61    use super::Polygon;
62    use use_point::Point2;
63
64    #[test]
65    fn computes_polygon_area() {
66        let polygon = Polygon::new(vec![
67            Point2::new(0.0, 0.0),
68            Point2::new(4.0, 0.0),
69            Point2::new(0.0, 3.0),
70        ]);
71
72        assert_eq!(polygon.vertex_count(), 3);
73        assert_eq!(polygon.area(), 6.0);
74        assert!(!polygon.is_empty());
75    }
76}