Compare commits

..

1 Commits

17 changed files with 110 additions and 1973 deletions

2
.gitignore vendored
View File

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

39
Cargo.lock generated
View File

@@ -1,42 +1,5 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "approx" name = "tuples"
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" 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] [package]
name = "ray-tracer" name = "tuples"
version = "0.1.0" version = "0.1.0"
authors = ["Jon Janzen <jonjanzen@me.com>"] authors = ["Jon Janzen <jonjanzen@me.com>"]
edition = "2018" edition = "2018"
@@ -7,4 +7,3 @@ edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
features = { path = "features" }

View File

@@ -1,172 +0,0 @@
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());
}
}

View File

@@ -1,151 +0,0 @@
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);
}
}

View File

@@ -1,27 +0,0 @@
#[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);
}
}

View File

@@ -1,687 +0,0 @@
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);
}
}

View File

@@ -1,70 +0,0 @@
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));
}
}

View File

@@ -1,20 +0,0 @@
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,261 +0,0 @@
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

@@ -1,196 +1,3 @@
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() { fn main() {
before_clock(); println!("Hello, world!");
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,12 +1,10 @@
# This file is automatically @generated by Cargo. # This file is automatically @generated by Cargo.
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3
[[package]] [[package]]
name = "approx" name = "approx"
version = "0.5.0" version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "072df7202e63b127ab55acfe16ce97013d5b97bf160489336d3f1840fd78e99e" checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278"
dependencies = [ dependencies = [
"num-traits", "num-traits",
] ]
@@ -17,14 +15,6 @@ version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a"
[[package]]
name = "features"
version = "0.1.0"
dependencies = [
"approx",
"num-traits",
]
[[package]] [[package]]
name = "num-traits" name = "num-traits"
version = "0.2.14" version = "0.2.14"
@@ -33,3 +23,10 @@ checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290"
dependencies = [ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "structs"
version = "0.1.0"
dependencies = [
"approx",
]

10
structs/Cargo.toml Normal file
View File

@@ -0,0 +1,10 @@
[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"

View File

@@ -1,12 +1,10 @@
use crate::num_traits_cast; #[macro_use]
extern crate approx;
use std::fmt;
use std::ops; use std::ops;
use num_traits::NumCast; #[derive(Debug)]
struct Tuple {
#[derive(Debug, Copy, Clone)]
pub struct Tuple {
x: f32, x: f32,
y: f32, y: f32,
z: f32, z: f32,
@@ -14,120 +12,56 @@ pub struct Tuple {
} }
impl Tuple { impl Tuple {
pub fn new<X, Y, Z, W>(x: X, y: Y, z: Z, w: W) -> Tuple fn new(x: f32, y: f32, z: f32, w: f32) -> Tuple {
where X: NumCast,
Y: NumCast,
Z: NumCast,
W: NumCast,
{
Tuple { Tuple {
x: num_traits_cast!(x), x,
y: num_traits_cast!(y), y,
z: num_traits_cast!(z), z,
w: num_traits_cast!(w), w,
} }
} }
pub fn point_zero() -> Tuple {
Tuple {
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 fn point(x: f32, y: f32, z: f32) -> Tuple {
where T: NumCast,
U: NumCast,
V: NumCast,
{
Tuple { Tuple {
x: num_traits_cast!(x), x,
y: num_traits_cast!(y), y,
z: num_traits_cast!(z), z,
w: 1.0, w: 1.0,
} }
} }
pub fn vector<X, Y, Z>(x: X, y: Y, z: Z) -> Tuple fn vector(x: f32, y: f32, z: f32) -> Tuple {
where X: NumCast,
Y: NumCast,
Z: NumCast {
Tuple { Tuple {
x: num_traits_cast!(x), x,
y: num_traits_cast!(y), y,
z: num_traits_cast!(z), z,
w: 0.0, w: 0.0,
} }
} }
} }
impl Tuple { impl Tuple {
pub fn x(&self) -> f32 { fn x(&self) -> f32 {
self.x self.x
} }
pub fn y(&self) -> f32 { fn y(&self) -> f32 {
self.y self.y
} }
pub fn z(&self) -> f32 { fn z(&self) -> f32 {
self.z self.z
} }
pub fn w(&self) -> f32 { fn is_point(&self) -> bool {
self.w
}
pub fn is_point(&self) -> bool {
self.w == 1.0 self.w == 1.0
} }
pub fn is_vector(&self) -> bool { fn is_vector(&self) -> bool {
self.w == 0.0 self.w == 0.0
} }
pub fn magnitude(&self) -> f32 {
(
(self.x * self.x) +
(self.y * self.y) +
(self.z * self.z) +
(self.w * self.w)
).sqrt()
}
pub fn normalize(&self) -> Tuple {
let m = self.magnitude();
Tuple::new(
self.x / m,
self.y / m,
self.z / m,
self.w / m,
)
}
pub fn dot(&self, rhs: &Tuple) -> f32 {
self.x * rhs.x +
self.y * rhs.y +
self.z * rhs.z +
self.w * rhs.w
}
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,
self.x * rhs.y - self.y * rhs.x
)
}
}
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 { impl PartialEq for Tuple {
@@ -217,7 +151,6 @@ impl ops::Div<f32> for Tuple {
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -361,119 +294,4 @@ mod tests {
assert_eq!(result, a /2.0); assert_eq!(result, a /2.0);
} }
#[test]
fn magnitude_x() {
let v = Tuple::vector(1.0, 0.0, 0.0);
assert_eq!(1.0, v.magnitude());
}
#[test]
fn magnitude_y() {
let v = Tuple::vector(0.0, 1.0, 0.0);
assert_eq!(1.0, v.magnitude());
}
#[test]
fn magnitude_z() {
let v = Tuple::vector(0.0, 0.0, 1.0);
assert_eq!(1.0, v.magnitude());
}
#[test]
fn magnitude_123() {
let v = Tuple::vector(1.0, 2.0, 3.0);
assert_eq!(14.0_f32.sqrt(), v.magnitude());
}
#[test]
fn magnitude_123_neg() {
let v = Tuple::vector(-1.0, -2.0, -3.0);
assert_eq!(14.0_f32.sqrt(), v.magnitude());
}
#[test]
fn normalize_x() {
let v = Tuple::vector(4.0, 0.0, 0.0);
let norm = v.normalize();
let result = Tuple::vector(1.0, 0.0, 0.0);
assert_eq!(result, norm);
}
#[test]
fn normalize_123() {
let v = Tuple::vector(1.0, 2.0, 3.0);
let norm = v.normalize();
let sqrt_of_14 = 14.0_f32.sqrt();
assert_relative_eq!(norm.x, 1.0 / sqrt_of_14);
assert_relative_eq!(norm.y, 2.0 / sqrt_of_14);
assert_relative_eq!(norm.z, 3.0 / sqrt_of_14);
}
#[test]
fn normalize_magnitude() {
let v = Tuple::vector(1.0, 2.0, 3.0);
let norm = v.normalize();
let m = norm.magnitude();
assert_relative_eq!(1.0, m);
}
#[test]
fn dot_product() {
let a = Tuple::vector(1.0, 2.0, 3.0);
let b = Tuple::vector(2.0, 3.0, 4.0);
assert_eq!(20.0, a.dot(&b));
}
#[test]
fn cross_product() {
let a = Tuple::vector(1.0, 2.0, 3.0);
let b = Tuple::vector(2.0, 3.0, 4.0);
let a_cross_b = Tuple::vector(-1.0, 2.0, -1.0);
assert_eq!(a_cross_b, a.cross(&b));
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());
}
} }

32
tuples/Cargo.lock generated Normal file
View File

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

View File

@@ -9,6 +9,7 @@ fn point(x: f32, y: f32, z: f32) -> PointVector {
(x, y, z, 1.0) (x, y, z, 1.0)
} }
fn vector(x: f32, y: f32, z: f32) -> PointVector { fn vector(x: f32, y: f32, z: f32) -> PointVector {
(x, y, z, 0.0) (x, y, z, 0.0)
} }
@@ -41,15 +42,30 @@ fn tuple_equals(lhs: PointVector, rhs: PointVector) -> bool {
} }
fn tuple_add(lhs: PointVector, rhs: PointVector) -> PointVector { fn tuple_add(lhs: PointVector, rhs: PointVector) -> PointVector {
(lhs.0 + rhs.0, lhs.1 + rhs.1, lhs.2 + rhs.2, lhs.3 + rhs.3) (
lhs.0 + rhs.0,
lhs.1 + rhs.1,
lhs.2 + rhs.2,
lhs.3 + rhs.3
)
} }
fn tuple_sub(lhs: PointVector, rhs: PointVector) -> PointVector { fn tuple_sub(lhs: PointVector, rhs: PointVector) -> PointVector {
(lhs.0 - rhs.0, lhs.1 - rhs.1, lhs.2 - rhs.2, lhs.3 - rhs.3) (
lhs.0 - rhs.0,
lhs.1 - rhs.1,
lhs.2 - rhs.2,
lhs.3 - rhs.3
)
} }
fn tuple_neg(lhs: PointVector) -> PointVector { fn tuple_neg(lhs: PointVector) -> PointVector {
(-lhs.0, -lhs.1, -lhs.2, -lhs.3) (
-lhs.0,
-lhs.1,
-lhs.2,
-lhs.3
)
} }
fn tuple_mult(lhs: PointVector, scalar: f32) -> PointVector { fn tuple_mult(lhs: PointVector, scalar: f32) -> PointVector {
@@ -70,41 +86,8 @@ fn tuple_div(lhs: PointVector, divisor: f32) -> PointVector {
) )
} }
fn magnitude(vector: PointVector) -> f32 {
(
(vector.0 * vector.0) +
(vector.1 * vector.1) +
(vector.2 * vector.2) +
(vector.3 * vector.3)
).sqrt()
}
fn normalize(vector: PointVector) -> PointVector {
let m = magnitude(vector);
(vector.0 / m,
vector.1 / m,
vector.2 / m,
vector.3 / m,
)
}
fn dot(lhs: PointVector, rhs: PointVector) -> f32 {
lhs.0 * rhs.0 +
lhs.1 * rhs.1 +
lhs.2 * rhs.2 +
lhs.3 * rhs.3
}
fn cross(lhs: PointVector, rhs: PointVector) -> PointVector {
vector(
lhs.1 * rhs.2 - lhs.2 * rhs.1,
lhs.2 * rhs.0 - lhs.0 * rhs.2,
lhs.0 * rhs.1 - lhs.1 * rhs.0
)
}
#[cfg(test)] #[cfg(test)]
pub mod tests { mod tests {
use super::*; use super::*;
#[test] #[test]
@@ -238,86 +221,4 @@ pub mod tests {
assert_eq!(true, tuple_equals(result, tuple_div(a, 2.0))); assert_eq!(true, tuple_equals(result, tuple_div(a, 2.0)));
} }
#[test]
fn compute_magnitude_x() {
let v = vector(1.0, 0.0, 0.0);
assert_eq!(1.0, magnitude(v));
}
#[test]
fn compute_magnitude_y() {
let v = vector(0.0, 1.0, 0.0);
assert_eq!(1.0, magnitude(v));
}
#[test]
fn compute_magnitude_z() {
let v = vector(0.0, 0.0, 1.0);
assert_eq!(1.0, magnitude(v));
}
#[test]
fn compute_magnitude_123() {
let v = vector(1.0, 2.0, 3.0);
assert_relative_eq!(14.0_f32.sqrt(), magnitude(v));
}
#[test]
fn compute_magnitude_123_neg() {
let v = vector(-1.0, -2.0, -3.0);
assert_relative_eq!(14.0_f32.sqrt(), magnitude(v));
}
#[test]
fn normalize_x() {
let v = vector(4.0, 0.0, 0.0);
let norm = normalize(v);
assert_eq!(vector(1.0, 0.0, 0.0), norm);
}
#[test]
fn normalize_123() {
let v = vector(1.0, 2.0, 3.0);
let norm = normalize(v);
let sqrt_of_14 = 14.0_f32.sqrt();
assert_relative_eq!(norm.0, 1.0 / sqrt_of_14);
assert_relative_eq!(norm.1, 2.0 / sqrt_of_14);
assert_relative_eq!(norm.2, 3.0 / sqrt_of_14);
}
#[test]
fn magnitude_of_normalized_vector() {
let v = vector(1.0, 2.0, 3.0);
let norm = normalize(v);
let m = magnitude(norm);
assert_relative_eq!(1.0, m);
}
#[test]
fn dot_product() {
let a = vector(1.0, 2.0, 3.0);
let b = vector(2.0, 3.0, 4.0);
assert_eq!(20.0, dot(a, b));
}
#[test]
fn cross_product() {
let a = vector(1.0, 2.0, 3.0);
let b = vector(2.0, 3.0, 4.0);
let a_cross_b = vector(-1.0, 2.0, -1.0);
assert_eq!(a_cross_b, cross(a, b));
let b_cross_a = vector(1.0, -2.0, 1.0);
assert_eq!(b_cross_a, cross(b, a));
}
pub fn
} }