Compare commits

...

64 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
Jon Janzen
5ca76b424a multiple matrix by tuple 2021-03-28 19:16:14 -06:00
Jon Janzen
6fd1eb49b7 wrote test for matix multiplication 2021-03-28 18:29:18 -06:00
Jon Janzen
c47d3448a3 can create from arrays 2021-03-28 18:19:56 -06:00
Jon Janzen
f24280c866 matrix with equality 2021-03-28 18:00:15 -06:00
Jon Janzen
2a602f5804 wrote first bit of matrix 2021-03-26 20:01:41 -06:00
Jon Janzen
7bb7fa8ab4 added matrix init 2021-03-26 16:32:00 -06:00
Jon Janzen
6dafed1195 multi-ball 2021-03-21 19:49:24 -06:00
Jon Janzen
750435847e start from 0 works 2021-03-21 19:27:16 -06:00
Jon Janzen
1f33d87193 can make a ppm 2021-03-20 20:12:47 -06:00
Jon Janzen
842c93568b assert new line at end of ppm 2021-03-05 17:32:47 -07:00
Jon Janzen
aa4e01d3f7 added canvas sizes 2021-03-05 17:14:06 -07:00
Jon Janzen
1fc84c7d1f Can create canvas 2021-02-19 19:58:52 -07:00
Jon Janzen
5232e69ec1 Added color 2021-02-13 17:09:47 -07:00
Jon Janzen
06d08942f0 Played with values 2021-02-07 14:59:00 -07:00
Jon Janzen
bf1afc092e Created a Projectile and allowed it to be operated on by wind and gravity 2021-02-07 14:49:14 -07:00
Jon Janzen
c863cf5a8b Made methods pub so I could use them 2021-02-07 14:48:45 -07:00
17 changed files with 1738 additions and 86 deletions

1
.gitignore vendored
View File

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

39
Cargo.lock generated
View File

