33use super :: {
44 turns_to_radians,
55 vector:: Vector ,
6+ MathError ,
67} ;
78
89// -------------------------------- MATRIX -------------------------------------
@@ -17,7 +18,10 @@ pub trait Matrix<V: Vector> {
1718 fn transpose ( & self ) -> Self ;
1819 fn inverse ( & self ) -> Self ;
1920 fn transform ( & self , other : & V ) -> V ;
20- fn determinant ( & self ) -> f32 ;
21+ /// Compute the determinant of the matrix.
22+ ///
23+ /// Returns an error when the matrix is empty or not square.
24+ fn determinant ( & self ) -> Result < f32 , MathError > ;
2125 fn size ( & self ) -> ( usize , usize ) ;
2226 fn row ( & self , row : usize ) -> & V ;
2327 fn at ( & self , row : usize , column : usize ) -> V :: Scalar ;
@@ -84,70 +88,67 @@ pub fn translation_matrix<
8488/// Rotates the input matrix by the given number of turns around the given axis.
8589/// The axis must be a unit vector and the turns must be in the range [0, 1).
8690/// The rotation is counter-clockwise when looking down the axis.
91+ ///
92+ /// Returns an error when the matrix is not 4x4, or when `axis_to_rotate` is not
93+ /// a unit axis vector (`[1,0,0]`, `[0,1,0]`, `[0,0,1]`). A zero axis (`[0,0,0]`)
94+ /// is treated as "no rotation".
8795pub fn rotate_matrix <
88- InputVector : Vector < Scalar = f32 > ,
89- ResultingVector : Vector < Scalar = f32 > ,
90- OutputMatrix : Matrix < ResultingVector > + Default + Clone ,
96+ V : Vector < Scalar = f32 > ,
97+ MatrixLike : Matrix < V > + Default + Clone ,
9198> (
92- matrix_to_rotate : OutputMatrix ,
93- axis_to_rotate : InputVector ,
99+ matrix_to_rotate : MatrixLike ,
100+ axis_to_rotate : [ f32 ; 3 ] ,
94101 angle_in_turns : f32 ,
95- ) -> OutputMatrix {
102+ ) -> Result < MatrixLike , MathError > {
96103 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+ if rows != columns {
105+ return Err ( MathError :: NonSquareMatrix {
106+ rows,
107+ cols : columns,
108+ } ) ;
109+ }
110+ if rows != 4 {
111+ return Err ( MathError :: InvalidRotationMatrixSize {
112+ rows,
113+ cols : columns,
114+ } ) ;
115+ }
104116
105117 let angle_in_radians = turns_to_radians ( angle_in_turns) ;
106118 let cosine_of_angle = angle_in_radians. cos ( ) ;
107119 let sin_of_angle = angle_in_radians. sin ( ) ;
108120
109- let _t = 1.0 - cosine_of_angle;
110- let x = axis_to_rotate. at ( 0 ) ;
111- let y = axis_to_rotate. at ( 1 ) ;
112- let z = axis_to_rotate. at ( 2 ) ;
113-
114- let mut rotation_matrix = OutputMatrix :: default ( ) ;
115-
116- let rotation = match ( x as u8 , y as u8 , z as u8 ) {
117- ( 0 , 0 , 0 ) => {
118- // No rotation
119- return matrix_to_rotate;
120- }
121- ( 0 , 0 , 1 ) => {
122- // Rotate around z-axis
123- [
124- [ cosine_of_angle, sin_of_angle, 0.0 , 0.0 ] ,
125- [ -sin_of_angle, cosine_of_angle, 0.0 , 0.0 ] ,
126- [ 0.0 , 0.0 , 1.0 , 0.0 ] ,
127- [ 0.0 , 0.0 , 0.0 , 1.0 ] ,
128- ]
129- }
130- ( 0 , 1 , 0 ) => {
131- // Rotate around y-axis
132- [
133- [ cosine_of_angle, 0.0 , -sin_of_angle, 0.0 ] ,
134- [ 0.0 , 1.0 , 0.0 , 0.0 ] ,
135- [ sin_of_angle, 0.0 , cosine_of_angle, 0.0 ] ,
136- [ 0.0 , 0.0 , 0.0 , 1.0 ] ,
137- ]
138- }
139- ( 1 , 0 , 0 ) => {
140- // Rotate around x-axis
141- [
142- [ 1.0 , 0.0 , 0.0 , 0.0 ] ,
143- [ 0.0 , cosine_of_angle, sin_of_angle, 0.0 ] ,
144- [ 0.0 , -sin_of_angle, cosine_of_angle, 0.0 ] ,
145- [ 0.0 , 0.0 , 0.0 , 1.0 ] ,
146- ]
147- }
148- _ => {
149- panic ! ( "Axis must be a unit vector" )
150- }
121+ let mut rotation_matrix = MatrixLike :: default ( ) ;
122+ let [ x, y, z] = axis_to_rotate;
123+
124+ let rotation = if axis_to_rotate == [ 0.0 , 0.0 , 0.0 ] {
125+ return Ok ( matrix_to_rotate) ;
126+ } else if axis_to_rotate == [ 0.0 , 0.0 , 1.0 ] {
127+ // Rotate around z-axis
128+ [
129+ [ cosine_of_angle, sin_of_angle, 0.0 , 0.0 ] ,
130+ [ -sin_of_angle, cosine_of_angle, 0.0 , 0.0 ] ,
131+ [ 0.0 , 0.0 , 1.0 , 0.0 ] ,
132+ [ 0.0 , 0.0 , 0.0 , 1.0 ] ,
133+ ]
134+ } else if axis_to_rotate == [ 0.0 , 1.0 , 0.0 ] {
135+ // Rotate around y-axis
136+ [
137+ [ cosine_of_angle, 0.0 , -sin_of_angle, 0.0 ] ,
138+ [ 0.0 , 1.0 , 0.0 , 0.0 ] ,
139+ [ sin_of_angle, 0.0 , cosine_of_angle, 0.0 ] ,
140+ [ 0.0 , 0.0 , 0.0 , 1.0 ] ,
141+ ]
142+ } else if axis_to_rotate == [ 1.0 , 0.0 , 0.0 ] {
143+ // Rotate around x-axis
144+ [
145+ [ 1.0 , 0.0 , 0.0 , 0.0 ] ,
146+ [ 0.0 , cosine_of_angle, sin_of_angle, 0.0 ] ,
147+ [ 0.0 , -sin_of_angle, cosine_of_angle, 0.0 ] ,
148+ [ 0.0 , 0.0 , 0.0 , 1.0 ] ,
149+ ]
150+ } else {
151+ return Err ( MathError :: InvalidRotationAxis { axis : [ x, y, z] } ) ;
151152 } ;
152153
153154 for ( i, row) in rotation. iter ( ) . enumerate ( ) . take ( rows) {
@@ -156,7 +157,7 @@ pub fn rotate_matrix<
156157 }
157158 }
158159
159- return matrix_to_rotate. multiply ( & rotation_matrix) ;
160+ return Ok ( matrix_to_rotate. multiply ( & rotation_matrix) ) ;
160161}
161162
162163/// Creates a 4x4 perspective matrix given the fov in turns (unit between
@@ -325,40 +326,47 @@ where
325326 }
326327
327328 /// Computes the determinant of any square matrix using Laplace expansion.
328- fn determinant ( & self ) -> f32 {
329- let ( width, height) =
330- ( self . as_ref ( ) [ 0 ] . as_ref ( ) . len ( ) , self . as_ref ( ) . len ( ) ) ;
329+ fn determinant ( & self ) -> Result < f32 , MathError > {
330+ let rows = self . as_ref ( ) . len ( ) ;
331+ if rows == 0 {
332+ return Err ( MathError :: EmptyMatrix ) ;
333+ }
334+
335+ let cols = self . as_ref ( ) [ 0 ] . as_ref ( ) . len ( ) ;
336+ if cols == 0 {
337+ return Err ( MathError :: EmptyMatrix ) ;
338+ }
331339
332- if width != height {
333- panic ! ( "Cannot compute determinant of non-square matrix" ) ;
340+ if cols != rows {
341+ return Err ( MathError :: NonSquareMatrix { rows , cols } ) ;
334342 }
335343
336- return match height {
337- 1 => self . as_ref ( ) [ 0 ] . as_ref ( ) [ 0 ] ,
344+ return match rows {
345+ 1 => Ok ( self . as_ref ( ) [ 0 ] . as_ref ( ) [ 0 ] ) ,
338346 2 => {
339347 let a = self . at ( 0 , 0 ) ;
340348 let b = self . at ( 0 , 1 ) ;
341349 let c = self . at ( 1 , 0 ) ;
342350 let d = self . at ( 1 , 1 ) ;
343- a * d - b * c
351+ return Ok ( a * d - b * c) ;
344352 }
345353 _ => {
346354 let mut result = 0.0 ;
347- for i in 0 ..height {
348- let mut submatrix: Vec < Vec < f32 > > = Vec :: with_capacity ( height - 1 ) ;
349- for j in 1 ..height {
355+ for i in 0 ..rows {
356+ let mut submatrix: Vec < Vec < f32 > > = Vec :: with_capacity ( rows - 1 ) ;
357+ for j in 1 ..rows {
350358 let mut row = Vec :: new ( ) ;
351- for k in 0 ..height {
359+ for k in 0 ..rows {
352360 if k != i {
353361 row. push ( self . at ( j, k) ) ;
354362 }
355363 }
356364 submatrix. push ( row) ;
357365 }
358- result +=
359- self . at ( 0 , i) * submatrix . determinant ( ) * ( -1.0_f32 ) . powi ( i as i32 ) ;
366+ let sub_determinant = submatrix . determinant ( ) ? ;
367+ result += self . at ( 0 , i) * sub_determinant * ( -1.0_f32 ) . powi ( i as i32 ) ;
360368 }
361- result
369+ return Ok ( result) ;
362370 }
363371 } ;
364372 }
@@ -397,6 +405,7 @@ mod tests {
397405 use crate :: math:: {
398406 matrix:: translation_matrix,
399407 turns_to_radians,
408+ MathError ,
400409 } ;
401410
402411 #[ test]
@@ -438,17 +447,17 @@ mod tests {
438447 #[ test]
439448 fn square_matrix_determinant ( ) {
440449 let m = [ [ 3.0 , 8.0 ] , [ 4.0 , 6.0 ] ] ;
441- assert_eq ! ( m. determinant( ) , -14.0 ) ;
450+ assert_eq ! ( m. determinant( ) , Ok ( -14.0 ) ) ;
442451
443452 let m2 = [ [ 6.0 , 1.0 , 1.0 ] , [ 4.0 , -2.0 , 5.0 ] , [ 2.0 , 8.0 , 7.0 ] ] ;
444- assert_eq ! ( m2. determinant( ) , -306.0 ) ;
453+ assert_eq ! ( m2. determinant( ) , Ok ( -306.0 ) ) ;
445454 }
446455
447456 #[ test]
448457 fn non_square_matrix_determinant ( ) {
449458 let m = [ [ 3.0 , 8.0 ] , [ 4.0 , 6.0 ] , [ 0.0 , 1.0 ] ] ;
450- let result = std :: panic :: catch_unwind ( || m. determinant ( ) ) ;
451- assert ! ( result. is_err ( ) ) ;
459+ let result = m. determinant ( ) ;
460+ assert_eq ! ( result, Err ( MathError :: NonSquareMatrix { rows : 3 , cols : 2 } ) ) ;
452461 }
453462
454463 #[ test]
@@ -503,7 +512,8 @@ mod tests {
503512 fn rotate_matrices ( ) {
504513 // Test a zero turn rotation.
505514 let matrix: [ [ f32 ; 4 ] ; 4 ] = filled_matrix ( 4 , 4 , 1.0 ) ;
506- let rotated_matrix = rotate_matrix ( matrix, [ 0.0 , 0.0 , 1.0 ] , 0.0 ) ;
515+ let rotated_matrix =
516+ rotate_matrix ( matrix, [ 0.0 , 0.0 , 1.0 ] , 0.0 ) . expect ( "valid axis" ) ;
507517 assert_eq ! ( rotated_matrix, matrix) ;
508518
509519 // Test a 90 degree rotation.
@@ -513,7 +523,8 @@ mod tests {
513523 [ 9.0 , 10.0 , 11.0 , 12.0 ] ,
514524 [ 13.0 , 14.0 , 15.0 , 16.0 ] ,
515525 ] ;
516- let rotated = rotate_matrix ( matrix, [ 0.0 , 1.0 , 0.0 ] , 0.25 ) ;
526+ let rotated =
527+ rotate_matrix ( matrix, [ 0.0 , 1.0 , 0.0 ] , 0.25 ) . expect ( "valid axis" ) ;
517528 let expected = [
518529 [ 3.0 , 1.9999999 , -1.0000001 , 4.0 ] ,
519530 [ 7.0 , 5.9999995 , -5.0000005 , 8.0 ] ,
0 commit comments