Compare commits
1 Commits
main
...
92bfb55f87
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
92bfb55f87 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,3 +1 @@
|
|||||||
target
|
target
|
||||||
rusty-tags.vi
|
|
||||||
*.ppm
|
|
||||||
|
|||||||
39
Cargo.lock
generated
39
Cargo.lock
generated
@@ -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",
|
|
||||||
]
|
|
||||||
|
|||||||
@@ -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" }
|
|
||||||
|
|||||||
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
195
src/main.rs
195
src/main.rs
@@ -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) * ¢er;
|
|
||||||
let center = &Matrix::scaling(0, 10, 0) * ¢er;
|
|
||||||
let center = &Matrix::rotation_z((i as f32 / 12.0) * (2.0 * PI)) * ¢er;
|
|
||||||
let center = &Matrix::translation(middle, middle, 0) * ¢er;
|
|
||||||
|
|
||||||
draw_cross(&mut canvas, ¢er, color);
|
|
||||||
}
|
|
||||||
|
|
||||||
write_canvas_to_file(canvas, "clock.ppm");
|
|
||||||
|
|
||||||
println!("Ending clock!");
|
|
||||||
}
|
}
|
||||||
|
|||||||
21
features/Cargo.lock → structs/Cargo.lock
generated
21
features/Cargo.lock → structs/Cargo.lock
generated
@@ -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
10
structs/Cargo.toml
Normal 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"
|
||||||
@@ -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
32
tuples/Cargo.lock
generated
Normal 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",
|
||||||
|
]
|
||||||
@@ -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"
|
|
||||||
@@ -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
|
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user