Compare commits

...

48 Commits

Author SHA1 Message Date
Jon Janzen
6a5d95ae64 Sphere 2023-12-01 18:08:32 -07:00
Jon Janzen
e7f926ac1e Added position 2022-03-21 15:25:56 -06:00
Jon Janzen
cd297fd4a9 enforce point and vector for origin and direction 2022-03-11 18:35:11 -07:00
Jon Janzen
910570bf37 Can create ray 2022-03-11 18:23:26 -07:00
Jon Janzen
f2056615be added ray.rs 2022-03-11 18:15:39 -07:00
Jon Janzen
05c17b0179 no vscode 2022-01-07 08:50:57 -07:00
Jon Janzen
6e5af62c02 fixed warnings 2022-01-05 08:45:30 -07:00
Jon Janzen
be9abb56d9 removed extra .0s 2022-01-02 17:16:42 -07:00
Jon Janzen
b40d7c5b7b tranformation works with any number type 2022-01-01 18:11:39 -07:00
Jon Janzen
ae9316bc8e color can use any number type 2022-01-01 17:58:48 -07:00
Jon Janzen
e22fa3db61 matrix can handle any type for input 2022-01-01 17:54:03 -07:00
Jon Janzen
8a0f44144d Tuple point and vector take any time 2022-01-01 16:51:36 -07:00
Jon Janzen
71fc73bf1a tuple::new can take any type of number 2022-01-01 16:30:51 -07:00
Jon Janzen
b2ec53525d added draw cross 2021-10-03 16:19:44 -06:00
Jon Janzen
0b3132ee4f cleaned up a bit 2021-10-03 15:56:39 -06:00
Jon Janzen
1e57661ace Can make a clock face 2021-10-02 19:22:27 -06:00
Jon Janzen
ed3773a299 refactored main to make room for clock 2021-09-11 20:16:07 -06:00
Jon Janzen
1a414bc485 approx with numbers near 0 needs higher max relative 2021-09-11 19:59:59 -06:00
Jon Janzen
0dfa9248cb shearing 2021-09-03 20:36:37 -06:00
Jon Janzen
2c1b5354fa rotation complete 2021-09-03 20:17:18 -06:00
Jon Janzen
5fb429b9d0 rotation around x axis 2021-09-03 20:09:28 -06:00
Jon Janzen
e523852cd4 added scaling 2021-09-03 19:42:49 -06:00
Jon Janzen
48b5d0a400 Added multiplying a tuple by a matrix 2021-09-03 19:21:09 -06:00
Jon Janzen
be2d23914d transformation function 2021-09-03 19:13:26 -06:00
Jon Janzen
909aa52dd6 added transformations 2021-06-13 09:58:44 -06:00
Jon Janzen
6fd0e7608d Removed out.ppm 2021-06-13 09:54:26 -06:00
Jon Janzen
29bccd78fe completed rename. merged crates to features 2021-06-13 09:53:38 -06:00
Jon Janzen
90d3148335 Added some clarifying text to the output 2021-06-12 15:54:16 -06:00
Jon Janzen
267bc15e2e played iwht matrix 2021-04-02 19:02:57 -06:00
Jon Janzen
a4ffb3b7d6 Inverse tests, and Mul is references 2021-04-02 18:49:39 -06:00
Jon Janzen
e148f8ccef Added assert_matrix_eq to allow high max_relative for tests 2021-04-02 17:23:44 -06:00
Jon Janzen
ccb4184fb3 inverse works but rel equal doesn't 2021-04-02 17:15:04 -06:00
Jon Janzen
49b1744604 went back to vec
submatrixes and determinant can be used in the same method now
2021-04-02 16:33:31 -06:00
Jon Janzen
75f882f860 determinants can not function
const_generics are not done and I can not use 2 methods in one due to the errors
2021-04-02 15:58:25 -06:00
Jon Janzen
02c557c4a3 slightly better minor 2021-04-02 15:00:00 -06:00
Jon Janzen
d8fe799890 added cofactor 2021-04-02 14:58:55 -06:00
Jon Janzen
517cb40de8 minor without calling submatrix 2021-04-02 14:52:52 -06:00
Jon Janzen
6dcfb6863b Added minor 2021-04-02 14:37:49 -06:00
Jon Janzen
c9d2559cba fixed sub_matrix Width 2021-04-02 14:09:53 -06:00
Jon Janzen
bc4f061bcd added submatrixes
had to use in development features to allow math with const generics
2021-04-02 13:56:19 -06:00
Jon Janzen
704503a9a1 handled clippy 2021-03-31 15:52:21 -06:00
Jon Janzen
9c20d13833 allow rectangular matrixes 2021-03-31 15:38:00 -06:00
Jon Janzen
82360600fe Fixed transpose by moving to an iterative method 2021-03-31 15:30:08 -06:00
Jon Janzen
588acf36a3 added determinant 2021-03-31 15:19:48 -06:00
Jon Janzen
c78c1ed8f6 switched from vec to array with const generic size 2021-03-31 15:19:04 -06:00
Jon Janzen
8a3538c789 Added Cargo.lock 2021-03-30 19:05:34 -06:00
Jon Janzen
6c685aef15 transposition 2021-03-29 19:38:00 -06:00
Jon Janzen
f106344504 identity matrix 2021-03-29 19:18:21 -06:00
23 changed files with 1292 additions and 513 deletions

1
.gitignore vendored
View File

@@ -1,2 +1,3 @@
target
rusty-tags.vi
*.ppm

29
Cargo.lock generated
View File

@@ -1,10 +1,12 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "approx"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e"
dependencies = [
"num-traits",
]
@@ -16,17 +18,11 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "canvas"
version = "0.1.0"
dependencies = [
"color",
]
[[package]]
name = "color"
name = "features"
version = "0.1.0"
dependencies = [
"approx",
"num-traits",
]
[[package]]
@@ -39,17 +35,8 @@ dependencies = [
]
[[package]]
name = "structs"
name = "ray-tracer"
version = "0.1.0"
dependencies = [
"approx",
]
[[package]]
name = "tuples"
version = "0.1.0"
dependencies = [
"canvas",
"color",
"structs",
"features",
]

View File

@@ -1,5 +1,5 @@
[package]
name = "tuples"
name = "ray-tracer"
version = "0.1.0"
authors = ["Jon Janzen <jonjanzen@me.com>"]
edition = "2018"
@@ -7,6 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
canvas = { path = "canvas" }
color = { path = "color" }
structs = { path = "structs" }
features = { path = "features" }

39
canvas/Cargo.lock generated
View File

@@ -1,39 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "approx"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "canvas"
version = "0.1.0"
dependencies = [
"color",
]
[[package]]
name = "color"
version = "0.1.0"
dependencies = [
"approx",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]

View File

@@ -1,10 +0,0 @@
[package]
name = "canvas"
version = "0.1.0"
authors = ["Jon Janzen <jonjanzen@me.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
color = { path = "../color" }

32
color/Cargo.lock generated
View File

@@ -1,32 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "approx"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "color"
version = "0.1.0"
dependencies = [
"approx",
]
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]

View File

