Skip to content

Commit 96fd91c

Browse files
committed
[add] matrix rotations for 4x4 matrices & tests to validate.
1 parent 50869a4 commit 96fd91c

2 files changed

Lines changed: 190 additions & 5 deletions

File tree

lambda/src/math/matrix.rs

Lines changed: 171 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,10 @@
33
use lambda_platform::rand::get_uniformly_random_floats_between;
44

55
use super::vector::Vector;
6+
use crate::{
7+
assert_approximately_equal,
8+
math::turns_to_radians,
9+
};
610

711
// -------------------------------- MATRIX -------------------------------------
812

@@ -77,6 +81,70 @@ pub fn translation_matrix<
7781
return result;
7882
}
7983

84+
/// Rotates the input matrix by the given number of turns around the given axis.
85+
/// The axis must be a unit vector and the turns must be in the range [0, 1).
86+
/// The rotation is counter-clockwise when looking down the axis.
87+
pub fn rotate_matrix<
88+
InputVector: Vector<Scalar = f32>,
89+
ResultingVector: Vector<Scalar = f32>,
90+
OutputMatrix: Matrix<ResultingVector> + Default + Clone,
91+
>(
92+
matrix_to_rotate: OutputMatrix,
93+
axis_to_rotate: InputVector,
94+
angle_in_turns: f32,
95+
) -> OutputMatrix {
96+
let (rows, columns) = matrix_to_rotate.size();
97+
assert_eq!(rows, columns, "Matrix must be square");
98+
assert_eq!(rows, 4, "Matrix must be 4x4");
99+
assert_eq!(
100+
axis_to_rotate.size(),
101+
3,
102+
"Axis vector must have 3 elements (x, y, z)"
103+
);
104+
105+
// Convert the angle from turns to radians
106+
let angle_in_radians = turns_to_radians(angle_in_turns);
107+
let cosine_of_angle = angle_in_radians.cos();
108+
let sin_of_angle = -angle_in_radians.sin();
109+
110+
let t = 1.0 - cosine_of_angle;
111+
let x = axis_to_rotate.at(0);
112+
let y = axis_to_rotate.at(1);
113+
let z = axis_to_rotate.at(2);
114+
115+
let mut rotation_matrix = OutputMatrix::default();
116+
117+
let rotation = [
118+
[
119+
t * x * x + cosine_of_angle,
120+
t * x * y - (sin_of_angle * z),
121+
t * x * z + (sin_of_angle * y),
122+
0.0,
123+
],
124+
[
125+
t * x * y + (sin_of_angle * z),
126+
t * y * y + cosine_of_angle,
127+
t * y * z - (sin_of_angle * x),
128+
0.0,
129+
],
130+
[
131+
t * x * z - (sin_of_angle * y),
132+
t * y * z - (sin_of_angle * x),
133+
t * z * z + cosine_of_angle,
134+
0.0,
135+
],
136+
[0.0, 0.0, 0.0, 1.0],
137+
];
138+
139+
for i in 0..rows {
140+
for j in 0..columns {
141+
rotation_matrix.update(i, j, rotation[i][j]);
142+
}
143+
}
144+
145+
return matrix_to_rotate.multiply(&rotation_matrix);
146+
}
147+
80148
/// Creates a 4x4 perspective matrix given the fov in turns (unit between
81149
/// 0..2pi radians), aspect ratio, near clipping plane (also known as z_near),
82150
/// and far clipping plane (also known as z_far). Enforces that the matrix being
@@ -98,7 +166,7 @@ pub fn perspective_matrix<
98166
"Matrix must be square to be a perspective matrix"
99167
);
100168
debug_assert_eq!(rows, 4, "Matrix must be 4x4 to be a perspective matrix");
101-
let fov_in_radians = fov * std::f32::consts::PI * 2.0;
169+
let fov_in_radians = turns_to_radians(fov);
102170
let f = 1.0 / (fov_in_radians / 2.0).tan();
103171
let range = near_clipping_plane - far_clipping_plane;
104172

@@ -115,9 +183,70 @@ pub fn perspective_matrix<
115183
return result;
116184
}
117185

