1use glam::Vec2;
2use std::ops::Index;
3
4#[derive(Clone, Debug, Default)]
6pub struct Outline {
7 vertices: Vec<Vec2>,
8}
9
10impl Outline {
11 pub fn new(vertices: impl Iterator<Item = Vec2>) -> Self {
16 Outline {
17 vertices: vertices.collect(),
18 }
19 }
20
21 pub fn prev_that_next(&self, i: isize) -> (Vec2, Vec2, Vec2) {
24 (self[i - 1], self[i], self[i + 1])
25 }
26
27 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 pub fn convex(&self, i: isize) -> bool {
37 let (_, sin) = self.inner_angle_cos_sin(i);
38 sin > 0f32
39 }
40
41 pub fn concave(&self, i: isize) -> bool {
44 !self.convex(i)
45 }
46
47 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 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 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}