Copy Matrix4x4 impl from pbrt and start on tests
This commit is contained in:
parent
3cf580f607
commit
72b15e5516
@ -1,2 +1,3 @@
|
||||
pub mod canvas;
|
||||
pub mod matrices;
|
||||
pub mod tuples;
|
||||
|
||||
261
rtchallenge/src/matrices.rs
Normal file
261
rtchallenge/src/matrices.rs
Normal file
@ -0,0 +1,261 @@
|
||||
use std::fmt;
|
||||
use std::ops::{Add, Div, Index, Mul, Neg, Sub};
|
||||
|
||||
#[derive(Default, Clone, Copy)]
|
||||
/// Matrix4x4 represents a 4x4 matrix in row-major form. So, element `m[i][j]` corresponds to m<sub>i,j</sub>
|
||||
/// where `i` is the row number and `j` is the column number.
|
||||
pub struct Matrix4x4 {
|
||||
m: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl From<[f32; 16]> for Matrix4x4 {
|
||||
fn from(t: [f32; 16]) -> Self {
|
||||
Matrix4x4 {
|
||||
m: [
|
||||
[t[0], t[1], t[2], t[3]],
|
||||
[t[4], t[5], t[6], t[7]],
|
||||
[t[8], t[9], t[10], t[11]],
|
||||
[t[12], t[13], t[14], t[15]],
|
||||
],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Matrix4x4 {
|
||||
/// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal.
|
||||
pub fn identity() -> Matrix4x4 {
|
||||
Matrix4x4::new(
|
||||
[1., 0., 0., 0.],
|
||||
[0., 1., 0., 0.],
|
||||
[0., 0., 1., 0.],
|
||||
[0., 0., 0., 1.],
|
||||
)
|
||||
}
|
||||
|
||||
/// Create a `Matrix4x4` with each of the given rows.
|
||||
pub fn new(r0: [f32; 4], r1: [f32; 4], r2: [f32; 4], r3: [f32; 4]) -> Matrix4x4 {
|
||||
Matrix4x4 {
|
||||
m: [r0, r1, r2, r3],
|
||||
}
|
||||
}
|
||||
|
||||
/// Transpose self, returning a new matrix that has been reflected across the diagonal.
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rtchallenge::matrices::Matrix4x4;
|
||||
///
|
||||
/// let m = Matrix4x4::new(
|
||||
/// [2., 0., 0., 0.],
|
||||
/// [3., 1., 0., 0.],
|
||||
/// [4., 0., 1., 0.],
|
||||
/// [5., 6., 7., 1.],
|
||||
/// );
|
||||
/// let m_t = Matrix4x4::new(
|
||||
/// [2., 3., 4., 5.],
|
||||
/// [0., 1., 0., 6.],
|
||||
/// [0., 0., 1., 7.],
|
||||
/// [0., 0., 0., 1.],
|
||||
/// );
|
||||
/// assert_eq!(m.transpose(), m_t);
|
||||
pub fn transpose(&self) -> Matrix4x4 {
|
||||
let m = self.m;
|
||||
Matrix4x4 {
|
||||
m: [
|
||||
[m[0][0], m[1][0], m[2][0], m[3][0]],
|
||||
[m[0][1], m[1][1], m[2][1], m[3][1]],
|
||||
[m[0][2], m[1][2], m[2][2], m[3][2]],
|
||||
[m[0][3], m[1][3], m[2][3], m[3][3]],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns a new matrix that is the inverse of self. If self is A, inverse returns A<sup>-1</sup>, where
|
||||
/// AA<sup>-1</sup> = I.
|
||||
/// This implementation uses a numerically stable Gauss–Jordan elimination routine to compute the inverse.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rtchallenge::matrices::Matrix4x4;
|
||||
///
|
||||
/// let i = Matrix4x4::identity();
|
||||
/// assert_eq!(i.inverse() * i, i);
|
||||
///
|
||||
/// let m = Matrix4x4::new(
|
||||
/// [2., 0., 0., 0.],
|
||||
/// [0., 3., 0., 0.],
|
||||
/// [0., 0., 4., 0.],
|
||||
/// [0., 0., 0., 1.],
|
||||
/// );
|
||||
/// assert_eq!(m.inverse() * m, i);
|
||||
/// assert_eq!(m * m.inverse(), i);
|
||||
/// ```
|
||||
pub fn inverse(&self) -> Matrix4x4 {
|
||||
// TODO(wathiede): how come the C++ version doesn't need to deal with non-invertable
|
||||
// matrix.
|
||||
let mut indxc: [usize; 4] = Default::default();
|
||||
let mut indxr: [usize; 4] = Default::default();
|
||||
let mut ipiv: [usize; 4] = Default::default();
|
||||
let mut minv = self.m;
|
||||
|
||||
for i in 0..4 {
|
||||
let mut irow: usize = 0;
|
||||
let mut icol: usize = 0;
|
||||
let mut big: f32 = 0.;
|
||||
// Choose pivot
|
||||
for j in 0..4 {
|
||||
if ipiv[j] != 1 {
|
||||
for (k, ipivk) in ipiv.iter().enumerate() {
|
||||
if *ipivk == 0 {
|
||||
if minv[j][k].abs() >= big {
|
||||
big = minv[j][k].abs();
|
||||
irow = j;
|
||||
icol = k;
|
||||
}
|
||||
} else if *ipivk > 1 {
|
||||
eprintln!("Singular matrix in MatrixInvert");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ipiv[icol] += 1;
|
||||
// Swap rows _irow_ and _icol_ for pivot
|
||||
if irow != icol {
|
||||
// Can't figure out how to make swap work here.
|
||||
#[allow(clippy::manual_swap)]
|
||||
for k in 0..4 {
|
||||
let tmp = minv[irow][k];
|
||||
minv[irow][k] = minv[icol][k];
|
||||
minv[icol][k] = tmp;
|
||||
}
|
||||
}
|
||||
indxr[i] = irow;
|
||||
indxc[i] = icol;
|
||||
if minv[icol][icol] == 0. {
|
||||
eprintln!("Singular matrix in MatrixInvert");
|
||||
}
|
||||
|
||||
// Set $m[icol][icol]$ to one by scaling row _icol_ appropriately
|
||||
let pivinv: f32 = minv[icol][icol].recip();
|
||||
minv[icol][icol] = 1.;
|
||||
for j in 0..4 {
|
||||
minv[icol][j] *= pivinv;
|
||||
}
|
||||
|
||||
// Subtract this row from others to zero out their columns
|
||||
for j in 0..4 {
|
||||
if j != icol {
|
||||
let save = minv[j][icol];
|
||||
minv[j][icol] = 0.;
|
||||
for k in 0..4 {
|
||||
minv[j][k] -= minv[icol][k] * save;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Swap columns to reflect permutation
|
||||
for j in (0..4).rev() {
|
||||
if indxr[j] != indxc[j] {
|
||||
for mi in &mut minv {
|
||||
mi.swap(indxr[j], indxc[j])
|
||||
}
|
||||
}
|
||||
}
|
||||
Matrix4x4 { m: minv }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Matrix4x4 {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
if f.alternate() {
|
||||
write!(
|
||||
f,
|
||||
"{:?}\n {:?}\n {:?}\n {:?}",
|
||||
self.m[0], self.m[1], self.m[2], self.m[3]
|
||||
)
|
||||
} else {
|
||||
write!(
|
||||
f,
|
||||
"[{:?} {:?} {:?} {:?}]",
|
||||
self.m[0], self.m[1], self.m[2], self.m[3]
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Matrix4x4> for Matrix4x4 {
|
||||
type Output = Matrix4x4;
|
||||
|
||||
/// Implement matrix multiplication for `Matrix4x4`.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::matrices::Matrix4x4;
|
||||
///
|
||||
/// let i = Matrix4x4::identity();
|
||||
/// let m1 = Matrix4x4::identity();
|
||||
/// let m2 = Matrix4x4::identity();
|
||||
///
|
||||
/// assert_eq!(m1 * m2, i);
|
||||
/// ```
|
||||
fn mul(self, m2: Matrix4x4) -> Matrix4x4 {
|
||||
let m1 = self;
|
||||
let mut r: Matrix4x4 = Default::default();
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
r.m[i][j] = m1.m[i][0] * m2.m[0][j]
|
||||
+ m1.m[i][1] * m2.m[1][j]
|
||||
+ m1.m[i][2] * m2.m[2][j]
|
||||
+ m1.m[i][3] * m2.m[3][j];
|
||||
}
|
||||
}
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Matrix4x4 {
|
||||
fn eq(&self, rhs: &Matrix4x4) -> bool {
|
||||
let l = self.m;
|
||||
let r = rhs.m;
|
||||
for i in 0..4 {
|
||||
for j in 0..4 {
|
||||
let d = (l[i][j] - r[i][j]).abs();
|
||||
if d > f32::EPSILON {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<(usize, usize)> for Matrix4x4 {
|
||||
type Output = f32;
|
||||
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
|
||||
&self.m[row][col]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn construct4x4() {
|
||||
let m = Matrix4x4::new(
|
||||
[1., 2., 3., 4.],
|
||||
[5.5, 6.5, 7.5, 8.5],
|
||||
[9., 10., 11., 12.],
|
||||
[13.5, 14.5, 15.5, 16.5],
|
||||
);
|
||||
|
||||
assert_eq!(m[(0, 0)], 1.);
|
||||
assert_eq!(m[(0, 3)], 4.);
|
||||
assert_eq!(m[(1, 0)], 5.5);
|
||||
assert_eq!(m[(1, 2)], 7.5);
|
||||
assert_eq!(m[(2, 2)], 11.);
|
||||
assert_eq!(m[(3, 0)], 13.5);
|
||||
assert_eq!(m[(3, 2)], 15.5);
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user