Skip to main content

mesh_graph/plane_slice/
polygon.rs

1use std::collections::VecDeque;
2
3use glam::{Mat4, Vec2, Vec3, Vec4Swizzles};
4use itertools::Itertools;
5
6#[derive(Debug, Clone, Copy)]
7pub enum PolygonTerminal {
8    Start,
9    End,
10}
11
12#[derive(Debug, Clone, PartialEq)]
13pub struct Polygon2 {
14    pub vertices: VecDeque<Vec2>,
15}
16
17#[derive(Debug, Clone, PartialEq)]
18pub struct Polygon3 {
19    pub vertices: VecDeque<Vec3>,
20}
21
22impl Polygon2 {
23    /// Returns the terminal vertex of the polygon, or `None` if the polygon is empty.
24    pub fn terminal(&self, terminal: PolygonTerminal) -> Option<Vec2> {
25        if self.vertices.is_empty() {
26            return None;
27        }
28
29        match terminal {
30            PolygonTerminal::Start => Some(self.vertices[0]),
31            PolygonTerminal::End => Some(self.vertices[self.vertices.len() - 1]),
32        }
33    }
34
35    /// Extends the polygon by a line from the terminal vertex to `other_line_end`.
36    pub fn extend_by_line(&mut self, terminal: PolygonTerminal, other_line_end: Vec2) {
37        match terminal {
38            PolygonTerminal::Start => self.vertices.push_front(other_line_end),
39            PolygonTerminal::End => self.vertices.push_back(other_line_end),
40        }
41    }
42
43    /// This assumes that the polygon to be merged doesn't contain any vertices that are already part of this polygon.
44    pub fn merge_polygon(
45        &mut self,
46        my_terminal: PolygonTerminal,
47        mut other: Polygon2,
48        other_terminal: PolygonTerminal,
49    ) {
50        let mut vertices = VecDeque::new();
51
52        match my_terminal {
53            PolygonTerminal::Start => {
54                match other_terminal {
55                    PolygonTerminal::Start => vertices.extend(other.vertices.into_iter().rev()),
56                    PolygonTerminal::End => vertices.append(&mut other.vertices),
57                }
58
59                vertices.append(&mut self.vertices);
60            }
61            PolygonTerminal::End => {
62                vertices.append(&mut self.vertices);
63
64                match other_terminal {
65                    PolygonTerminal::Start => vertices.append(&mut other.vertices),
66                    PolygonTerminal::End => vertices.extend(other.vertices.into_iter().rev()),
67                }
68            }
69        }
70
71        self.vertices = vertices;
72    }
73
74    /// Returns `true` if the polygon is closed (the terminal vertex is the same as the first vertex).
75    pub fn is_closed(&self) -> bool {
76        if let (Some(front), Some(back)) = (self.vertices.front(), self.vertices.back()) {
77            front.distance_squared(*back) < 1e-6
78        } else {
79            false
80        }
81    }
82
83    /// Closes the polygon by adding the terminal vertex to the front if it is not already closed.
84    pub fn close(&mut self) {
85        if !self.is_closed() {
86            self.vertices.push_back(self.vertices[0]);
87        }
88    }
89
90    /// Returns the length (cumulative distance between vertices) of the polygon.
91    pub fn length(&self) -> f32 {
92        self.vertices
93            .iter()
94            .array_windows()
95            .map(|[a, b]| a.distance(*b))
96            .sum()
97    }
98
99    #[cfg(feature = "rerun")]
100    pub fn log_rerun(&self, name: &str) {
101        use crate::utils::vec2_array;
102
103        crate::RR
104            .log(
105                name,
106                &rerun::LineStrips3D::new([self.vertices.iter().copied().map(vec2_array)]),
107            )
108            .unwrap();
109    }
110
111    /// Returns the minimum and maximum coordinates of the polygon.
112    ///
113    /// The minimum is the smallest `x` and `y` values, and the maximum is the largest `x` and `y` values.
114    /// The pair forms an AABB (axis-aligned bounding box) that encloses the polygon.
115    pub fn min_max(&self) -> (Vec2, Vec2) {
116        let mut min = Vec2::MAX;
117        let mut max = Vec2::MIN;
118
119        for vertex in &self.vertices {
120            min = min.min(*vertex);
121            max = max.max(*vertex);
122        }
123
124        (min, max)
125    }
126
127    /// Wether a point is inside the polygon.
128    ///
129    /// Assumes that the polygon is closed.
130    pub fn contains_point(&self, point: Vec2) -> bool {
131        // Use ray casting algorithm in 2D. Taken from https://wrfranklin.org/Research/Short_Notes/pnpoly.html
132
133        let mut inside = false;
134
135        for (a, b) in self.vertices.iter().tuple_windows() {
136            if ((a.y > point.y) != (b.y > point.y))
137                && (point.x < (b.x - a.x) * (point.y - a.y) / (b.y - a.y) + a.x)
138            {
139                inside = !inside;
140            }
141        }
142
143        inside
144    }
145}
146
147impl Polygon3 {
148    pub fn from_polygon2_with_transform(polygon: Polygon2, transform: Mat4) -> Self {
149        let vertices = polygon
150            .vertices
151            .into_iter()
152            .map(|v| (transform * v.extend(0.0).extend(1.0)).xyz())
153            .collect();
154
155        Self { vertices }
156    }
157
158    /// Returns the length (cumulative distance between vertices) of the polygon.
159    pub fn length(&self) -> f32 {
160        self.vertices
161            .iter()
162            .array_windows()
163            .map(|[a, b]| a.distance(*b))
164            .sum()
165    }
166
167    #[cfg(feature = "rerun")]
168    pub fn log_rerun<'a>(
169        rr: &rerun::RecordingStream,
170        name: &str,
171        polygons: impl IntoIterator<Item = &'a Self>,
172    ) {
173        use crate::utils::vec3_array;
174
175        rr.log(
176            name,
177            &rerun::LineStrips3D::new(
178                polygons
179                    .into_iter()
180                    .map(|p| p.vertices.iter().copied().map(vec3_array)),
181            ),
182        )
183        .unwrap();
184    }
185}