completed rename. merged crates to features
This commit is contained in:
172
features/src/canvas.rs
Normal file
172
features/src/canvas.rs
Normal 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());
|
||||
}
|
||||
}
|
||||
145
features/src/color.rs
Normal file
145
features/src/color.rs
Normal file
@@ -0,0 +1,145 @@
|
||||
use std::ops;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Color {
|
||||
red: f32,
|
||||
green: f32,
|
||||
blue: f32,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn new(red: f32, green: f32, blue: f32) -> Color {
|
||||
Color {
|
||||
red,
|
||||
green,
|
||||
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.0);
|
||||
|
||||
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, 0.2, 0.4);
|
||||
let c2 = Color::new(0.9, 1.0, 0.1);
|
||||
let res = Color::new(0.9, 0.2, 0.04);
|
||||
|
||||
assert_eq!(res, c1 * c2);
|
||||
}
|
||||
}
|
||||
15
features/src/lib.rs
Normal file
15
features/src/lib.rs
Normal file
@@ -0,0 +1,15 @@
|
||||
#[macro_use]
|
||||
extern crate approx;
|
||||
|
||||
pub mod structs;
|
||||
pub mod color;
|
||||
pub mod canvas;
|
||||
pub mod matrix;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
}
|
||||
}
|
||||
644
features/src/matrix.rs
Normal file
644
features/src/matrix.rs
Normal file
@@ -0,0 +1,644 @@
|
||||
use crate::structs::Tuple;
|
||||
|
||||
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<const H: usize, const W: usize>(array: [[f32; W]; H]) -> Matrix {
|
||||
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(*v);
|
||||
}
|
||||
matrix.push(row);
|
||||
}
|
||||
Matrix {
|
||||
matrix,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_vec(matrix: Vec<Vec<f32>>) -> Matrix {
|
||||
Matrix {
|
||||
matrix,
|
||||
}
|
||||
}
|
||||
|
||||
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),
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[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.0, 5.0,],
|
||||
[1.0, 2.0,],
|
||||
];
|
||||
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.0, 5.0, 0.0],
|
||||
[1.0, -2.0, -7.0],
|
||||
[0.0, 1.0, 1.0],
|
||||
];
|
||||
|
||||
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.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 m_a = Matrix::from_array(a);
|
||||
|
||||
let b = [
|
||||
[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 m_b = Matrix::from_array(b);
|
||||
|
||||
assert_eq!(m_a, m_b);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_equality_b() {
|
||||
let a = [
|
||||
[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 m_a = Matrix::from_array(a);
|
||||
|
||||
let b = [
|
||||
[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, 1.0],
|
||||
];
|
||||
|
||||
let m_b = Matrix::from_array(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);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn matrix_by_identity() {
|
||||
let matrix = Matrix::from_array([
|
||||
[0.0, 1.0, 2.0, 4.0,],
|
||||
[1.0, 2.0, 4.0, 8.0,],
|
||||
[2.0, 4.0, 8.0, 16.0],
|
||||
[4.0, 8.0, 16.0, 32.0,]
|
||||
]);
|
||||
|
||||
let expected = Matrix::from_array([
|
||||
[0.0, 1.0, 2.0, 4.0,],
|
||||
[1.0, 2.0, 4.0, 8.0,],
|
||||
[2.0, 4.0, 8.0, 16.0],
|
||||
[4.0, 8.0, 16.0, 32.0,]
|
||||
]);
|
||||
|
||||
assert_eq!(&matrix * &Matrix::identity(4), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuple_by_identity() {
|
||||
let t = Tuple::new(1.0, 2.0, 3.0, 4.0);
|
||||
let expected = Tuple::new(1.0, 2.0, 3.0, 4.0);
|
||||
|
||||
assert_eq!(&Matrix::identity(4) * &t, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transposition() {
|
||||
let mut m = Matrix::from_array([
|
||||
[0.0, 9.0, 3.0, 0.0],
|
||||
[9.0, 8.0, 0.0, 8.0],
|
||||
[1.0, 8.0, 5.0, 3.0],
|
||||
[0.0, 0.0, 5.0, 8.0],
|
||||
]);
|
||||
|
||||
let expected = Matrix::from_array([
|
||||
[0.0, 9.0, 1.0, 0.0],
|
||||
[9.0, 8.0, 8.0, 0.0],
|
||||
[3.0, 0.0, 5.0, 5.0],
|
||||
[0.0, 8.0, 3.0, 8.0],
|
||||
]);
|
||||
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.0, 5.0],
|
||||
[-3.0, 2.0],
|
||||
]);
|
||||
|
||||
assert_eq!(17.0, m.determinant());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submatrix_3x3() {
|
||||
let start = Matrix::from_array([
|
||||
[1.0, 5.0, 0.0],
|
||||
[-3.0, 2.0, 7.0],
|
||||
[0.0, 6.0, -3.0],
|
||||
]);
|
||||
|
||||
let expected = Matrix::from_array([
|
||||
[-3.0, 2.0],
|
||||
[0.0, 6.0],
|
||||
]);
|
||||
|
||||
assert_eq!(expected, start.sub_matrix(0, 2));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn submatrix_4x4() {
|
||||
let start = Matrix::from_array([
|
||||
[-6.0, 1.0, 1.0, 6.0],
|
||||
[-8.0, 5.0, 8.0, 6.0],
|
||||
[-1.0, 0.0, 8.0, 2.0],
|
||||
[-7.0, 1.0, -1.0, 1.0],
|
||||
]);
|
||||
let expected = Matrix::from_array([
|
||||
[-6.0, 1.0, 6.0],
|
||||
[-8.0, 8.0, 6.0],
|
||||
[-7.0, -1.0, 1.0],
|
||||
]);
|
||||
|
||||
assert_eq!(expected, start.sub_matrix(2, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn minor_3x3() {
|
||||
let m = Matrix::from_array([
|
||||
[3.0, 5.0, 0.0],
|
||||
[2.0, -1.0, -7.0],
|
||||
[6.0, -1.0, 5.0],
|
||||
]);
|
||||
|
||||
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.0, 5.0, 0.0],
|
||||
[2.0, -1.0, -7.0],
|
||||
[6.0, -1.0, 5.0],
|
||||
]);
|
||||
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.0, 2.0, 6.0],
|
||||
[-5.0, 8.0, -4.0],
|
||||
[2.0, 6.0, 4.0],
|
||||
]);
|
||||
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.0, -8.0, 3.0, 5.0],
|
||||
[-3.0, 1.0, 7.0, 3.0],
|
||||
[1.0, 2.0, -9.0, 6.0],
|
||||
[-6.0, 7.0, 7.0, -9.0],
|
||||
]);
|
||||
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.0, 4.0, 4.0, 4.0],
|
||||
[5.0, 5.0, 7.0, 6.0],
|
||||
[4.0, -9.0, 3.0, -7.0],
|
||||
[9.0, 1.0, 7.0, -6.0],
|
||||
]);
|
||||
|
||||
assert_eq!(-2120.0, m.determinant());
|
||||
assert_eq!(true, m.is_invertable());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn can_invert_not_invertable() {
|
||||
let m = Matrix::from_array([
|
||||
[-4.0, 2.0, -2.0, -3.0],
|
||||
[9.0, 6.0, 2.0, 6.0],
|
||||
[0.0, -5.0, 1.0, -5.0],
|
||||
[0.0, 0.0, 0.0, 0.0],
|
||||
]);
|
||||
|
||||
assert_eq!(0.0, m.determinant());
|
||||
assert_eq!(false, m.is_invertable());
|
||||
|
||||
}
|
||||
|
||||
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([
|
||||
[-5.0, 2.0, 6.0, -8.0],
|
||||
[1.0, -5.0, 1.0, 8.0],
|
||||
[7.0, 7.0, -6.0, -7.0],
|
||||
[1.0, -3.0, 7.0, 4.0],
|
||||
]);
|
||||
|
||||
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([
|
||||
[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.0, -5.0, 9.0, 2.0],
|
||||
[7.0, 5.0, 6.0, 1.0],
|
||||
[-6.0, 0.0, 9.0, 6.0],
|
||||
[-3.0, 0.0, -9.0, -4.0],
|
||||
]).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.0, 3.0, 0.0, 9.0],
|
||||
[-5.0, -2.0, -6.0, -3.0],
|
||||
[-4.0, 9.0, 6.0, 4.0],
|
||||
[-7.0, 6.0, 6.0, 2.0],
|
||||
]).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.0, -9.0, 7.0, 3.0],
|
||||
[3.0, -8.0, 2.0, -9.0],
|
||||
[-4.0, 4.0, 4.0, 1.0],
|
||||
[-6.0, 5.0, -1.0, 1.0],
|
||||
]);
|
||||
let b = Matrix::from_array([
|
||||
[8.0, 2.0, 2.0, 2.0],
|
||||
[3.0, -1.0, 7.0, 0.0],
|
||||
[7.0, 0.0, 5.0, 4.0],
|
||||
[6.0, -2.0, 0.0, 5.0],
|
||||
]);
|
||||
|
||||
let c = &a * &b;
|
||||
let r = &c * &b.inverse();
|
||||
let expected = Matrix::from_array([
|
||||
[3.0, -9.0, 7.0, 3.0],
|
||||
[3.0, -8.0, 2.0, -9.0],
|
||||
[-4.0, 4.0, 4.0, 1.0],
|
||||
[-6.0, 5.0, -1.0, 1.0],
|
||||
]);
|
||||
assert_matrix_eq(&r, &expected, 0.00001);
|
||||
}
|
||||
}
|
||||
419
features/src/structs.rs
Normal file
419
features/src/structs.rs
Normal file
@@ -0,0 +1,419 @@
|
||||
use std::fmt;
|
||||
use std::ops;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Tuple {
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
w: f32,
|
||||
}
|
||||
|
||||
impl Tuple {
|
||||
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Tuple {
|
||||
Tuple {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
w,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn point(x: f32, y: f32, z: f32) -> Tuple {
|
||||
Tuple {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
w: 1.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vector(x: f32, y: f32, z: f32) -> Tuple {
|
||||
Tuple {
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
w: 0.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Tuple {
|
||||
pub fn x(&self) -> f32 {
|
||||
self.x
|
||||
}
|
||||
|
||||
pub fn y(&self) -> f32 {
|
||||
self.y
|
||||
}
|
||||
|
||||
pub fn z(&self) -> f32 {
|
||||
self.z
|
||||
}
|
||||
|
||||
pub fn w(&self) -> f32 {
|
||||
self.w
|
||||
}
|
||||
|
||||
pub fn is_point(&self) -> bool {
|
||||
self.w == 1.0
|
||||
}
|
||||
|
||||
pub fn is_vector(&self) -> bool {
|
||||
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 {
|
||||
fn eq(&self, _rhs: &Self) -> bool {
|
||||
relative_eq!(self.x, _rhs.x)
|
||||
&& relative_eq!(self.y, _rhs.y)
|
||||
&& relative_eq!(self.z, _rhs.z)
|
||||
&& relative_eq!(self.w, _rhs.w)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Add<Tuple> for Tuple {
|
||||
type Output = Tuple;
|
||||
|
||||
fn add(self, _rhs: Tuple) -> Tuple {
|
||||
Tuple::new(
|
||||
self.x + _rhs.x,
|
||||
self.y + _rhs.y,
|
||||
self.z + _rhs.z,
|
||||
self.w + _rhs.w,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Sub<Tuple> for Tuple {
|
||||
type Output = Tuple;
|
||||
|
||||
fn sub(self, _rhs: Tuple) -> Tuple {
|
||||
Tuple::new(
|
||||
self.x - _rhs.x,
|
||||
self.y - _rhs.y,
|
||||
self.z - _rhs.z,
|
||||
self.w - _rhs.w,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Neg for Tuple {
|
||||
type Output = Tuple;
|
||||
|
||||
fn neg(self) -> Tuple {
|
||||
Tuple::new(
|
||||
-self.x,
|
||||
-self.y,
|
||||
-self.z,
|
||||
-self.w,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul<f32> for Tuple {
|
||||
type Output = Tuple;
|
||||
|
||||
fn mul(self, _rhs: f32) -> Tuple {
|
||||
Tuple::new(
|
||||
self.x * _rhs,
|
||||
self.y * _rhs,
|
||||
self.z * _rhs,
|
||||
self.w * _rhs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Mul<Tuple> for f32 {
|
||||
type Output = Tuple;
|
||||
|
||||
fn mul(self, _rhs: Tuple) -> Tuple {
|
||||
Tuple::new(
|
||||
_rhs.x * self,
|
||||
_rhs.y * self,
|
||||
_rhs.z * self,
|
||||
_rhs.w * self,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ops::Div<f32> for Tuple {
|
||||
type Output = Tuple;
|
||||
|
||||
fn div(self, _rhs: f32) -> Tuple {
|
||||
Tuple::new(
|
||||
self.x / _rhs,
|
||||
self.y / _rhs,
|
||||
self.z / _rhs,
|
||||
self.w / _rhs,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_point() {
|
||||
let tuple = Tuple::new(4.3, -4.2, 3.1, 1.0);
|
||||
|
||||
assert_relative_eq!( 4.3, tuple.x);
|
||||
assert_relative_eq!(-4.2, tuple.y());
|
||||
assert_relative_eq!( 3.1, tuple.z());
|
||||
assert_eq!(true, tuple.is_point());
|
||||
assert_eq!(false, tuple.is_vector());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_point() {
|
||||
let tuple = Tuple::point(4.3, -4.2, 3.1);
|
||||
assert_eq!(true, tuple.is_point());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_vector() {
|
||||
let tuple = Tuple::new(4.3, -4.2, 3.1, 0.0);
|
||||
|
||||
assert_relative_eq!( 4.3, tuple.x());
|
||||
assert_relative_eq!(-4.2, tuple.y());
|
||||
assert_relative_eq!( 3.1, tuple.z());
|
||||
assert_eq!(false, tuple.is_point());
|
||||
assert_eq!(true, tuple.is_vector());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_vector() {
|
||||
let vector = Tuple::vector(4.0, -4.0, 3.0);
|
||||
assert_eq!(true, vector.is_vector());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuples_equal() {
|
||||
let lhs = Tuple::point(1.0, 2.0, 3.0);
|
||||
let rhs = Tuple::point(1.0, 2.0, 3.0);
|
||||
assert_eq!(lhs, rhs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuples_relative_equal() {
|
||||
let lhs = Tuple::point(1.0000001, 2.0, 3.0);
|
||||
let rhs = Tuple::point(1.0, 2.0, 3.0);
|
||||
assert_eq!(lhs, rhs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuples_not_equal() {
|
||||
let lhs = Tuple::point(1.0, 2.0, 3.0);
|
||||
let rhs = Tuple::vector(1.0, 2.0, 3.0);
|
||||
assert_ne!(lhs, rhs);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_two_tuples() {
|
||||
let a1 = Tuple::point(3.0, -2.0, 5.0);
|
||||
let a2 = Tuple::vector(-2.0, 3.0, 1.0);
|
||||
|
||||
let result = Tuple::point(1.0, 1.0, 6.0);
|
||||
assert_eq!(result, a1 + a2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract_two_points() {
|
||||
let a1 = Tuple::point(3.0, 2.0, 1.0);
|
||||
let a2 = Tuple::point(5.0, 6.0, 7.0);
|
||||
|
||||
let result = Tuple::vector(-2.0, -4.0, -6.0);
|
||||
assert_eq!(result, a1 - a2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subract_vector_from_point() {
|
||||
let a1 = Tuple::point(3.0, 2.0, 1.0);
|
||||
let a2 = Tuple::vector(5.0, 6.0, 7.0);
|
||||
|
||||
let result = Tuple::point(-2.0, -4.0, -6.0);
|
||||
assert_eq!(result, a1 - a2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subract_vector_from_vector() {
|
||||
let a1 = Tuple::vector(3.0, 2.0, 1.0);
|
||||
let a2 = Tuple::vector(5.0, 6.0, 7.0);
|
||||
|
||||
let result = Tuple::vector(-2.0, -4.0, -6.0);
|
||||
assert_eq!(result, a1 - a2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract_vector_from_zero_vector() {
|
||||
let a1 = Tuple::vector(0.0, 0.0, 0.0);
|
||||
let a2 = Tuple::vector(5.0, 6.0, 7.0);
|
||||
|
||||
let result = Tuple::vector(-5.0, -6.0, -7.0);
|
||||
assert_eq!(result, a1 - a2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negate_tuple() {
|
||||
let a = Tuple::new(1.0, -2.0, 3.0, -4.0);
|
||||
let result = Tuple::new(-1.0, 2.0, -3.0, 4.0);
|
||||
|
||||
assert_eq!(result, -a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_tuple_by_scalar() {
|
||||
let a = Tuple::new(1.0, -2.0, 3.0, -4.0);
|
||||
let result = Tuple::new(3.5, -7.0, 10.5, -14.0);
|
||||
|
||||
assert_eq!(result, a * 3.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_scalar_by_tuple() {
|
||||
let a = Tuple::new(1.0, -2.0, 3.0, -4.0);
|
||||
let result = Tuple::new(3.5, -7.0, 10.5, -14.0);
|
||||
|
||||
assert_eq!(result, 3.5 * a);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_tuple_by_fraction() {
|
||||
let a = Tuple::new(1.0, -2.0, 3.0, -4.0);
|
||||
let result = Tuple::new(0.5, -1.0, 1.5, -2.0);
|
||||
|
||||
assert_eq!(result, a * 0.5);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn divide_tuple() {
|
||||
let a = Tuple::new(1.0, -2.0, 3.0, -4.0);
|
||||
let result = Tuple::new(0.5, -1.0, 1.5, -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));
|
||||
}
|
||||
}
|
||||
321
features/src/tuples.rs
Normal file
321
features/src/tuples.rs
Normal file
@@ -0,0 +1,321 @@
|
||||
#[macro_use]
|
||||
extern crate approx;
|
||||
|
||||
use std::f32;
|
||||
|
||||
type PointVector = (f32, f32, f32, f32);
|
||||
|
||||
fn point(x: f32, y: f32, z: f32) -> PointVector {
|
||||
(x, y, z, 1.0)
|
||||
}
|
||||
|
||||
fn vector(x: f32, y: f32, z: f32) -> PointVector {
|
||||
(x, y, z, 0.0)
|
||||
}
|
||||
|
||||
fn tuple_x(tuple: PointVector) -> f32 {
|
||||
tuple.0
|
||||
}
|
||||
|
||||
fn tuple_y(tuple: PointVector) -> f32 {
|
||||
tuple.1
|
||||
}
|
||||
|
||||
fn tuple_z(tuple: PointVector) -> f32 {
|
||||
tuple.2
|
||||
}
|
||||
|
||||
fn tuple_is_point(tuple: PointVector) -> bool {
|
||||
relative_eq!(1.0, tuple.3)
|
||||
}
|
||||
|
||||
fn tuple_is_vector(tuple: PointVector) -> bool {
|
||||
relative_eq!(0.0, tuple.3)
|
||||
}
|
||||
|
||||
fn tuple_equals(lhs: PointVector, rhs: PointVector) -> bool {
|
||||
relative_eq!(lhs.0, rhs.0)
|
||||
&& relative_eq!(lhs.1, rhs.1)
|
||||
&& relative_eq!(lhs.2, rhs.2)
|
||||
&& relative_eq!(lhs.3, rhs.3)
|
||||
}
|
||||
|
||||
fn tuple_add(lhs: PointVector, rhs: PointVector) -> PointVector {
|
||||
(lhs.0 + rhs.0, lhs.1 + rhs.1, lhs.2 + rhs.2, lhs.3 + rhs.3)
|
||||
}
|
||||
|
||||
fn tuple_sub(lhs: PointVector, rhs: PointVector) -> PointVector {
|
||||
(lhs.0 - rhs.0, lhs.1 - rhs.1, lhs.2 - rhs.2, lhs.3 - rhs.3)
|
||||
}
|
||||
|
||||
fn tuple_neg(lhs: PointVector) -> PointVector {
|
||||
(-lhs.0, -lhs.1, -lhs.2, -lhs.3)
|
||||
}
|
||||
|
||||
fn tuple_mult(lhs: PointVector, scalar: f32) -> PointVector {
|
||||
(
|
||||
lhs.0 * scalar,
|
||||
lhs.1 * scalar,
|
||||
lhs.2 * scalar,
|
||||
lhs.3 * scalar,
|
||||
)
|
||||
}
|
||||
|
||||
fn tuple_div(lhs: PointVector, divisor: f32) -> PointVector {
|
||||
(
|
||||
lhs.0 / divisor,
|
||||
lhs.1 / divisor,
|
||||
lhs.2 / divisor,
|
||||
lhs.3 / divisor,
|
||||
)
|
||||
}
|
||||
|
||||
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)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_point() {
|
||||
let tuple = (4.3, -4.2, 3.1, 1.0);
|
||||
|
||||
assert_relative_eq!(4.3, tuple_x(tuple));
|
||||
assert_relative_eq!(-4.2, tuple_y(tuple));
|
||||
assert_relative_eq!(3.1, tuple_z(tuple));
|
||||
assert_eq!(true, tuple_is_point(tuple));
|
||||
assert_eq!(false, tuple_is_vector(tuple));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_point() {
|
||||
let point = point(4.0, -4.0, 3.0);
|
||||
assert_eq!(true, tuple_is_point(point));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_vector() {
|
||||
let tuple = (4.3, -4.2, 3.1, 0.0);
|
||||
|
||||
assert_relative_eq!(4.3, tuple_x(tuple));
|
||||
assert_relative_eq!(-4.2, tuple_y(tuple));
|
||||
assert_relative_eq!(3.1, tuple_z(tuple));
|
||||
assert_eq!(false, tuple_is_point(tuple));
|
||||
assert_eq!(true, tuple_is_vector(tuple));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_vector() {
|
||||
let vector = vector(4.0, -4.0, 3.0);
|
||||
assert_eq!(true, tuple_is_vector(vector));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuples_equal() {
|
||||
let lhs = point(1.0, 2.0, 3.0);
|
||||
let rhs = point(1.0, 2.0, 3.0);
|
||||
assert_eq!(true, tuple_equals(lhs, rhs));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuples_relative_equal() {
|
||||
let lhs = point(1.0000001, 2.0, 3.0);
|
||||
let rhs = point(1.0, 2.0, 3.0);
|
||||
assert_eq!(true, tuple_equals(lhs, rhs));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn tuples_not_equal() {
|
||||
let lhs = point(1.0, 2.0, 3.0);
|
||||
let rhs = vector(1.0, 2.0, 3.0);
|
||||
assert_eq!(false, tuple_equals(lhs, rhs));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_two_tuples() {
|
||||
let a1 = point(3.0, -2.0, 5.0);
|
||||
let a2 = vector(-2.0, 3.0, 1.0);
|
||||
|
||||
let result = point(1.0, 1.0, 6.0);
|
||||
assert_eq!(true, tuple_equals(result, tuple_add(a1, a2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract_two_points() {
|
||||
let a1 = point(3.0, 2.0, 1.0);
|
||||
let a2 = point(5.0, 6.0, 7.0);
|
||||
|
||||
let result = vector(-2.0, -4.0, -6.0);
|
||||
assert_eq!(true, tuple_equals(result, tuple_sub(a1, a2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subract_vector_from_point() {
|
||||
let a1 = point(3.0, 2.0, 1.0);
|
||||
let a2 = vector(5.0, 6.0, 7.0);
|
||||
|
||||
let result = point(-2.0, -4.0, -6.0);
|
||||
assert_eq!(true, tuple_equals(result, tuple_sub(a1, a2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subract_vector_from_vector() {
|
||||
let a1 = vector(3.0, 2.0, 1.0);
|
||||
let a2 = vector(5.0, 6.0, 7.0);
|
||||
|
||||
let result = vector(-2.0, -4.0, -6.0);
|
||||
assert_eq!(true, tuple_equals(result, tuple_sub(a1, a2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subtract_vector_from_zero_vector() {
|
||||
let a1 = vector(0.0, 0.0, 0.0);
|
||||
let a2 = vector(5.0, 6.0, 7.0);
|
||||
|
||||
let result = vector(-5.0, -6.0, -7.0);
|
||||
assert_eq!(true, tuple_equals(result, tuple_sub(a1, a2)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn negate_tuple() {
|
||||
let a = (1.0, -2.0, 3.0, -4.0);
|
||||
let result = (-1.0, 2.0, -3.0, 4.0);
|
||||
|
||||
assert_eq!(result, tuple_neg(a));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_tuple_by_scalar() {
|
||||
let a = (1.0, -2.0, 3.0, -4.0);
|
||||
let result = (3.5, -7.0, 10.5, -14.0);
|
||||
|
||||
assert_eq!(true, tuple_equals(result, tuple_mult(a, 3.5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn multiply_tuple_by_fraction() {
|
||||
let a = (1.0, -2.0, 3.0, -4.0);
|
||||
let result = (0.5, -1.0, 1.5, -2.0);
|
||||
|
||||
assert_eq!(true, tuple_equals(result, tuple_mult(a, 0.5)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn divide_tuple() {
|
||||
let a = (1.0, -2.0, 3.0, -4.0);
|
||||
let result = (0.5, -1.0, 1.5, -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));
|
||||
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user