@@ -1,5 +1,42 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "tuples"
name = "approx"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e"
dependencies = [
"num-traits",
]
[[package]]
name = "autocfg"
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"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [
"autocfg",
]
[[package]]
name = "ray-tracer"
version = "0.1.0"
dependencies = [
"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,3 +7,4 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
features = { path = "features" }

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 = "tuples"
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"

172
features/src/canvas.rs Normal file
View File

@@ -0,0 +1,172 @@
use crate::color::Color;
pub struct Canvas {
width: usize,
height: usize,
pixels: Vec<Vec<Color>>,
}
impl Canvas {
pub fn new(width: usize, height: usize) -> Canvas {
Canvas {
width,
height,
pixels : vec![vec![Color::new(0.0, 0.0, 0.0); width]; height],
}
}
pub fn width(&self) -> usize {
self.width
}
pub fn height(&self) -> usize {
self.height
}
}
impl Canvas {
pub fn write_pixel(&mut self, x: usize, y: usize, color: Color) {
self.pixels[y][x] = color;
}
pub fn pixel(&self, x: usize, y: usize) -> Color {
self.pixels[y][x]
}
fn header(&self) -> String {
let mut ppm = String::new();
ppm.push_str("P3\n");
ppm.push_str(self.width.to_string().as_str());
ppm.push(' ');
ppm.push_str(self.height.to_string().as_str());
ppm.push('\n');
ppm.push_str("255");
ppm.push('\n');
ppm
}
pub fn to_ppm(&self) -> String {
let mut ppm = self.header();
for row in &self.pixels {
let mut ppm_row = String::new();
for pixel in row {
let pixel_str = pixel.ppm_str();
if pixel_str.len() + ppm_row.len() > 70 {
let components: Vec<&str> = pixel_str.split(" ").collect();
for component in components {
if ppm_row.len() + component.len() > 70 {
ppm_row.pop();
ppm.push_str(&ppm_row);
ppm.push_str("\n");
ppm_row = String::new();
}
ppm_row.push_str(component);
ppm_row.push_str(" ");
}
} else {
ppm_row.push_str(&pixel_str);
ppm_row.push_str(" ");
}
}
ppm_row.pop();
ppm.push_str(&ppm_row);
ppm.push('\n');
}
ppm
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn create_canvas() {
let c = Canvas::new(10, 20);
assert_eq!(10, c.width);
assert_eq!(20, c.height);
let black = Color::new(0.0, 0.0, 0.0);
for row in c.pixels {
for pixel in row {
assert_eq!(black, pixel);
}
}
}
#[test]
fn write_pixels_to_canvas() {
let mut c = Canvas::new(10, 20);
let red = Color::new(1.0, 0.0, 0.0);
c.write_pixel(2, 3, red);
assert_eq!(red, c.pixel(2, 3));
}
#[test]
fn constructing_ppm_header() {
let c = Canvas::new(5, 3);
let ppm = c.to_ppm();
let expected = "P3
5 3
255";
let header = ppm.split("\n").take(3).collect::<Vec<&str>>().join("\n");
assert_eq!(expected, header);
}
#[test]
fn constructing_the_ppm_pixel_data() {
let mut c = Canvas::new(5, 3);
let c1 = Color::new(1.5, 0.0, 0.0);
let c2 = Color::new(0.0, 0.5, 0.0);
let c3 = Color::new(-0.5, 0.0, 1.0);
c.write_pixel(0, 0, c1);
c.write_pixel(2, 1, c2);
c.write_pixel(4, 2, c3);
let ppm = c.to_ppm();
let line_4 = "255 0 0 0 0 0 0 0 0 0 0 0 0 0 0";
let line_5 = "0 0 0 0 0 0 0 127 0 0 0 0 0 0 0";
let line_6 = "0 0 0 0 0 0 0 0 0 0 0 0 0 0 255";
let v : Vec<&str> = ppm.split("\n").collect();
print!("{}", ppm);
assert_eq!(line_4, *v.get(3).expect("must exist"));
assert_eq!(line_5, *v.get(4).expect("must exist"));
assert_eq!(line_6, *v.get(5).expect("must exist"));
}
#[test]
fn test_split_long_lines() {
let mut c = Canvas::new(10, 2);
for row in &mut c.pixels {
for pix in row {
*pix = Color::new(1.0, 0.8, 0.6);
}
}
let line4_7 = r#"255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204
153 255 204 153 255 204 153 255 204 153 255 204 153
255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204
153 255 204 153 255 204 153 255 204 153 255 204 153
"#;
let ppm = c.to_ppm();
let four_to_seven = ppm.split("\n").skip(3).collect::<Vec<&str>>().join("\n");
assert_eq!(line4_7, four_to_seven);
}
#[test]
fn ends_with_newline() {
let c = Canvas::new(5, 3);
let mut ppm = c.to_ppm();
assert_eq!(Some('\n'), ppm.pop());
}
}

151
features/src/color.rs Normal file
View File

@@ -0,0 +1,151 @@
use crate::num_traits_cast;
use std::ops;
use num_traits::NumCast;
#[derive(Debug, Copy, Clone)]
pub struct Color {
red: f32,
green: f32,
blue: f32,
}
impl Color {
pub fn new<R, G, B>(red: R, green: G, blue: B) -> Color
where R: NumCast,
G: NumCast,
B: NumCast,
{
Color {
red: num_traits_cast!(red),
green: num_traits_cast!(green),
blue: num_traits_cast!(blue),
}
}
pub fn red(&self) -> f32 {
self.red
}
pub fn green(&self) -> f32 {
self.green
}
pub fn blue(&self) -> f32 {
self.blue
}
pub fn ppm_str(&self) -> String {
// need to scale 0 - 1 -> 0 - 255
let r = (self.red * 255.0) as u8;
let g = (self.green * 255.0) as u8;
let b = (self.blue * 255.0) as u8;
format!("{} {} {}", r, g, b)
}
}
impl PartialEq for Color {
fn eq(&self, _rhs: &Self) -> bool {
relative_eq!(self.red, _rhs.red)
&& relative_eq!(self.green, _rhs.green)
&& relative_eq!(self.blue, _rhs.blue)
}
}
impl ops::Add for Color {
type Output = Color;
fn add(self, _rhs: Color) -> Color {
Color::new(
self.red + _rhs.red,
self.green + _rhs.green,
self.blue + _rhs.blue,
)
}
}
impl ops::Mul for Color {
type Output = Color;
fn mul(self, _rhs: Color) -> Color {
Color::new(
self.red * _rhs.red,
self.green * _rhs.green,
self.blue * _rhs.blue,
)
}
}
impl ops::Mul<i32> for Color {
type Output = Color;
fn mul(self, _rhs: i32) -> Color {
let val = _rhs as f32;
Color::new(
self.red * val,
self.green * val,
self.blue * val,
)
}
}
impl ops::Sub for Color {
type Output = Color;
fn sub(self, _rhs: Color) -> Color {
Color::new(
self.red - _rhs.red,
self.green - _rhs.green,
self.blue - _rhs.blue,
)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn color() {
let c = Color::new(-0.5, 0.4, 1.7);
assert_eq!(-0.5, c.red);
assert_eq!(0.4, c.green);
assert_eq!(1.7, c.blue);
}
#[test]
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);
assert_eq!(res, c1 + c2);
}
#[test]
fn sub() {
let c1 = Color::new(0.9, 0.6, 0.75);
let c2 = Color::new(0.7, 0.1, 0.25);
let res = Color::new(0.2, 0.5, 0.5);
assert_eq!(res, c1 - c2);
}
#[test]
fn mul_by_scalar() {
let c = Color::new(0.9, 0.6, 0.75);
let res = Color::new(1.8, 1.2, 1.5);
assert_eq!(res, c * 2);
}
#[test]
fn mul() {
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,10 +1,12 @@
#[macro_use]
extern crate approx;
use crate::num_traits_cast;
use std::fmt;
use std::ops;
#[derive(Debug)]
struct Tuple {
use num_traits::NumCast;
#[derive(Debug, Copy, Clone)]
pub struct Tuple {
x: f32,
y: f32,
z: f32,
@@ -12,57 +14,82 @@ struct Tuple {
}
impl Tuple {
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),
}
}
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,
}
}
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,
}
}
}
impl Tuple {
fn x(&self) -> f32 {
pub fn x(&self) -> f32 {
self.x
}
fn y(&self) -> f32 {
pub fn y(&self) -> f32 {
self.y
}
fn z(&self) -> f32 {
pub fn z(&self) -> f32 {
self.z
}
fn is_point(&self) -> bool {
pub fn w(&self) -> f32 {
self.w
}
pub fn is_point(&self) -> bool {
self.w == 1.0
}
fn is_vector(&self) -> bool {
pub fn is_vector(&self) -> bool {
self.w == 0.0
}
fn magnitude(&self) -> f32 {
pub fn magnitude(&self) -> f32 {
(
(self.x * self.x) +
(self.y * self.y) +
@@ -71,7 +98,7 @@ impl Tuple {
).sqrt()
}
fn normalize(&self) -> Tuple {
pub fn normalize(&self) -> Tuple {
let m = self.magnitude();
Tuple::new(
self.x / m,
@@ -81,14 +108,14 @@ impl Tuple {
)
}
fn dot(&self, rhs: &Tuple) -> f32 {
pub fn dot(&self, rhs: &Tuple) -> f32 {
self.x * rhs.x +
self.y * rhs.y +
self.z * rhs.z +
self.w * rhs.w
}
fn cross(&self, rhs: &Tuple) -> Tuple {
pub fn cross(&self, rhs: &Tuple) -> Tuple {
Tuple::vector(
self.y * rhs.z - self.z * rhs.y,
self.z * rhs.x - self.x * rhs.z,
@@ -97,6 +124,12 @@ impl Tuple {
}
}
impl fmt::Display for Tuple {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "({}, {}, {}) {}", self.x, self.y, self.z, if self.is_point() { "p" } else { "v" } )
}
}
impl PartialEq for Tuple {
fn eq(&self, _rhs: &Self) -> bool {
relative_eq!(self.x, _rhs.x)
@@ -184,6 +217,7 @@ impl ops::Div<f32> for Tuple {
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -407,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,3 +1,196 @@
fn main() {
println!("Hello, world!");
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::*;
#[derive(Debug)]
struct Environment {
gravity: Tuple,
wind: Tuple,
}
#[derive(Debug, Clone, Copy)]
struct Projectile {
position: Tuple,
velocity: Tuple,
}
impl Projectile {
fn new(position: Tuple, velocity: Tuple) -> Projectile {
if !position.is_point() {
panic!("position can not be a vector");
}
if !velocity.is_vector() {
panic!("velocity can not be point");
}
Projectile {
position: position,
velocity: velocity,
}
}
}
impl Projectile {
fn tick(&mut self, env: &Environment) {
self.position = self.position + self.velocity;
self.velocity = self.velocity + env.wind + env.gravity;
}
}
impl fmt::Display for Projectile {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
write!(f, "pos: {}, vel {}", self.position, self.velocity)
}
}
fn init_env() -> Environment {
Environment {
gravity: Tuple::vector(0, 0, -0.98),
wind: Tuple::vector(0, 0, 0),
}
}
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),
Tuple::vector(2.2, 0, 21.0),
);
let mut ball2 = Projectile::new(
Tuple::point(195, 0, 0),
Tuple::vector(-2.2, 0, 21),
);
let mut ball3 = Projectile::new(
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);
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);
canvas.write_pixel(ball3.position.x() as usize, canvas.height() - (ball3.position.z() as usize) - 1, color3);
ball.tick(&env);
ball2.tick(&env);
ball3.tick(&env);
if ball.position.z() >= (canvas.height() - 1) as f32
|| ball.position.z() < 0.0
|| ball.position.x() >= (canvas.width() - 1) as f32
|| ball.position.x() < 0.0 {
break;
}
}
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 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",
]