outline_2d/
lib.rs

1use glam::Vec2;
2use std::ops::Index;
3
4/// Represent closed circuit of vertices
5#[derive(Clone, Debug, Default)]
6pub struct Outline {
7    vertices: Vec<Vec2>,
8}
9
10impl Outline {
11    /// Creates new outline.
12    /// # Arguments
13    /// * `vertices` - iterator of vertices. They **MUST** follow in order, which guarantee:
14    /// 1) when follow from i to i+1 vertex, inner area of polygon **MUST** be at left side;
15    pub fn new(vertices: impl Iterator<Item = Vec2>) -> Self {
16        Outline {
17            vertices: vertices.collect(),
18        }
19    }
20
21    /// Tuple of (`i-1`, `i`, `i+1`) vertices;
22    /// * `i` - index of vertex. May be negative;
23    pub fn prev_that_next(&self, i: isize) -> (Vec2, Vec2, Vec2) {
24        (self[i - 1], self[i], self[i + 1])
25    }
26
27    /// Tuple of vectors to previous and to next vertex for `i`-th vertex;
28    /// * `i` - index of vertex. May be negative;
29    pub fn to_neighbors(&self, i: isize) -> (Vec2, Vec2) {
30        let (prev, that, next) = self.prev_that_next(i);
31        (prev - that, next - that)
32    }
33
34    /// Test if angle is convex;
35    /// * `i` - index of vertex. May be negative;
36    pub fn convex(&self, i: isize) -> bool {
37        let (_, sin) = self.inner_angle_cos_sin(i);
38        sin > 0f32
39    }
40
41    /// Test if angle is concave;
42    /// * `i` - index of vertex. May be negative;
43    pub fn concave(&self, i: isize) -> bool {
44        !self.convex(i)
45    }
46
47    /// `sin()` and `cos()` for counter-clockwise angle between vector to next vertex and vector
48    /// to previous.
49    /// # Arguments
50    /// * `i` - index of vertex. May be negative;
51    pub fn inner_angle_cos_sin(&self, i: isize) -> (f32, f32) {
52        let (to_prev, to_next) = self.to_neighbors(i);
53        let prev_inv_len = to_prev.length_reciprocal();
54        let next_inv_len = to_next.length_reciprocal();
55        let norm_coef = prev_inv_len * next_inv_len;
56        let cross = to_next.extend(0f32).cross(to_prev.extend(0f32));
57
58        let cos = norm_coef * to_prev.dot(to_next);
59        let sin = norm_coef * cross.z();
60        (cos, sin)
61    }
62
63    /// Inner angle for vertex `i`-th vertex
64    /// # Arguments
65    /// * `i` - index of vertex. May be negative;
66    pub fn inner_angle(&self, i: isize) -> f32 {
67        let (cos, sin) = self.inner_angle_cos_sin(i);
68        sin.atan2(cos)
69    }
70
71    /// Outer angle for vertex `i`-th vertex
72    /// # Arguments
73    /// * `i` - index of vertex. May be negative;
74    pub fn outer_angle(&self, i: isize) -> f32 {
75        2f32 * std::f32::consts::PI - self.inner_angle(i)
76    }
77}
78
79impl Index<isize> for Outline {
80    type Output = Vec2;
81
82    fn index(&self, i: isize) -> &Vec2 {
83        &self.vertices[i.rem_euclid(self.vertices.len() as isize) as usize]
84    }
85}
86
87#[cfg(test)]
88mod tests {
89    use super::Outline;
90    use glam::Vec2;
91
92    fn default_verts() -> Vec<Vec2> {
93        let a = Vec2::new(0f32, 1f32);
94        let b = Vec2::new(2f32, 3f32);
95        let c = Vec2::new(4f32, 5f32);
96        let d = Vec2::new(6f32, 7f32);
97        vec![a, b, c, d]
98    }
99
100    #[test]
101    fn indexing() {
102        let verts = default_verts();
103        let outline = Outline::new(verts.clone().into_iter());
104        assert_eq!(outline[0], verts[0]);
105        assert_eq!(outline[1], verts[1]);
106        assert_eq!(outline[2], verts[2]);
107        assert_eq!(outline[3], verts[3]);
108        assert_eq!(outline[-1], verts[3]);
109        assert_eq!(outline[-3], verts[1]);
110        assert_eq!(outline[-19], verts[1]);
111    }
112
113    #[test]
114    fn prev_that_next() {
115        let verts = default_verts();
116        let outline = Outline::new(verts.clone().into_iter());
117        let (p, t, n) = outline.prev_that_next(0);
118        assert_eq!(p, verts[3]);
119        assert_eq!(t, verts[0]);
120        assert_eq!(n, verts[1]);
121
122        let (p, t, n) = outline.prev_that_next(-1);
123        assert_eq!(p, verts[2]);
124        assert_eq!(t, verts[3]);
125        assert_eq!(n, verts[0]);
126    }
127
128    #[test]
129    fn convex() {
130        let a = Vec2::new(0f32, 0f32);
131        let b = Vec2::new(1f32, 0f32);
132        let c = Vec2::new(1f32, 1f32);
133        let verts = vec![a, b, c];
134        let outline = Outline::new(verts.into_iter());
135        assert!(outline.convex(1));
136
137        let b = Vec2::new(0f32, 1f32);
138        let verts = vec![a, b, c];
139        let outline = Outline::new(verts.into_iter());
140        assert!(!outline.convex(1));
141    }
142
143    #[test]
144    fn concave() {
145        let a = Vec2::new(0f32, 0f32);
146        let b = Vec2::new(1f32, 0f32);
147        let c = Vec2::new(1f32, 1f32);
148        let verts = vec![a, b, c];
149        let outline = Outline::new(verts.into_iter());
150        assert!(!outline.concave(1));
151
152        let b = Vec2::new(0f32, 1f32);
153        let verts = vec![a, b, c];
154        let outline = Outline::new(verts.into_iter());
155        assert!(outline.concave(1));
156    }
157
158    #[test]
159    fn inner_angle() {
160        let a = Vec2::new(0f32, 0f32);
161        let b = Vec2::new(1f32, 0f32);
162        let c = Vec2::new(1f32, 1f32);
163        let verts = vec![a, b, c];
164        let outline = Outline::new(verts.into_iter());
165        assert_eq!(outline.inner_angle(0), std::f32::consts::FRAC_PI_4);
166        assert_eq!(outline.inner_angle(1), std::f32::consts::FRAC_PI_2);
167    }
168
169    #[test]
170    fn outer_angle() {
171        let a = Vec2::new(0f32, 0f32);
172        let b = Vec2::new(1f32, 0f32);
173        let c = Vec2::new(1f32, 1f32);
174        let verts = vec![a, b, c];
175        let outline = Outline::new(verts.into_iter());
176        assert_eq!(outline.outer_angle(0), 7f32 * std::f32::consts::FRAC_PI_4);
177        assert_eq!(outline.outer_angle(1), 3f32 * std::f32::consts::FRAC_PI_2);
178    }
179}