@@ -1,10 +1,12 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "approx"
version = "0.4.0"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e"
dependencies = [
"num-traits",
]
@@ -15,6 +17,14 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "features"
version = "0.1.0"
dependencies = [
"approx",
"num-traits",
]
[[package]]
name = "num-traits"
version = "0.2.14"
@@ -23,10 +33,3 @@ checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "structs"
version = "0.1.0"
dependencies = [
"approx",
]

View File

@@ -1,10 +1,10 @@
[package]
name = "color"
name = "features"
version = "0.1.0"
authors = ["Jon Janzen <jonjanzen@me.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
approx = "0.4"
approx = "0.5"
num-traits = "0.2"

View File

@@ -1,4 +1,4 @@
use color::Color;
use crate::color::Color;
pub struct Canvas {
width: usize,

View File

@@ -1,7 +1,6 @@
#[macro_use]
extern crate approx;
use crate::num_traits_cast;
use std::ops;
use num_traits::NumCast;
#[derive(Debug, Copy, Clone)]
pub struct Color {
@@ -11,11 +10,15 @@ pub struct Color {
}
impl Color {
pub fn new(red: f32, green: f32, blue: f32) -> Color {
pub fn new<R, G, B>(red: R, green: G, blue: B) -> Color
where R: NumCast,
G: NumCast,
B: NumCast,
{
Color {
red,
green,
blue,
red: num_traits_cast!(red),
green: num_traits_cast!(green),
blue: num_traits_cast!(blue),
}
}
@@ -115,7 +118,7 @@ mod tests {
fn add() {
let c1 = Color::new(0.9, 0.6, 0.75);
let c2 = Color::new(0.7, 0.1, 0.25);
let res = Color::new(1.6, 0.7, 1.0);
let res = Color::new(1.6, 0.7, 1);
assert_eq!(res, c1 + c2);
}
@@ -139,8 +142,8 @@ mod tests {
#[test]
fn mul() {
let c1 = Color::new(1.0, 0.2, 0.4);
let c2 = Color::new(0.9, 1.0, 0.1);
let c1 = Color::new(1, 0.2, 0.4);
let c2 = Color::new(0.9, 1, 0.1);
let res = Color::new(0.9, 0.2, 0.04);
assert_eq!(res, c1 * c2);

27
features/src/lib.rs Normal file
View File

@@ -0,0 +1,27 @@
#[macro_use]
extern crate approx;
extern crate num_traits;
pub mod structs;
pub mod color;
pub mod canvas;
pub mod matrix;
pub mod transformations;
pub mod ray;
pub mod sphere;
#[macro_export]
macro_rules! num_traits_cast {
($tt:expr) => {
num_traits::cast($tt).unwrap()
};
}
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}

687
features/src/matrix.rs Normal file
View File

@@ -0,0 +1,687 @@
use crate::structs::Tuple;
use crate::num_traits_cast;
use num_traits::NumCast;
use std::ops::{Index, IndexMut};
#[derive(Debug)]
pub struct Matrix {
matrix: Vec<Vec<f32>>,
}
impl Matrix {
pub fn default(width: usize, height: usize) -> Self {
Matrix {
matrix: vec![vec![0.0f32; width]; height],
}
}
pub fn from_array<T, const H: usize, const W: usize>(array: [[T; W]; H]) -> Matrix
where T: NumCast + Copy {
let mut matrix: Vec<Vec<f32>> = Vec::with_capacity(H);
for r in array.iter() {
let mut row: Vec<f32> = Vec::with_capacity(W);
for v in r.iter() {
row.push(num_traits_cast!(*v));
}
matrix.push(row);
}
Matrix {
matrix,
}
}
pub fn from_vec<T>(matrix: Vec<Vec<T>>) -> Matrix
where T: NumCast + Copy {
let mut matrix_f32 : Vec<Vec<f32>> = Vec::with_capacity(matrix.len());
for r in matrix.iter() {
let mut row: Vec<f32> = Vec::with_capacity(r.len());
for v in r.iter() {
row.push(num_traits_cast!(*v));
}
matrix_f32.push(row);
}
Matrix {
matrix: matrix_f32,
}
}
pub fn identity(size: usize) -> Matrix {
let mut m = Self::default(size, size);
for i in 0..m.matrix.len() {
m.matrix[i][i] = 1.0;
}
m
}
pub fn transpose(&mut self) {
for i in 0..self.matrix.len() {
for j in i..self.matrix[0].len() {
let v = self.matrix[i][j];
self.matrix[i][j] = self.matrix[j][i];
self.matrix[j][i] = v;
}
}
}
pub fn determinant(&self) -> f32 {
if self.matrix[0].len() == 2 {
self.matrix[0][0] * self.matrix[1][1] - self.matrix[0][1] * self.matrix[1][0]
} else {
let mut sum = 0.0;
for (col, val) in self.matrix[0].iter().enumerate().take(self.matrix[0].len()) {
sum += val * self.cofactor(0, col);
}
sum
}
}
pub fn minor(&self, row: usize, col: usize) -> f32 {
let m = self.sub_matrix(row, col);
let det = m.determinant();
det
}
pub fn cofactor(&self, row: usize, col: usize) -> f32 {
let minor = self.minor(row, col);
if (row + col) & 0x1 == 0 {
minor
} else {
minor * -1.0
}
}
pub fn sub_matrix(&self, skip_row: usize, skip_col: usize) -> Matrix
{
let mut m = Vec::<Vec<f32>>::with_capacity(self.matrix.len() - 1);
for (i, row) in self.matrix.iter().enumerate().take(self.matrix.len()) {
if i == skip_row { continue; }
let mut r = Vec::<f32>::with_capacity(row.len() - 1);
for (j, col) in row.iter().enumerate().take(row.len()) {
if j == skip_col { continue; }
r.push(*col);
}
m.push(r);
}
Matrix::from_vec(m)
}
pub fn is_invertable(&self) -> bool {
self.determinant() != 0.0
}
pub fn inverse(&self) -> Matrix {
// seems dangerous
if !self.is_invertable() {
panic!("We can't invert {:?}", self.matrix);
}
//let mut matrix: Vec<Vec<f32>> = Vec::with_capacity(self.matrix.len());
let mut matrix = Matrix::default(self.matrix.len(), self.matrix[0].len());
let det = self.determinant();
for (row_idx, row) in self.matrix.iter().enumerate().take(self.matrix.len()) {
for (col_idx, _) in row.iter().enumerate().take(row.len()) {
let c = self.cofactor(row_idx, col_idx);
let val = c / det;
matrix[col_idx][row_idx] = val;
}
}
matrix
}
}
impl Index<usize> for Matrix {
type Output = Vec<f32>;
fn index(&self, index: usize) -> &Self::Output {
&self.matrix[index]
}
}
impl IndexMut<usize> for Matrix {
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
&mut self.matrix[index]
}
}
impl PartialEq for Matrix {
fn eq(&self, _rhs: &Self) -> bool {
if self.matrix.len() != _rhs.matrix.len() {
return false;
}
for row_idx in 0..self.matrix.len() {
if self.matrix[row_idx].len() != _rhs.matrix[row_idx].len() {
return false;
}
for col_idx in 0..self.matrix[row_idx].len() {
if !relative_eq!(
self.matrix[row_idx][col_idx],
_rhs.matrix[row_idx][col_idx]) {
return false;
}
}
}
true
}
}
impl Matrix {
fn calc_val_for_mul(&self, row: usize, rhs: &Matrix, col: usize) -> f32 {
let mut sum = 0.0;
for i in 0..self.matrix.len() {
sum += self.matrix[row][i] * rhs.matrix[i][col];
}
sum
}
fn calc_val_for_mul_tuple(&self, row: usize, tuple: &Tuple) -> f32 {
(self.matrix[row][0] * tuple.x()) +
(self.matrix[row][1] * tuple.y()) +
(self.matrix[row][2] * tuple.z()) +
(self.matrix[row][3] * tuple.w())
}
}
impl std::ops::Mul<&Matrix> for &Matrix {
type Output = Matrix;
fn mul(self, _rhs: &Matrix) -> Matrix {
let mut result: Vec<Vec<f32>> = Vec::with_capacity(self.matrix.len());
for row in 0..self.matrix.len() {
let width = self.matrix[row].len();
let mut new_col = Vec::with_capacity(width);
for col in 0..width {
new_col.push( self.calc_val_for_mul(row, &_rhs, col));
}
result.push(new_col);
}
Matrix::from_vec(result)
}
}
impl std::ops::Mul<&Tuple> for &Matrix {
type Output = Tuple;
fn mul(self, _rhs: &Tuple) -> Tuple {
Tuple::new(
self.calc_val_for_mul_tuple(0, &_rhs),
self.calc_val_for_mul_tuple(1, &_rhs),
self.calc_val_for_mul_tuple(2, &_rhs),
self.calc_val_for_mul_tuple(3, &_rhs),
)
}
}
impl std::ops::Mul<&Matrix> for &Tuple {
type Output = Tuple;
fn mul(self, rhs: &Matrix) -> Tuple {
Tuple::new(
rhs.calc_val_for_mul_tuple(0, &self),
rhs.calc_val_for_mul_tuple(1, &self),
rhs.calc_val_for_mul_tuple(2, &self),
rhs.calc_val_for_mul_tuple(3, &self),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matrix_4x4() {
let m = [
[1.0, 2.0, 3.0, 4.0],
[5.5, 6.5, 7.5, 8.5],
[9.0, 10.0, 11.0, 12.0],
[13.5, 14.5, 15.5, 16.5],
];
let matrix = Matrix::from_array(m);
assert_eq!(1.0, matrix[0][0]);
assert_eq!(4.0, matrix[0][3]);
assert_eq!(5.5, matrix[1][0]);
assert_eq!(7.5, matrix[1][2]);
assert_eq!(11.0, matrix[2][2]);
assert_eq!(13.5, matrix[3][0]);
assert_eq!(15.5, matrix[3][2]);
}
#[test]
fn matrix_4x4_array() {
let m = [
[1.0, 2.0, 3.0, 4.0],
[5.5, 6.5, 7.5, 8.5],
[9.0, 10.0, 11.0, 12.0],
[13.5, 14.5, 15.5, 16.5],
];
let matrix = Matrix::from_array(m);
assert_eq!(1.0, matrix[0][0]);
assert_eq!(4.0, matrix[0][3]);
assert_eq!(5.5, matrix[1][0]);
assert_eq!(7.5, matrix[1][2]);
assert_eq!(11.0, matrix[2][2]);
assert_eq!(13.5, matrix[3][0]);
assert_eq!(15.5, matrix[3][2]);
}
#[test]
fn matrix_2x2() {
let m = [
[-3, 5,],
[1, 2,],
];
let matrix = Matrix::from_array(m);
assert_eq!(-3.0, matrix[0][0]);
assert_eq!(5.0, matrix[0][1]);
assert_eq!(1.0, matrix[1][0]);
assert_eq!(2.0, matrix[1][1]);
}
#[test]
fn matrix_3x3() {
let m = [
[-3, 5, 0],
[1, -2, -7],
[0, 1, 1],
];
let matrix = Matrix::from_array(m);
assert_eq!(-3.0, matrix[0][0]);
assert_eq!(-2.0, matrix[1][1]);
assert_eq!(1.0, matrix[2][2]);
}
#[test]
fn matrix_equality_a() {
let a = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 8, 7, 6],
[5, 4, 3, 2],
];
let m_a = Matrix::from_array(a);
let b = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 8, 7, 6],
[5, 4, 3, 2],
];
let m_b = Matrix::from_array(b);
assert_eq!(m_a, m_b);
}
#[test]
fn matrix_equality_b() {
let a = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 8, 7, 6],
[5, 4, 3, 2],
];
let m_a = Matrix::from_array(a);
let b = [
[2, 3, 4, 5],
[6, 7, 8, 9],
[8, 7, 6, 5],
[4, 3, 2, 1],
];
let m_b = Matrix::from_array(b);
assert_ne!(m_a, m_b);
}
#[test]
fn multiply() {
let matrix_a = Matrix::from_array([
[1, 2, 3, 4,],
[5, 6, 7, 8,],
[9, 8, 7, 6,],
[5, 4, 3, 2,],
]);
let matrix_b = Matrix::from_array([
[-2, 1, 2, 3,],
[3, 2, 1, -1,],
[4, 3, 6, 5,],
[1, 2, 7, 8,],
]);
let expected = Matrix::from_array([
[20, 22, 50, 48],
[44, 54, 114, 108],
[40, 58, 110, 102,],
[16, 26, 46, 42],
]);
assert_eq!(&matrix_a * &matrix_b, expected);
}
#[test]
fn multiply_by_tuple() {
let matrix = Matrix::from_array([
[1, 2, 3, 4],
[2, 4, 4, 2],
[8, 6, 4, 1],
[0, 0, 0, 1],
]);
let tuple = Tuple::new(1, 2, 3, 1);
let expected = Tuple::new(18, 24, 33, 1);
assert_eq!(&matrix * &tuple, expected);
}
#[test]
fn multiply_by_tuple_reverse() {
let matrix = Matrix::from_array([
[1, 2, 3, 4],
[2, 4, 4, 2],
[8, 6, 4, 1],
[0, 0, 0, 1],
]);
let tuple = Tuple::new(1, 2, 3, 1);
let expected = Tuple::new(18, 24, 33, 1);
assert_eq!(&tuple * &matrix, expected);
}
#[test]
fn matrix_by_identity() {
let matrix = Matrix::from_array([
[0, 1, 2, 4,],
[1, 2, 4, 8,],
[2, 4, 8, 16],
[4, 8, 16, 32,]
]);
let expected = Matrix::from_array([
[0, 1, 2, 4,],
[1, 2, 4, 8,],
[2, 4, 8, 16],
[4, 8, 16, 32,]
]);
assert_eq!(&matrix * &Matrix::identity(4), expected);
}
#[test]
fn tuple_by_identity() {
let t = Tuple::new(1, 2, 3, 4);
let expected = Tuple::new(1, 2, 3, 4);
assert_eq!(&Matrix::identity(4) * &t, expected);
}
#[test]
fn transposition() {
let mut m = Matrix::from_array([
[0, 9, 3, 0],
[9, 8, 0, 8],
[1, 8, 5, 3],
[0, 0, 5, 8],
]);
let expected = Matrix::from_array([
[0, 9, 1, 0],
[9, 8, 8, 0],
[3, 0, 5, 5],
[0, 8, 3, 8],
]);
m.transpose();
assert_eq!(m, expected);
}
#[test]
fn transpose_identity() {
let mut m = Matrix::identity(4);
m.transpose();
assert_eq!(m, Matrix::identity(4));
}
#[test]
fn determinant_2x2() {
let m = Matrix::from_array([
[1, 5],
[-3, 2],
]);
assert_eq!(17.0, m.determinant());
}
#[test]
fn submatrix_3x3() {
let start = Matrix::from_array([
[1, 5, 0],
[-3, 2, 7],
[0, 6, -3],
]);
let expected = Matrix::from_array([
[-3, 2],
[0, 6],
]);
assert_eq!(expected, start.sub_matrix(0, 2));
}
#[test]
fn submatrix_4x4() {
let start = Matrix::from_array([
[-6, 1, 1, 6],
[-8, 5, 8, 6],
[-1, 0, 8, 2],
[-7, 1, -1, 1],
]);
let expected = Matrix::from_array([
[-6, 1, 6],
[-8, 8, 6],
[-7, -1, 1],
]);
assert_eq!(expected, start.sub_matrix(2, 1));
}
#[test]
fn minor_3x3() {
let m = Matrix::from_array([
[3, 5, 0],
[2, -1, -7],
[6, -1, 5],
]);
let s = m.sub_matrix(1, 0);
assert_eq!(25.0, s.determinant());
assert_eq!(25.0, m.minor(1, 0));
}
#[test]
fn cofactor_3x3() {
let m = Matrix::from_array([
[3, 5, 0],
[2, -1, -7],
[6, -1, 5],
]);
assert_eq!(-12.0, m.minor(0, 0));
assert_eq!(-12.0, m.cofactor(0, 0));
assert_eq!(25.0, m.minor(1, 0));
assert_eq!(-25.0, m.cofactor(1, 0));
}
#[test]
fn determinant_3x3() {
let m = Matrix::from_array([
[1, 2, 6],
[-5, 8, -4],
[2, 6, 4],
]);
assert_eq!(56.0, m.cofactor(0, 0));
assert_eq!(12.0, m.cofactor(0, 1));
assert_eq!(-46.0, m.cofactor(0, 2));
assert_eq!(-196.0, m.determinant());
}
#[test]
fn determinant_4x4() {
let m = Matrix::from_array([
[-2, -8, 3, 5],
[-3, 1, 7, 3],
[1, 2, -9, 6],
[-6, 7, 7, -9],
]);
assert_eq!(690.0, m.cofactor(0, 0));
assert_eq!(447.0, m.cofactor(0, 1));
assert_eq!(210.0, m.cofactor(0, 2));
assert_eq!(51.0, m.cofactor(0, 3));
assert_eq!(-4071.0, m.determinant());
}
#[test]
fn can_invert_invertable() {
let m = Matrix::from_array([
[6, 4, 4, 4],
[5, 5, 7, 6],
[4, -9, 3, -7],
[9, 1, 7, -6],
]);
assert_eq!(-2120.0, m.determinant());
assert_eq!(true, m.is_invertable());
}
#[test]
fn can_invert_not_invertable() {
let m = Matrix::from_array([
[-4, 2, -2, -3],
[9, 6, 2, 6],
[0, -5, 1, -5],
[0, 0, 0, 0],
]);
assert_eq!(0.0, m.determinant());
assert_eq!(false, m.is_invertable());
}
pub fn assert_matrix_eq(_lhs: &Matrix, _rhs: &Matrix, max_relative: f32) -> bool {
if _lhs.matrix.len() != _rhs.matrix.len() {
return false;
}
for row_idx in 0.._lhs.matrix.len() {
if _lhs.matrix[row_idx].len() != _rhs.matrix[row_idx].len() {
return false;
}
for col_idx in 0.._lhs.matrix[row_idx].len() {
assert_relative_eq!( _lhs.matrix[row_idx][col_idx],
_rhs.matrix[row_idx][col_idx],
max_relative = max_relative);
}
}
true
}
#[test]
fn inverse() {
let m = Matrix::from_array::<i32, 4, 4>([
[-5, 2, 6, -8],
[1, -5, 1, 8],
[7, 7, -6, -7],
[1, -3, 7, 4],
]);
let b = m.inverse();
assert_eq!(532.0, m.determinant());
assert_eq!(-160.0, m.cofactor(2, 3));
assert_eq!(-160.0/532.0, b[3][2]);
assert_eq!(105.0, m.cofactor(3, 2));
assert_eq!(105.0/532.0, b[2][3]);
let expected = Matrix::from_array::<f32, 4, 4>([
[0.21805, 0.45113, 0.24060, -0.04511],
[-0.80827, -1.45677, -0.44361, 0.52068],
[-0.07895, -0.22368, -0.05263, 0.19737],
[-0.52256, -0.81392, -0.30075, 0.30639],
]);
assert_matrix_eq(&expected, &b, 0.0001);
}
#[test]
fn inverse_2() {
let m = Matrix::from_array([
[8, -5, 9, 2],
[7, 5, 6, 1],
[-6, 0, 9, 6],
[-3, 0, -9, -4],
]).inverse();
let expected = Matrix::from_array([
[-0.15385, -0.15385, -0.28205, -0.53846],
[-0.07692, 0.12308, 0.02564, 0.03077],
[0.35897, 0.35897, 0.43590, 0.92308],
[-0.69321, -0.69321, -0.76923, -1.92308],
]);
assert_matrix_eq(&expected, &m, 0.01);
}
#[test]
fn inverse_3() {
let m = Matrix::from_array([
[9, 3, 0, 9],
[-5, -2, -6, -3],
[-4, 9, 6, 4],
[-7, 6, 6, 2],
]).inverse();
let expected = Matrix::from_array([
[-0.04074, -0.07778, 0.14444, -0.22222],
[-0.07778, 0.03333, 0.36667, -0.33333],
[-0.02901, -0.14630, -0.10926, 0.12963],
[0.17778, 0.06667, -0.26667, 0.33333],
]);
assert_matrix_eq(&expected, &m, 0.01);
}
#[test]
fn multiply_by_inverse() {
let a = Matrix::from_array([
[3, -9, 7, 3],
[3, -8, 2, -9],
[-4, 4, 4, 1],
[-6, 5, -1, 1],
]);
let b = Matrix::from_array([
[8, 2, 2, 2],
[3, -1, 7, 0],
[7, 0, 5, 4],
[6, -2, 0, 5],
]);
let c = &a * &b;
let r = &c * &b.inverse();
let expected = Matrix::from_array([
[3, -9, 7, 3],
[3, -8, 2, -9],
[-4, 4, 4, 1],
[-6, 5, -1, 1],
]);
assert_matrix_eq(&r, &expected, 0.00001);
}
}

70
features/src/ray.rs Normal file
View File

@@ -0,0 +1,70 @@
use crate::num_traits_cast;
use num_traits::NumCast;
use crate::structs::Tuple;
#[derive(Debug, PartialEq)]
pub struct Ray {
origin: Tuple,
direction: Tuple,
}
impl Ray {
pub fn new(origin: Tuple, direction: Tuple) -> Option<Ray> {
if !origin.is_point() || !direction.is_vector() {
None
} else {
Some(Ray {
origin,
direction,
})
}
}
pub fn position<X: NumCast>(&self, time: X) -> Tuple {
self.origin + self.direction * num_traits_cast!(time)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn create_a_ray() {
let origin = Tuple::point(1, 2, 3);
let direction = Tuple::vector(4, 5, 6);
let ray = Ray::new(origin, direction).unwrap();
assert_eq!(ray.origin, origin);
assert_eq!(ray.direction, direction);
}
#[test]
pub fn origin_must_be_point() {
let direction = Tuple::vector::<i32, i32, i32>(4, 5, 6);
let ray = Ray::new(direction, direction);
assert_eq!(ray, None);
}
#[test]
pub fn direction_must_be_vector() {
let point = Tuple::point::<i32, i32, i32>(4, 5, 6);
let ray = Ray::new(point, point);
assert_eq!(ray, None);
}
#[test]
pub fn create_and_query_ray() {
let origin = Tuple::point(2, 3, 4);
let direction = Tuple::vector(1, 0, 0);
let ray = Ray::new(origin, direction).unwrap();
assert_eq!(Tuple::point(2, 3, 4), ray.position(0));
assert_eq!(Tuple::point(3, 3, 4), ray.position(1));
assert_eq!(Tuple::point(1, 3, 4), ray.position(-1));
assert_eq!(Tuple::point(4.5, 3, 4), ray.position(2.5));
}
}

20
features/src/sphere.rs Normal file
View File

@@ -0,0 +1,20 @@
use crate::ray::Ray;
use crate::structs::Tuple;
#[cfg(test)]
mod tests {
use super::*;
#[test]
pub fn ray_intersects_with_sphere_at_two_points() {
let ray = Ray::new(Tuple::point(0, 0, -5), Tuple::vector(0, 0, 1)).unwrap();
let sphere = Sphere::new();
let xs = intesect(sphere, ray);
assert_eq!(2, xs.count());
assert_eq!(4.0, xs[0]);
assert_eq!(6.0, xs[2]);
}
}

View File

@@ -1,9 +1,10 @@
#[macro_use]
extern crate approx;
use crate::num_traits_cast;
use std::fmt;
use std::ops;
use num_traits::NumCast;
#[derive(Debug, Copy, Clone)]
pub struct Tuple {
x: f32,
@@ -13,30 +14,51 @@ pub struct Tuple {
}
impl Tuple {
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Tuple {
pub fn new<X, Y, Z, W>(x: X, y: Y, z: Z, w: W) -> Tuple
where X: NumCast,
Y: NumCast,
Z: NumCast,
W: NumCast,
{
Tuple {
x,
y,
z,
w,
x: num_traits_cast!(x),
y: num_traits_cast!(y),
z: num_traits_cast!(z),
w: num_traits_cast!(w),
}
}
pub fn point(x: f32, y: f32, z: f32) -> Tuple {
pub fn point_zero() -> Tuple {
Tuple {
x,
y,
z,
x: 0.0,
y: 0.0,
z: 0.0,
w: 1.0
}
}
pub fn point<T, U, V>(x: T, y: U, z: V) -> Tuple
where T: NumCast,
U: NumCast,
V: NumCast,
{
Tuple {
x: num_traits_cast!(x),
y: num_traits_cast!(y),
z: num_traits_cast!(z),
w: 1.0,
}
}
pub fn vector(x: f32, y: f32, z: f32) -> Tuple {
pub fn vector<X, Y, Z>(x: X, y: Y, z: Z) -> Tuple
where X: NumCast,
Y: NumCast,
Z: NumCast {
Tuple {
x,
y,
z,
x: num_traits_cast!(x),
y: num_traits_cast!(y),
z: num_traits_cast!(z),
w: 0.0,
}
}
@@ -419,4 +441,39 @@ mod tests {
let b_cross_a = Tuple::vector(1.0, -2.0, 1.0);
assert_eq!(b_cross_a, b.cross(&a));
}
#[test]
fn works_with_i32() {
let a = Tuple::new(1, 2, 3, 0);
assert_eq!(1.0, a.x());
assert_eq!(2.0, a.y());
assert_eq!(3.0, a.z());
}
#[test]
fn works_with_mixed_types() {
let a = Tuple::new(1.1, 2.2, 3, 0);
assert_eq!(1.1, a.x());
assert_eq!(2.2, a.y());
assert_eq!(3.0, a.z());
}
#[test]
fn point_with_mixed_types() {
let a = Tuple::point(1.0, 2.2, 3);
assert_eq!(1.0, a.x());
assert_eq!(2.2, a.y());
assert_eq!(3.0, a.z());
assert_eq!(1.0, a.w());
}
#[test]
fn vector_with_mixed_types() {
let a = Tuple::vector(1.0, 2.2, 3);
assert_eq!(1.0, a.x());
assert_eq!(2.2, a.y());
assert_eq!(3.0, a.z());
assert_eq!(0.0, a.w());
}
}

View File

@@ -0,0 +1,261 @@
use crate::matrix::Matrix;
use crate::num_traits_cast;
use num_traits::NumCast;
impl Matrix {
pub fn translation<X, Y, Z>(x: X, y: Y, z: Z) -> Self
where X: NumCast,
Y: NumCast,
Z: NumCast, {
Matrix::from_array([
[1.0, 0.0, 0.0, num_traits_cast!(x)],
[0.0, 1.0, 0.0, num_traits_cast!(y)],
[0.0, 0.0, 1.0, num_traits_cast!(z)],
[0.0, 0.0, 0.0, 1.0],
])
}
pub fn scaling<X, Y, Z>(x: X, y: Y, z: Z) -> Self
where X: NumCast,
Y: NumCast,
Z: NumCast, {
Matrix::from_array([
[num_traits_cast!(x), 0.0, 0.0, 0.0],
[0.0, num_traits_cast!(y), 0.0, 0.0],
[0.0, 0.0, num_traits_cast!(z), 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
pub fn rotation_x<R: NumCast>(r: R) -> Self {
let r: f32 = num_traits_cast!(r);
Matrix::from_array([
[1.0 , 0.0, 0.0, 0.0],
[0.0, r.cos(), -1.0 * r.sin(), 0.0],
[0.0, r.sin(), r.cos() , 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
pub fn rotation_y(r: f32) -> Self {
Matrix::from_array([
[r.cos(), 0.0, r.sin(), 0.0],
[0.0, 1.0, 0.0, 0.0],
[-1.0 * r.sin(), 0.0, r.cos() , 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
pub fn rotation_z<R: NumCast>(r: R) -> Self {
let r : f32 = num_traits_cast!(r);
Matrix::from_array([
[r.cos(), -1.0 * r.sin(), 0.0, 0.0],
[r.sin(), r.cos(), 0.0, 0.0],
[0.0, 0.0, 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
pub fn shearing<XY, XZ, YX, YZ, ZX, ZY>(xy: XY, xz: XZ, yx: YX, yz: YZ, zx: ZX, zy: ZY) -> Self
where XY: NumCast,
XZ: NumCast,
YX: NumCast,
YZ: NumCast,
ZX: NumCast,
ZY: NumCast,
{
Matrix::from_array([
[1.0, num_traits_cast!(xy), num_traits_cast!(xz), 0.0],
[num_traits_cast!(yx), 1.0, num_traits_cast!(yz), 0.0],
[num_traits_cast!(zx), num_traits_cast!(zy), 1.0, 0.0],
[0.0, 0.0, 0.0, 1.0],
])
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::structs::Tuple;
use std::f32::consts::PI;
#[test]
fn multiply_by_a_translations_matrix() {
let transform = Matrix::translation(5, -3, 2);
let p = Tuple::point(-3, 4, 5);
let expected_point = Tuple::point(2, 1, 7);
let translated_point = &p * &transform;
assert_eq!(expected_point, translated_point);
}
#[test]
fn multiply_by_the_inverse_of_a_translation_matrix() {
let transform = Matrix::translation(5, -3, 2);
let inv = transform.inverse();
let p = Tuple::point(-3, 4, 5);
let expected_point = Tuple::point(-8, 7, 3);
assert_eq!(&inv * &p, expected_point);
}
#[test]
fn translation_does_not_affect_vectors() {
let transform = Matrix::translation(5, -3, 2);
let v = Tuple::vector(-3, 4, 5);
assert_eq!(&transform * &v, v);
}
#[test]
fn scaling_matrix_applied_to_point() {
let transform = Matrix::scaling(2, 3, 4);
let p = Tuple::point(-4, 6, 8);
let expected = Tuple::point(-8, 18, 32);
assert_eq!(&transform * &p, expected);
}
#[test]
fn scaling_matrix_apled_to_vector() {
let transform = Matrix::scaling(2, 3, 4);
let v = Tuple::vector(-4, 6, 8);
assert_eq!(&transform * &v, Tuple::vector(-8, 18, 32));
}
#[test]
fn multiplying_inverse_of_scaling_matrix() {
let transform = Matrix::scaling(2, 3, 4);
let inv = transform.inverse();
let v = Tuple::vector(-4, 6, 8);
assert_eq!(&inv * &v, Tuple::vector(-2, 2, 2));
}
#[test]
fn reflection_is_scaling_by_a_negative_value() {
let transform = Matrix::scaling(-1, 1, 1);
let p = Tuple::point(2, 3, 4);
assert_eq!(&transform * &p, Tuple::point(-2, 3, 4));
}
fn sqrt_of_2() -> f32 {
(2 as f32).sqrt()
}
#[test]
fn rotating_a_point_around_the_x_axis() {
let p = Tuple::point(0, 1, 0);
let half_quarter = Matrix::rotation_x(PI / 4.0);
let full_quarter = Matrix::rotation_x(PI / 2.0);
assert_eq!(&half_quarter * &p, Tuple::point(0.0, sqrt_of_2() / 2.0, sqrt_of_2() / 2.0));
assert_eq!(&full_quarter * &p, Tuple::point(0.0, 0.0, 1.0));
}
#[test]
fn inverse_of_an_x_rotation_rotates_opposite_direction() {
let p = Tuple::point(0, 1, 0);
let half_quarter = Matrix::rotation_x(PI / 4.0);
let inv = half_quarter.inverse();
assert_eq!(&inv * &p, Tuple::point(0.0, sqrt_of_2() / 2.0, -1.0 * sqrt_of_2() / 2.0));
}
#[test]
fn rotating_point_around_y_axis() {
let p = Tuple::point(0, 0, 1);
let half_quarter = Matrix::rotation_y(PI / 4.0);
let full_quarter = Matrix::rotation_y(PI / 2.0);
assert_eq!(&half_quarter * &p, Tuple::point(sqrt_of_2() / 2.0, 0.0, sqrt_of_2() / 2.0));
assert_eq!(&full_quarter * &p, Tuple::point(1.0, 0.0, 0.0));
}
#[test]
fn rotating_point_around_z_axis() {
let p = Tuple::point(0, 1, 0);
let half_quarter = Matrix::rotation_z(PI / 4.0);
let full_quarter = Matrix::rotation_z(PI / 2.0);
assert_eq!(&half_quarter * &p, Tuple::point(-1.0 * sqrt_of_2() / 2.0, sqrt_of_2() / 2.0, 0.0));
assert_eq!(&full_quarter * &p, Tuple::point(-1.0, 0.0, 0.0));
}
#[test]
fn shearing_transform_moves_x_in_proportion_to_y() {
let transform = Matrix::shearing(1, 0, 0, 0, 0, 0);
let p = Tuple::point(2, 3, 4);
assert_eq!(&transform * &p, Tuple::point(5, 3, 4));
}
#[test]
fn shearing_transform_moves_x_in_proportion_to_z() {
let transform = Matrix::shearing(0, 1, 0, 0, 0, 0);
let p = Tuple::point(2, 3, 4);
assert_eq!(&transform * &p, Tuple::point(6, 3, 4));
}
#[test]
fn shearing_transform_moves_y_in_proportion_to_z() {
let transform = Matrix::shearing(0, 0, 0, 1, 0, 0);
let p = Tuple::point(2, 3, 4);
assert_eq!(&transform * &p, Tuple::point(2, 7, 4));
}
#[test]
fn shearing_transform_moves_z_in_proportion_to_x() {
let transform = Matrix::shearing(0, 0, 0, 0, 1, 0);
let p = Tuple::point(2, 3, 4);
assert_eq!(&transform * &p, Tuple::point(2, 3, 6));
}
#[test]
fn shearing_transform_moves_z_in_proportion_to_y() {
let transform = Matrix::shearing(0, 0, 0, 0, 0, 1);
let p = Tuple::point(2, 3, 4);
assert_eq!(&transform * &p, Tuple::point(2, 3, 7));
}
#[test]
fn individual_transformations_are_applied_in_sequence() {
let p = Tuple::point(1, 0, 1);
let a = Matrix::rotation_x(PI / 2.0);
let b = Matrix::scaling(5, 5, 5);
let c = Matrix::translation(10, 5, 7);
let p2 = &a * &p;
assert_eq!(p2, Tuple::point(1, -1, 0));
let p3 = &b * &p2;
// assert_eq!(p3, Tuple::point(5, -5, -00));
assert_relative_eq!(p3.x(), 5.0);
assert_relative_eq!(p3.y(), -5.0);
//assert_relative_eq!(p3.z(), 0, 1);
// I don't think the approx crate can handle numbers close to 0 appropriately
assert_eq!(true, relative_eq!(p3.z(), 0.0, max_relative = 1.0));
assert_relative_eq!(p3.w(), 1.0);
let p4 = &c * &p3;
assert_eq!(p4, Tuple::point(15, 0, 7));
}
#[test]
fn chained_transformations_must_be_applied_in_reverse_order() {
let p = Tuple::point(1, 0, 1);
let a = Matrix::rotation_x(PI / 2.0);
let b = Matrix::scaling(5, 5, 5);
let c = Matrix::translation(10, 5, 7);
let t = &(&c * &b) * &a;
assert_eq!(&t * &p, Tuple::point(15, 0, 7));
}
}

View File

@@ -104,7 +104,7 @@ fn cross(lhs: PointVector, rhs: PointVector) -> PointVector {
}
#[cfg(test)]
mod tests {
pub mod tests {
use super::*;
#[test]
@@ -318,4 +318,6 @@ mod tests {
assert_eq!(b_cross_a, cross(b, a));
}
pub fn
}

View File

@@ -1,11 +0,0 @@
[package]
name = "matrix"
version = "0.1.0"
authors = ["Jon Janzen <jonjanzen@me.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
approx = "0.4"
structs = { path = "../structs" }

View File

@@ -1,280 +0,0 @@
#[macro_use]
extern crate approx;
use structs::Tuple;
use std::ops::Index;
#[derive(Debug)]
pub struct Matrix {
matrix: Vec<Vec<f32>>,
}
impl Matrix {
pub fn new(matrix: Vec<Vec<f32>>) -> Matrix {
Matrix {
matrix: matrix,
}
}
pub fn from_array(matrix: [[f32; 4]; 4]) -> Matrix {
let mut m = Vec::with_capacity(4);
for row in 0..matrix.len() {
let mut r = Vec::with_capacity(4);
for col in 0..matrix[row].len() {
r.push(matrix[row][col]);
}
m.push(r);
}
Matrix {
matrix: m,
}
}
pub fn new_empty(row_count: usize, col_count: usize) -> Matrix {
let mut rows = Vec::with_capacity(row_count);
for _ in 0..row_count {
let mut col = Vec::with_capacity(col_count);
for _ in 0..col_count {
col.push(0.0);
}
rows.push(col);
}
Matrix::new(rows)
}
pub fn new_empty_4() -> Matrix {
Matrix::new_empty(4, 4)
}
}
impl Index<usize> for Matrix {
type Output = Vec<f32>;
fn index(&self, index: usize) -> &Self::Output {
&self.matrix[index]
}
}
impl PartialEq for Matrix {
fn eq(&self, _rhs: &Self) -> bool {
if self.matrix.len() != _rhs.matrix.len() {
return false;
}
for row_idx in 0..self.matrix.len() {
if self.matrix[row_idx].len() != _rhs.matrix[row_idx].len() {
return false;
}
for col_idx in 0..self.matrix[row_idx].len() {
if !relative_eq!(self.matrix[row_idx][col_idx], _rhs.matrix[row_idx][col_idx]) {
return false;
}
}
}
return true;
}
}
impl Matrix {
fn calc_val_for_mul(&self, row: usize, rhs: &Matrix, col: usize) -> f32 {
let mut sum = 0.0;
for i in 0..self.matrix.len() {
sum += self.matrix[row][i] * rhs.matrix[i][col];
}
sum
}
fn calc_val_for_mul_tuple(&self, row: usize, tuple: &Tuple) -> f32 {
(self.matrix[row][0] * tuple.x()) +
(self.matrix[row][1] * tuple.y()) +
(self.matrix[row][2] * tuple.z()) +
(self.matrix[row][3] * tuple.w())
}
}
impl std::ops::Mul<Matrix> for Matrix {
type Output = Matrix;
fn mul(self, _rhs: Matrix) -> Matrix {
let row_count = self.matrix.len();
let col_count = self.matrix[0].len();
let mut rows = Vec::with_capacity(row_count);
for row in 0..row_count {
let mut cols = Vec::with_capacity(col_count);
for col in 0..col_count {
cols.push(self.calc_val_for_mul(row, &_rhs, col));
}
rows.push(cols);
}
Matrix::new(rows)
}
}
impl std::ops::Mul<Tuple> for Matrix {
type Output = Tuple;
fn mul(self, _rhs: Tuple) -> Tuple {
Tuple::new(
self.calc_val_for_mul_tuple(0, &_rhs),
self.calc_val_for_mul_tuple(1, &_rhs),
self.calc_val_for_mul_tuple(2, &_rhs),
self.calc_val_for_mul_tuple(3, &_rhs),
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn matrix_4x4() {
let m = vec![
vec![1.0, 2.0, 3.0, 4.0],
vec![5.5, 6.5, 7.5, 8.5],
vec![9.0, 10.0, 11.0, 12.0],
vec![13.5, 14.5, 15.5, 16.5],
];
let matrix = Matrix::new(m);
assert_eq!(1.0, matrix[0][0]);
assert_eq!(4.0, matrix[0][3]);
assert_eq!(5.5, matrix[1][0]);
assert_eq!(7.5, matrix[1][2]);
assert_eq!(11.0, matrix[2][2]);
assert_eq!(13.5, matrix[3][0]);
assert_eq!(15.5, matrix[3][2]);
}
#[test]
fn matrix_4x4_array() {
let m = [
[1.0, 2.0, 3.0, 4.0],
[5.5, 6.5, 7.5, 8.5],
[9.0, 10.0, 11.0, 12.0],
[13.5, 14.5, 15.5, 16.5],
];
let matrix = Matrix::from_array(m);
assert_eq!(1.0, matrix[0][0]);
assert_eq!(4.0, matrix[0][3]);
assert_eq!(5.5, matrix[1][0]);
assert_eq!(7.5, matrix[1][2]);
assert_eq!(11.0, matrix[2][2]);
assert_eq!(13.5, matrix[3][0]);
assert_eq!(15.5, matrix[3][2]);
}
#[test]
fn matrix_2x2() {
let m = vec![
vec![-3.0, 5.0,],
vec![1.0, 2.0,],
];
let matrix = Matrix::new(m);
assert_eq!(-3.0, matrix[0][0]);
assert_eq!(5.0, matrix[0][1]);
assert_eq!(1.0, matrix[1][0]);
assert_eq!(2.0, matrix[1][1]);
}
#[test]
fn matrix_3x3() {
let m = vec![
vec![-3.0, 5.0, 0.0],
vec![1.0, -2.0, -7.0],
vec![0.0, 1.0, 1.0],
];
let matrix = Matrix::new(m);
assert_eq!(-3.0, matrix[0][0]);
assert_eq!(-2.0, matrix[1][1]);
assert_eq!(1.0, matrix[2][2]);
}
#[test]
fn matrix_equality_a() {
let a = vec![
vec![1.0, 2.0, 3.0, 4.0],
vec![5.0, 6.0, 7.0, 8.0],
vec![9.0, 8.0, 7.0, 6.0],
vec![5.0, 4.0, 3.0, 2.0],
];
let m_a = Matrix::new(a);
let b = vec![
vec![1.0, 2.0, 3.0, 4.0],
vec![5.0, 6.0, 7.0, 8.0],
vec![9.0, 8.0, 7.0, 6.0],
vec![5.0, 4.0, 3.0, 2.0],
];
let m_b = Matrix::new(b);
assert_eq!(m_a, m_b);
}
#[test]
fn matrix_equality_b() {
let a = vec![
vec![1.0, 2.0, 3.0, 4.0],
vec![5.0, 6.0, 7.0, 8.0],
vec![9.0, 8.0, 7.0, 6.0],
vec![5.0, 4.0, 3.0, 2.0],
];
let m_a = Matrix::new(a);
let b = vec![
vec![2.0, 3.0, 4.0, 5.0],
vec![6.0, 7.0, 8.0, 9.0],
vec![8.0, 7.0, 6.0, 5.0],
vec![4.0, 3.0, 2.0, 1.0],
];
let m_b = Matrix::new(b);
assert_ne!(m_a, m_b);
}
#[test]
fn multiply() {
let matrix_a = Matrix::from_array([
[1.0, 2.0, 3.0, 4.0,],
[5.0, 6.0, 7.0, 8.0,],
[9.0, 8.0, 7.0, 6.0,],
[5.0, 4.0, 3.0, 2.0,],
]);
let matrix_b = Matrix::from_array([
[-2.0, 1.0, 2.0, 3.0,],
[3.0, 2.0, 1.0, -1.0,],
[4.0, 3.0, 6.0, 5.0,],
[1.0, 2.0, 7.0, 8.0,],
]);
let expected = Matrix::from_array([
[20.0, 22.0, 50.0, 48.0],
[44.0, 54.0, 114.0, 108.0],
[40.0, 58.0, 110.0, 102.0,],
[16.0, 26.0, 46.0, 42.0],
]);
assert_eq!(matrix_a * matrix_b, expected);
}
#[test]
fn multiply_by_tuple() {
let matrix = Matrix::from_array([
[1.0, 2.0, 3.0, 4.0],
[2.0, 4.0, 4.0, 2.0],
[8.0, 6.0, 4.0, 1.0],
[0.0, 0.0, 0.0, 1.0],
]);
let tuple = Tuple::new(1.0, 2.0, 3.0, 1.0);
let expected = Tuple::new(18.0, 24.0, 33.0, 1.0);
assert_eq!(matrix * tuple, expected);
}
}

View File

@@ -1,7 +1,9 @@
use canvas::Canvas;
use color::Color;
use structs::Tuple;
use features::canvas::Canvas;
use features::color::Color;
use features::structs::Tuple;
use features::matrix::Matrix;
use std::f32::consts::PI;
use std::fmt;
use std::fs::File;
use std::io::prelude::*;
@@ -51,33 +53,54 @@ impl fmt::Display for Projectile {
fn init_env() -> Environment {
Environment {
gravity: Tuple::vector(0.0, 0.0, -0.98),
wind: Tuple::vector(0.0, 0.0, 0.0),
gravity: Tuple::vector(0, 0, -0.98),
wind: Tuple::vector(0, 0, 0),
}
}
fn main() {
fn write_canvas_to_file(canvas: Canvas, file_path: &str) {
let ppm = canvas.to_ppm();
let mut f = match File::create(file_path) {
Ok(f) => f,
Err(e) => panic!("file error. {}", e),
};
let _ = match f.write_all(ppm.as_bytes()) {
Ok(w) => w,
Err(e) => panic!("did not write. {}", e),
};
}
fn write_point(canvas: &mut Canvas, point: &Tuple, color: Color) {
canvas.write_pixel(point.x() as usize, point.y() as usize, color);
}
fn before_clock() {
let env = init_env();
let mut ball = Projectile::new(
Tuple::point(0.0, 0.0, 0.0),
Tuple::vector(2.2, 0.0, 21.0),
Tuple::point(0, 0, 0),
Tuple::vector(2.2, 0, 21.0),
);
let mut ball2 = Projectile::new(
Tuple::point(195.0, 0.0, 0.0),
Tuple::vector(-2.2, 0.0, 21.0),
Tuple::point(195, 0, 0),
Tuple::vector(-2.2, 0, 21),
);
let mut ball3 = Projectile::new(
Tuple::point(299.0, 0.0, 0.0),
Tuple::vector(-2.2, 0.0, 21.0),
Tuple::point(299, 0, 0),
Tuple::vector(-2.2, 0, 21),
);
let mut canvas = Canvas::new(300, 300);
let color = Color::new(1.0, 0.0, 0.0);
let color2 = Color::new(0.0, 1.0, 0.0);
let color3 = Color::new(0.0, 0.0, 1.0);
let color = Color::new(1, 0, 0);
let color2 = Color::new(0, 1, 0);
let color3 = Color::new(0, 0, 1);
loop {
canvas.write_pixel(ball.position.x() as usize, canvas.height() - (ball.position.z() as usize) - 1, color);
canvas.write_pixel(ball2.position.x() as usize, canvas.height() - (ball2.position.z() as usize) - 1, color2);
@@ -94,16 +117,80 @@ fn main() {
}
}
let ppm = canvas.to_ppm();
write_canvas_to_file(canvas, "out.ppm");
let i = Matrix::identity(4);
println!("The identity matrix is: {:#?}", i);
let inverse_i = i.inverse();
println!("The inverse of the identity matrix is: {:#?}", inverse_i);
let mut f = match File::create("out.ppm") {
Ok(f) => f,
Err(e) => panic!("file error. {}", e),
};
let _ = match f.write_all(ppm.as_bytes()) {
Ok(w) => w,
Err(e) => panic!("did not write. {}", e),
};
let mut a = Matrix::from_array([
[1, 2, 3],
[4, 5, 6],
[7, 8, 1]
]);
println!("Matrix: {:?}", a);
let mut b = a.inverse();
println!("Inverse: {:?}", b);
let c = &a * &b;
println!("Multiplied by inverse: {:?}", c);
b.transpose();
a.transpose();
let at = a.inverse();
println!("inverse then transpose: {:?}", b);
println!("transpose then inverse: {:?}", at);
let t = Tuple::point(1, 2, 3);
let mut id = Matrix::identity(4);
id[1][3] = -3.0;
let q = &id * &t;
println!("{:?}", id);
println!("{:?}", q);
}
fn main() {
before_clock();
clock();
}
fn draw_cross(canvas: &mut Canvas, start: &Tuple, color: Color) {
canvas.write_pixel(start.x() as usize, start.y() as usize, color);
let top = start * &Matrix::translation(1, 0, 0);
write_point(canvas, &top, color);
let right = start * &Matrix::translation(0, 1, 0);
write_point(canvas, &right, color);
let bottom = start * &Matrix::translation(-1, 0, 0);
write_point(canvas, &bottom, color);
let left = start * &Matrix::translation(0, -1, 0);
write_point(canvas, &left, color);
}
fn clock() {
println!("Starting clock!");
let mut canvas = Canvas::new(1024, 1024);
let color = Color::new(1, 0, 0);
let middle = 1024 / 2;
let middle_point = &Tuple::point_zero() * &Matrix::translation(middle, middle, 0);
draw_cross(&mut canvas, &middle_point, Color::new(0, 1, 0));
for i in 1..13 {
let center = Tuple::point_zero();
let center = &Matrix::translation(0, -24, 0) * &center;
let center = &Matrix::scaling(0, 10, 0) * &center;
let center = &Matrix::rotation_z((i as f32 / 12.0) * (2.0 * PI)) * &center;
let center = &Matrix::translation(middle, middle, 0) * &center;
draw_cross(&mut canvas, &center, color);
}
write_canvas_to_file(canvas, "clock.ppm");
println!("Ending clock!");
}

View File

@@ -1,10 +0,0 @@
[package]
name = "structs"
version = "0.1.0"
authors = ["Jon Janzen <jonjanzen@me.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
approx = "0.4"

32
tuples/Cargo.lock generated
View File

@@ -1,32 +0,0 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "approx"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "num-traits"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "tuples"
version = "0.1.0"
dependencies = [
"approx",
]

View File

@@ -1,10 +0,0 @@
[package]
name = "tuples"
version = "0.1.0"
authors = ["Jon Janzen <jonjanzen@me.com>"]
edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
approx = "0.4"