1use core::{
2 f32::consts::PI,
3 ops::{Add, AddAssign, Index, IndexMut, Mul, Sub},
4};
5
6#[derive(Clone, Copy, Debug, PartialEq)]
12pub struct Vec3(pub [f32; 3]);
13
14trait F32Ext {
15 fn close_to(self, other: Self, tolerance: Self) -> bool;
16}
17
18impl F32Ext for f32 {
19 fn close_to(self, other: Self, tolerance: Self) -> bool {
20 (self - other).abs() <= tolerance
21 }
22}
23
24impl Vec3 {
25 pub const ZERO: Self = Self([0.0, 0.0, 0.0]);
27
28 #[must_use]
30 pub const fn as_array(&self) -> &[f32; 3] {
31 &self.0
32 }
33
34 #[must_use]
36 pub const fn into_array(self) -> [f32; 3] {
37 self.0
38 }
39
40 #[must_use]
42 pub fn is_close_to(&self, other: &Self, tolerance: f32) -> bool {
43 self.0
44 .iter()
45 .zip(other.0.iter())
46 .all(|(left, right)| left.close_to(*right, tolerance))
47 }
48
49 #[must_use]
51 pub fn dot(self, rhs: Self) -> f32 {
52 self[0] * rhs[0] + self[1] * rhs[1] + self[2] * rhs[2]
53 }
54
55 #[must_use]
57 pub fn length(self) -> f32 {
58 libm::sqrtf(self.dot(self))
59 }
60
61 #[must_use]
63 pub fn distance_to(self, other: Self) -> f32 {
64 (self - other).length()
65 }
66}
67
68impl From<[f32; 3]> for Vec3 {
69 fn from(value: [f32; 3]) -> Self {
70 Self(value)
71 }
72}
73
74impl From<Vec3> for [f32; 3] {
75 fn from(value: Vec3) -> Self {
76 value.into_array()
77 }
78}
79
80impl Index<usize> for Vec3 {
81 type Output = f32;
82
83 fn index(&self, index: usize) -> &Self::Output {
84 &self.0[index]
85 }
86}
87
88impl IndexMut<usize> for Vec3 {
89 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
90 &mut self.0[index]
91 }
92}
93
94impl Add for Vec3 {
95 type Output = Self;
96
97 fn add(self, rhs: Self) -> Self::Output {
98 Self([self[0] + rhs[0], self[1] + rhs[1], self[2] + rhs[2]])
99 }
100}
101
102impl AddAssign for Vec3 {
103 fn add_assign(&mut self, rhs: Self) {
104 *self = *self + rhs;
105 }
106}
107
108impl Sub for Vec3 {
109 type Output = Self;
110
111 fn sub(self, rhs: Self) -> Self::Output {
112 Self([self[0] - rhs[0], self[1] - rhs[1], self[2] - rhs[2]])
113 }
114}
115
116impl Mul<f32> for Vec3 {
117 type Output = Self;
118
119 fn mul(self, rhs: f32) -> Self::Output {
120 Self([self[0] * rhs, self[1] * rhs, self[2] * rhs])
121 }
122}
123
124#[derive(Clone, Copy, Debug, PartialEq)]
133pub struct Mat3(pub [[f32; 3]; 3]);
134
135impl Mat3 {
136 pub const IDENTITY: Self = Self([[1.0, 0.0, 0.0], [0.0, 1.0, 0.0], [0.0, 0.0, 1.0]]);
138
139 #[must_use]
141 pub const fn as_array(&self) -> &[[f32; 3]; 3] {
142 &self.0
143 }
144
145 #[must_use]
147 pub const fn into_array(self) -> [[f32; 3]; 3] {
148 self.0
149 }
150
151 #[must_use]
153 pub fn yaw(radians: f32) -> Self {
154 let cos = libm::cosf(radians);
155 let sin = libm::sinf(radians);
156 Self([[cos, -sin, 0.0], [sin, cos, 0.0], [0.0, 0.0, 1.0]])
157 }
158
159 #[must_use]
161 pub fn pitch(radians: f32) -> Self {
162 let cos = libm::cosf(radians);
163 let sin = libm::sinf(radians);
164 Self([[cos, 0.0, sin], [0.0, 1.0, 0.0], [-sin, 0.0, cos]])
165 }
166
167 #[must_use]
169 pub fn roll(radians: f32) -> Self {
170 let cos = libm::cosf(radians);
171 let sin = libm::sinf(radians);
172 Self([[1.0, 0.0, 0.0], [0.0, cos, -sin], [0.0, sin, cos]])
173 }
174
175 #[must_use]
177 pub fn forward(&self) -> Vec3 {
178 Vec3([self[0][0], self[1][0], self[2][0]])
179 }
180
181 #[must_use]
183 pub fn left(&self) -> Vec3 {
184 Vec3([self[0][1], self[1][1], self[2][1]])
185 }
186
187 #[must_use]
189 pub fn up(&self) -> Vec3 {
190 Vec3([self[0][2], self[1][2], self[2][2]])
191 }
192
193 #[must_use]
195 pub fn is_close_to(&self, other: &Self, tolerance: f32) -> bool {
196 self.0
197 .iter()
198 .zip(other.0.iter())
199 .all(|(left, right)| Vec3::from(*left).is_close_to(&Vec3::from(*right), tolerance))
200 }
201}
202
203impl From<[[f32; 3]; 3]> for Mat3 {
204 fn from(value: [[f32; 3]; 3]) -> Self {
205 Self(value)
206 }
207}
208
209impl From<Mat3> for [[f32; 3]; 3] {
210 fn from(value: Mat3) -> Self {
211 value.into_array()
212 }
213}
214
215impl Index<usize> for Mat3 {
216 type Output = [f32; 3];
217
218 fn index(&self, index: usize) -> &Self::Output {
219 &self.0[index]
220 }
221}
222
223impl IndexMut<usize> for Mat3 {
224 fn index_mut(&mut self, index: usize) -> &mut Self::Output {
225 &mut self.0[index]
226 }
227}
228
229impl Mul for Mat3 {
230 type Output = Self;
231
232 fn mul(self, rhs: Self) -> Self::Output {
233 let mut out = [[0.0f32; 3]; 3];
234 for row in 0..3 {
235 for col in 0..3 {
236 for component in 0..3 {
237 out[row][col] += self[row][component] * rhs[component][col];
238 }
239 }
240 }
241 Self(out)
242 }
243}
244
245#[must_use]
246pub const fn degrees_to_radians(degrees: f32) -> f32 {
247 degrees * (PI / 180.0)
248}
249
250#[cfg(test)]
251mod tests {
252 use super::{F32Ext, Mat3, Vec3, degrees_to_radians};
253 use core::f32::consts::PI;
254
255 #[test]
256 fn test_degrees_to_radians() {
257 assert!(degrees_to_radians(180.0).close_to(PI, 1e-6));
258 assert!(degrees_to_radians(90.0).close_to(PI / 2.0, 1e-6));
259 }
260
261 #[test]
262 fn test_vec3_add_and_scale() {
263 let actual = Vec3::from([1.0, 2.0, 3.0]) + Vec3::from([4.0, -1.0, 0.5]) * 2.0;
264 let expected = Vec3::from([9.0, 0.0, 4.0]);
265
266 assert!(actual.is_close_to(&expected, 1e-6));
267 }
268
269 #[test]
270 fn test_vec3_sub() {
271 let actual = Vec3::from([5.0, 7.0, 9.0]) - Vec3::from([1.0, 2.0, 3.0]);
272 let expected = Vec3::from([4.0, 5.0, 6.0]);
273
274 assert!(actual.is_close_to(&expected, 1e-6));
275 }
276
277 #[test]
278 fn test_vec3_dot() {
279 let left = Vec3::from([1.0, 2.0, 3.0]);
280 let right = Vec3::from([4.0, -5.0, 6.0]);
281
282 assert!(left.dot(right).close_to(12.0, 1e-6));
283 }
284
285 #[test]
286 fn test_vec3_length() {
287 let vec3 = Vec3::from([3.0, 4.0, 0.0]);
288
289 assert!(vec3.length().close_to(5.0, 1e-6));
290 }
291
292 #[test]
293 fn test_vec3_distance_to_is_symmetric() {
294 let first = Vec3::from([1.0, 2.0, 3.0]);
295 let second = Vec3::from([4.0, 6.0, 3.0]);
296
297 let first_to_second = first.distance_to(second);
298 let second_to_first = second.distance_to(first);
299
300 assert!(first_to_second.close_to(5.0, 1e-6));
301 assert!(first_to_second.close_to(second_to_first, 1e-6));
302 }
303
304 #[test]
305 fn test_vec3_array_conversions() {
306 let vec = Vec3::from([1.0, 2.0, 3.0]);
307
308 assert_eq!(vec.as_array(), &[1.0, 2.0, 3.0]);
309 assert_eq!(vec.into_array(), [1.0, 2.0, 3.0]);
310 assert_eq!(<[f32; 3]>::from(vec), [1.0, 2.0, 3.0]);
311 }
312
313 #[test]
314 fn test_mat3_mul() {
315 let left = Mat3::from([[1.0, 2.0, 3.0], [0.0, 1.0, 4.0], [5.0, 6.0, 0.0]]);
316 let right = Mat3::from([[-2.0, 1.0, 0.0], [3.0, 0.0, 0.0], [4.0, 5.0, 1.0]]);
317 let expected = Mat3::from([[16.0, 16.0, 3.0], [19.0, 20.0, 4.0], [8.0, 5.0, 0.0]]);
318
319 assert!((left * right).is_close_to(&expected, 1e-6));
320 }
321
322 #[test]
323 fn test_mat3_array_conversions() {
324 let mat = Mat3::from([[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]]);
325 let expected = [[1.0, 2.0, 3.0], [4.0, 5.0, 6.0], [7.0, 8.0, 9.0]];
326
327 assert_eq!(mat.as_array(), &expected);
328 assert_eq!(mat.into_array(), expected);
329 assert_eq!(<[[f32; 3]; 3]>::from(mat), expected);
330 }
331
332 #[test]
333 fn test_rotation_forward_axes() {
334 let yaw_forward = Mat3::yaw(degrees_to_radians(90.0)).forward();
335 let pitch_forward = Mat3::pitch(degrees_to_radians(90.0)).forward();
336 let roll_forward = Mat3::roll(degrees_to_radians(90.0)).forward();
337
338 assert!(yaw_forward.is_close_to(&Vec3::from([0.0, 1.0, 0.0]), 1e-6));
339 assert!(pitch_forward.is_close_to(&Vec3::from([0.0, 0.0, -1.0]), 1e-6));
340 assert!(roll_forward.is_close_to(&Vec3::from([1.0, 0.0, 0.0]), 1e-6));
341 }
342
343 #[test]
344 fn test_rotation_local_axes() {
345 let yaw = Mat3::yaw(degrees_to_radians(90.0));
346 let pitch = Mat3::pitch(degrees_to_radians(90.0));
347 let roll = Mat3::roll(degrees_to_radians(90.0));
348
349 assert!(column(yaw, 2).is_close_to(&Vec3::from([0.0, 0.0, 1.0]), 1e-6));
350 assert!(column(pitch, 1).is_close_to(&Vec3::from([0.0, 1.0, 0.0]), 1e-6));
351 assert!(column(roll, 0).is_close_to(&Vec3::from([1.0, 0.0, 0.0]), 1e-6));
352 }
353
354 #[test]
355 fn test_mat3_is_close_to() {
356 let actual = Mat3::from([[1.0001, 0.0, 0.0], [0.0, 0.9999, 0.0], [0.0, 0.0, 1.0001]]);
357 let expected = Mat3::IDENTITY;
358
359 assert!(actual.is_close_to(&expected, 0.001));
360 assert!(!actual.is_close_to(&expected, 0.00001));
361 }
362
363 fn column(mat: Mat3, column_index: usize) -> Vec3 {
364 Vec3::from([
365 mat[0][column_index],
366 mat[1][column_index],
367 mat[2][column_index],
368 ])
369 }
370}