186+
pub fn zeroed_matrix<
187+
V: Vector<Scalar = f32>,
188+
MatrixLike: Matrix<V> + Default,
189+
>(
190+
rows: usize,
191+
columns: usize,
192+
) -> MatrixLike {
193+
let mut result = MatrixLike::default();
194+
for i in 0..rows {
195+
for j in 0..columns {
196+
result.update(i, j, 0.0);
197+
}
198+
}
199+
return result;
200+
}
201+
202+
/// Creates a new matrix with the given number of rows and columns, and fills it
203+
/// with the given value.
204+
pub fn filled_matrix<
205+
V: Vector<Scalar = f32>,
206+
MatrixLike: Matrix<V> + Default,
207+
>(
208+
rows: usize,
209+
columns: usize,
210+
value: V::Scalar,
211+
) -> MatrixLike {
212+
let mut result = MatrixLike::default();
213+
for i in 0..rows {
214+
for j in 0..columns {
215+
result.update(i, j, value);
216+
}
217+
}
218+
return result;
219+
}
220+
221+
pub fn identity_matrix<
222+
V: Vector<Scalar = f32>,
223+
MatrixLike: Matrix<V> + Default,
224+
>(
225+
rows: usize,
226+
columns: usize,
227+
) -> MatrixLike {
228+
assert_eq!(
229+
rows, columns,
230+
"Matrix must be square to be an identity matrix"
231+
);
232+
let mut result = MatrixLike::default();
233+
for i in 0..rows {
234+
for j in 0..columns {
235+
if i == j {
236+
result.update(i, j, 1.0);
237+
} else {
238+
result.update(i, j, 0.0);
239+
}
240+
}
241+
}
242+
return result;
243+
}
244+
118245
// -------------------------- ARRAY IMPLEMENTATION -----------------------------
119246

120-
/// Matrix implementations for arrays backed by vectors.
247+
/// Matrix implementations for arrays of f32 arrays. Including the trait Matrix into
248+
/// your code will allow you to use these function implementation for any array
249+
/// of f32 arrays.
121250
impl<Array, V> Matrix<V> for Array
122251
where
123252
Array: AsMut<[V]> + AsRef<[V]> + Default,
@@ -244,11 +373,16 @@ where
244373
mod tests {
245374

246375
use super::{
376+
filled_matrix,
247377
perspective_matrix,
378+
rotate_matrix,
248379
submatrix,
249380
Matrix,
250381
};
251-
use crate::math::matrix::translation_matrix;
382+
use crate::math::{
383+
matrix::translation_matrix,
384+
turns_to_radians,
385+
};
252386

253387
#[test]
254388
fn square_matrix_add() {
@@ -336,8 +470,8 @@ mod tests {
336470
perspective_matrix(1.0 / 4.0, 1.0, 1.0, 0.0);
337471

338472
// Compute the field of view values used by the perspective matrix by hand.
339-
let fov_radians = (1.0 / 4.0) * std::f32::consts::PI * 2.0;
340-
let f = 1.0 / (fov_radians as f32 / 2.0).tan();
473+
let fov_radians = turns_to_radians(1.0 / 4.0);
474+
let f = 1.0 / (fov_radians / 2.0).tan();
341475

342476
let expected: [[f32; 4]; 4] = [
343477
[f, 0.0, 0.0, 0.0],
@@ -348,4 +482,36 @@ mod tests {
348482

349483
assert_eq!(perspective, expected);
350484
}
485+
486+
#[test]
487+
fn rotate_matrices() {
488+
let rotation_matrix: [[f32; 4]; 4] = filled_matrix(4, 4, 1.0);
489+
let rotated_matrix = rotate_matrix(rotation_matrix, [0.0, 0.0, 1.0], 0.0);
490+
assert_eq!(rotated_matrix, rotation_matrix);
491+
492+
let matrix = [
493+
[1.0, 2.0, 3.0, 4.0],
494+
[5.0, 6.0, 7.0, 8.0],
495+
[9.0, 10.0, 11.0, 12.0],
496+
[13.0, 14.0, 15.0, 16.0],
497+
];
498+
let rotated = rotate_matrix(matrix, [0.0, 1.0, 0.0], 0.25);
499+
let expected = [
500+
[3.0, 1.9999999, -1.0000001, 4.0],
501+
[7.0, 5.9999995, -5.0000005, 8.0],
502+
[11.0, 9.999999, -9.000001, 12.0],
503+
[14.999999, 13.999999, -13.000001, 16.0],
504+
];
505+
506+
println!("rotated: {:?}", rotated);
507+
for i in 0..4 {
508+
for j in 0..4 {
509+
crate::assert_approximately_equal!(
510+
rotated.at(i, j),
511+
expected.at(i, j),
512+
0.1
513+
);
514+
}
515+
}
516+
}
351517
}

lambda/src/math/mod.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,22 @@
22
33
pub mod matrix;
44
pub mod vector;
5+
6+
/// Convert a turn into radians.
7+
fn turns_to_radians(turns: f32) -> f32 {
8+
return turns * std::f32::consts::PI * 2.0;
9+
}
10+
11+
#[macro_export]
12+
macro_rules! assert_approximately_equal {
13+
($a:expr, $b:expr, $eps:expr) => {{
14+
let (a, b, eps) = ($a, $b, $eps);
15+
assert!(
16+
(a - b).abs() < eps,
17+
"{} is not approximately equal to {} with an epsilon of {}",
18+
a,
19+
b,
20+
eps
21+
);
22+
}};
23+
}

0 commit comments

Comments
 (0)