Compare commits

..

No commits in common. "f347e2e47d4a5e71b9601a3989a0ecb7bc03e425" and "8e4f6b06e6fe597bea80355556927f9c5fb879a5" have entirely different histories.

18 changed files with 97 additions and 657 deletions

11
Cargo.lock generated
View File

@ -3334,15 +3334,12 @@ dependencies = [
[[package]] [[package]]
name = "ray-tracer" name = "ray-tracer"
version = "0.1.0" version = "0.1.0"
dependencies = [
"rayon",
]
[[package]] [[package]]
name = "rayon" name = "rayon"
version = "1.10.0" version = "1.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1"
dependencies = [ dependencies = [
"either", "either",
"rayon-core", "rayon-core",
@ -3350,9 +3347,9 @@ dependencies = [
[[package]] [[package]]
name = "rayon-core" name = "rayon-core"
version = "1.12.1" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed"
dependencies = [ dependencies = [
"crossbeam-deque", "crossbeam-deque",
"crossbeam-utils", "crossbeam-utils",

View File

@ -6,7 +6,6 @@ edition = "2021"
# 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]
rayon = "1.10.0"
[[bin]] [[bin]]
name = "projectile" name = "projectile"

View File

@ -46,7 +46,7 @@ fn main() {
} }
} }
let ppm = PPM::from(&canvas); let ppm = PPM::from(canvas);
let mut file = File::create("projectile.ppm").unwrap(); let mut file = File::create("projectile.ppm").unwrap();
let _ = file.write(ppm.as_bytes()); let _ = file.write(ppm.as_bytes());

View File

@ -1,101 +0,0 @@
use ray_tracer::{types::*, PPM};
use rayon::prelude::*;
use std::{
fs::File,
io::Write,
sync::{Arc, RwLock},
};
const SIZE: usize = 100;
fn render(
camera: &Point,
light: &PointLight,
sphere: &Sphere,
wall_z: f64,
wall_size: f64,
filename: &str,
) {
let canvas = Arc::new(RwLock::new(Canvas::new(SIZE, SIZE)));
let pixel_size = wall_size / SIZE as f64;
let half = wall_size / 2.;
let ray = Ray::new(camera.clone(), (Point::new(0., 0., wall_z) - camera).normalize());
let xs = ray.intersect(sphere);
match xs.hit() {
Some(hit) => {
let point = ray.position(hit.t);
let normal = hit.object.normal_at(&point);
let eye = -ray.direction;
let color = hit.object.material().lighting(&light, &point, &eye, &normal);
println!("{:?}", color);
}
None => {}
}
(0..SIZE).into_par_iter().for_each(|x| {
(0..SIZE).into_par_iter().for_each(|y| {
let world_x = -half + pixel_size * x as f64;
let world_y = half - pixel_size * y as f64;
let position = Point::new(world_x, world_y, wall_z);
let ray = Ray::new(camera.clone(), (position - camera).normalize());
let xs = ray.intersect(sphere);
match xs.hit() {
Some(hit) => {
let point = ray.position(hit.t);
let normal = hit.object.normal_at(&point);
let eye = -ray.direction;
let color = hit
.object
.material()
.lighting(&light, &point, &eye, &normal);
*canvas.write().unwrap().pixel_mut(x, y) = color;
}
None => {}
}
});
});
let ppm = PPM::from(&*canvas.read().unwrap());
let mut file = File::create(filename).unwrap();
let _ = file.write(ppm.as_bytes());
}
fn main() {
let mut sphere = Sphere::default();
let mut material = Material::default();
material.color = Color::new(1., 0.2, 1.);
*sphere.material_mut() = material;
let camera = Point::new(0., 0., -5.);
let wall_z = 10.;
let wall_size = 7.;
{
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_1.ppm");
}
{
let light = PointLight::new(Point::new(-1., 0., -10.), Color::new(1., 1., 1.));
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_2.ppm");
}
{
let light = PointLight::new(Point::new(1., 0., -10.), Color::new(1., 1., 1.));
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_3.ppm");
}
{
let light = PointLight::new(Point::new(0., -1., -10.), Color::new(1., 1., 1.));
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_4.ppm");
}
{
let light = PointLight::new(Point::new(0., 1., -10.), Color::new(1., 1., 1.));
render(&camera, &light, &sphere, wall_z, wall_size, "sphere_5.ppm");
}
}

View File

@ -29,8 +29,8 @@ fn join_to_line_limit(data: impl IntoIterator<Item = String>) -> Vec<String> {
#[derive(Debug)] #[derive(Debug)]
pub struct PPM(String); pub struct PPM(String);
impl From<&Canvas> for PPM { impl From<Canvas> for PPM {
fn from(c: &Canvas) -> Self { fn from(c: Canvas) -> Self {
// let v = vec![0.; c.width() * c.height() * 3]; // let v = vec![0.; c.width() * c.height() * 3];
let header = format!("P3\n{} {}\n255\n", c.width(), c.height()); let header = format!("P3\n{} {}\n255\n", c.width(), c.height());
@ -68,7 +68,7 @@ mod tests {
#[test] #[test]
fn construct_ppm_header() { fn construct_ppm_header() {
let c = Canvas::new(5, 3); let c = Canvas::new(5, 3);
let ppm = PPM::from(&c); let ppm = PPM::from(c);
assert!(ppm.starts_with( assert!(ppm.starts_with(
"P3\n\ "P3\n\
@ -91,7 +91,7 @@ mod tests {
0 0 0 0 0 0 0 128 0 0 0 0 0 0 0\n\ 0 0 0 0 0 0 0 128 0 0 0 0 0 0 0\n\
0 0 0 0 0 0 0 0 0 0 0 0 0 0 255\n"; 0 0 0 0 0 0 0 0 0 0 0 0 0 0 255\n";
let ppm = PPM::from(&c); let ppm = PPM::from(c);
assert_eq!(*ppm, expected); assert_eq!(*ppm, expected);
} }
@ -112,7 +112,7 @@ mod tests {
255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\ 255 204 153 255 204 153 255 204 153 255 204 153 255 204 153 255 204\n\
153 255 204 153 255 204 153 255 204 153 255 204 153\n"; 153 255 204 153 255 204 153 255 204 153 255 204 153\n";
let ppm = PPM::from(&c); let ppm = PPM::from(c);
assert_eq!(*ppm, expected); assert_eq!(*ppm, expected);
} }
} }

View File

@ -71,7 +71,7 @@ mod tests {
fn multiply_by_translation_matrix() { fn multiply_by_translation_matrix() {
let tr = translation(5., -3., 2.); let tr = translation(5., -3., 2.);
let p = Point::new(-3., 4., 5.); let p = Point::new(-3., 4., 5.);
assert_eq!(&tr * p, Point::new(2., 1., 7.)); assert_eq!(tr.clone() * p, Point::new(2., 1., 7.));
let inv = tr.inverse(); let inv = tr.inverse();
assert_eq!(inv * p, Point::new(-8., 7., 3.)); assert_eq!(inv * p, Point::new(-8., 7., 3.));
@ -81,7 +81,7 @@ mod tests {
fn translation_does_not_affect_vectors() { fn translation_does_not_affect_vectors() {
let tr = translation(5., -3., 2.); let tr = translation(5., -3., 2.);
let v = Vector::new(-3., 4., 5.); let v = Vector::new(-3., 4., 5.);
assert_eq!(tr * v, v); assert_eq!(tr.clone() * v, v);
} }
#[test] #[test]
@ -90,7 +90,7 @@ mod tests {
let p = Point::new(-4., 6., 8.); let p = Point::new(-4., 6., 8.);
let v = Vector::new(-4., 6., 8.); let v = Vector::new(-4., 6., 8.);
assert_eq!(&tr * p, Point::new(-8., 18., 32.)); assert_eq!(tr.clone() * p, Point::new(-8., 18., 32.));
assert_eq!(tr * v, Vector::new(-8., 18., 32.)); assert_eq!(tr * v, Vector::new(-8., 18., 32.));
} }

View File

@ -22,6 +22,7 @@ impl Color {
pub fn blue(&self) -> f64 { pub fn blue(&self) -> f64 {
self.0.2 self.0.2
} }
} }
impl Default for Color { impl Default for Color {

View File

View File

@ -1,39 +0,0 @@
use std::cmp::Ordering;
use super::Sphere;
#[derive(Clone, Debug, PartialEq)]
pub struct Intersection<'a> {
pub t: f64,
pub object: &'a Sphere,
}
pub struct Intersections<'a>(Vec<Intersection<'a>>);
impl<'a> Intersections<'a> {
pub fn is_empty(&'a self) -> bool {
self.0.is_empty()
}
pub fn len(&'a self) -> usize {
self.0.len()
}
pub fn hit(&'a self) -> Option<&Intersection<'a>> {
self.0.iter().find(|i| i.t >= 0.)
}
}
impl<'a> std::ops::Index<usize> for Intersections<'a> {
type Output = Intersection<'a>;
fn index(&self, idx: usize) -> &Intersection<'a> {
&self.0[idx]
}
}
impl<'a> From<Vec<Intersection<'a>>> for Intersections<'a> {
fn from(mut v: Vec<Intersection<'a>>) -> Self {
v.sort_by(|l, r| l.t.partial_cmp(&r.t).unwrap_or(Ordering::Equal));
Self(v)
}
}

View File

@ -1,12 +0,0 @@
use super::{Color, Point};
pub struct PointLight {
pub position: Point,
pub intensity: Color,
}
impl PointLight {
pub fn new(position: Point, intensity: Color) -> Self {
Self { position, intensity }
}
}

View File

@ -1,119 +0,0 @@
use super::{Color, Point, PointLight, Vector};
#[derive(Debug, PartialEq)]
pub struct Material {
pub color: Color,
pub ambient: f64,
pub diffuse: f64,
pub specular: f64,
pub shininess: f64,
}
impl Default for Material {
fn default() -> Self {
Self {
color: Color::new(1., 1., 1.),
ambient: 0.1,
diffuse: 0.9,
specular: 0.9,
shininess: 200.,
}
}
}
impl Material {
pub fn lighting(&self, light: &PointLight, position: &Point, eye: &Vector, normal_v: &Vector) -> Color {
let effective_color = self.color * light.intensity;
let light_v = (light.position - position).normalize();
let ambient = effective_color * self.ambient;
let light_dot_normal = light_v.dot(&normal_v);
let (diffuse, specular) = if light_dot_normal < 0. {
(Color::new(0., 0., 0.), Color::new(0., 0., 0.))
} else {
let diffuse = effective_color * self.diffuse * light_dot_normal;
let reflect_v = (-light_v).reflect(&normal_v);
let reflect_dot_eye = reflect_v.dot(&eye);
let specular = if reflect_dot_eye <= 0. {
Color::new(0., 0., 0.)
} else {
let factor = reflect_dot_eye.powf(self.shininess);
light.intensity * self.specular * factor
};
(diffuse, specular)
};
ambient + diffuse + specular
}
}
#[cfg(test)]
mod tests {
use crate::types::{Color, Point, PointLight, Vector};
use super::*;
#[test]
fn lighting_with_eye_between_light_and_surface() {
let m = Material::default();
let position = Point::default();
let eyev = Vector::new(0., 0., -1.);
let normalv = Vector::new(0., 0., -1.);
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(1.9, 1.9, 1.9));
}
#[test]
fn lighting_with_eye_between_light_and_surface_eye_offset_45() {
let m = Material::default();
let position = Point::default();
let eyev = Vector::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.);
let normalv = Vector::new(0., 0., -1.);
let light = PointLight::new(Point::new(0., 0., -10.), Color::new(1., 1., 1.));
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(1., 1., 1.));
}
#[test]
fn lighting_with_eye_between_light_and_surface_light_offset_45() {
let m = Material::default();
let position = Point::default();
let eyev = Vector::new(0., 0., -1.);
let normalv = Vector::new(0., 0., -1.);
let light = PointLight::new(Point::new(0., 10., -10.), Color::new(1., 1., 1.));
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(0.7364, 0.7364, 0.7364));
}
#[test]
fn lighting_eye_in_path_of_reflection() {
let m = Material::default();
let position = Point::default();
// let eyev = Vector::new(0., -2_f64.sqrt() / 2., -2_f64.sqrt() / 2.);
let eyev = Vector::new(0., -10., -10.).normalize();
let normalv = Vector::new(0., 0., -1.);
let light = PointLight::new(Point::new(0., 10., -10.), Color::new(1., 1., 1.));
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(1.6364, 1.6364, 1.6364));
}
#[test]
fn lighting_with_light_behind_surface() {
let m = Material::default();
let position = Point::default();
let eyev = Vector::new(0., 0., -1.);
let normalv = Vector::new(0., 0., -1.);
let light = PointLight::new(Point::new(0., 0., 10.), Color::new(1., 1., 1.));
assert_eq!(m.lighting(&light, &position, &eyev, &normalv), Color::new(0.1, 0.1, 0.1));
}
}

View File

@ -169,9 +169,9 @@ impl PartialEq for Matrix {
} }
} }
impl std::ops::Mul<&Matrix> for &Matrix { impl std::ops::Mul for Matrix {
type Output = Matrix; type Output = Matrix;
fn mul(self, rside: &Matrix) -> Matrix { fn mul(self, rside: Matrix) -> Matrix {
assert_eq!(self.size, 4); assert_eq!(self.size, 4);
assert_eq!(rside.size, 4); assert_eq!(rside.size, 4);
@ -189,28 +189,7 @@ impl std::ops::Mul<&Matrix> for &Matrix {
} }
} }
impl std::ops::Mul<Matrix> for &Matrix { impl std::ops::Mul<Tuple> for Matrix {
type Output = Matrix;
fn mul(self, rside: Matrix) -> Matrix {
self * &rside
}
}
impl std::ops::Mul<&Matrix> for Matrix {
type Output = Matrix;
fn mul(self, rside: &Matrix) -> Matrix {
&self * rside
}
}
impl std::ops::Mul for Matrix {
type Output = Matrix;
fn mul(self, rside: Matrix) -> Matrix {
&self * &rside
}
}
impl std::ops::Mul<Tuple> for &Matrix {
type Output = Tuple; type Output = Tuple;
fn mul(self, rside: Tuple) -> Tuple { fn mul(self, rside: Tuple) -> Tuple {
assert_eq!(self.size, 4); assert_eq!(self.size, 4);
@ -228,39 +207,10 @@ impl std::ops::Mul<Tuple> for &Matrix {
} }
} }
impl std::ops::Mul<Tuple> for Matrix {
type Output = Tuple;
fn mul(self, rside: Tuple) -> Tuple {
&self * rside
}
}
impl std::ops::Mul<&Point> for &Matrix {
type Output = Point;
fn mul(self, rside: &Point) -> Point {
let t: Tuple = **rside;
Point::from(self * t)
}
}
impl std::ops::Mul<Point> for &Matrix {
type Output = Point;
fn mul(self, rside: Point) -> Point {
self * &rside
}
}
impl std::ops::Mul<&Point> for Matrix {
type Output = Point;
fn mul(self, rside: &Point) -> Point {
&self * rside
}
}
impl std::ops::Mul<Point> for Matrix { impl std::ops::Mul<Point> for Matrix {
type Output = Point; type Output = Point;
fn mul(self, rside: Point) -> Point { fn mul(self, rside: Point) -> Point {
&self * rside Point::from(self * *rside)
} }
} }
@ -375,7 +325,7 @@ mod tests {
[4., 8., 16., 32.], [4., 8., 16., 32.],
]); ]);
assert_eq!(&a * Matrix::identity(), a); assert_eq!(a.clone() * Matrix::identity(), a);
} }
#[test] #[test]
@ -538,7 +488,7 @@ mod tests {
[7., 0., 5., 4.], [7., 0., 5., 4.],
[6., -2., 0., 5.], [6., -2., 0., 5.],
]); ]);
let c = &a * &b; let c = a.clone() * b.clone();
assert_eq!(c * b.inverse(), a); assert_eq!(c * b.inverse(), a);
} }
} }

View File

@ -1,8 +1,5 @@
mod canvas; mod canvas;
mod color; mod color;
mod intersections;
mod light;
mod material;
mod matrix; mod matrix;
mod point; mod point;
mod ray; mod ray;
@ -12,9 +9,6 @@ mod vector;
pub use canvas::Canvas; pub use canvas::Canvas;
pub use color::Color; pub use color::Color;
pub use intersections::{Intersections, Intersection};
pub use light::PointLight;
pub use material::Material;
pub use matrix::Matrix; pub use matrix::Matrix;
pub use point::Point; pub use point::Point;
pub use ray::Ray; pub use ray::Ray;
@ -160,26 +154,4 @@ mod tests {
*c.pixel_mut(2, 3) = red; *c.pixel_mut(2, 3) = red;
assert_eq!(*c.pixel(2, 3), red); assert_eq!(*c.pixel(2, 3), red);
} }
#[test]
fn reflect_vector_approaching_at_45() {
let v = Vector::new(1., -1., 0.);
let n = Vector::new(0., 1., 0.);
assert_eq!(v.reflect(&n), Vector::new(1., 1., 0.));
let v = Vector::new(-1., -1., 0.);
let n = Vector::new(1., 0., 0.);
assert_eq!(v.reflect(&n), Vector::new(1., -1., 0.));
let v = Vector::new(1., 0., -1.);
let n = Vector::new(0., 0., 1.);
assert_eq!(v.reflect(&n), Vector::new(1., 0., 1.));
}
#[test]
fn reflect_off_slanted_surface() {
let v = Vector::new(0., -1., 0.);
let n = Vector::new(2_f64.sqrt() / 2., 2_f64.sqrt() / 2., 0.);
assert_eq!(v.reflect(&n), Vector::new(1., 0., 0.));
}
} }

View File

@ -51,34 +51,13 @@ impl std::ops::Add<Vector> for Point {
} }
} }
impl std::ops::Sub<&Point> for &Point { impl std::ops::Sub for Point {
type Output = Vector; type Output = Vector;
fn sub(self, r: &Point) -> Self::Output { fn sub(self, r: Point) -> Self::Output {
Vector::from(self.0 - r.0) Vector::from(self.0 - r.0)
} }
} }
impl std::ops::Sub<Point> for &Point {
type Output = Vector;
fn sub(self, r: Point) -> Self::Output {
self - &r
}
}
impl std::ops::Sub<&Point> for Point {
type Output = Vector;
fn sub(self, r: &Point) -> Self::Output {
&self - r
}
}
impl std::ops::Sub<Point> for Point {
type Output = Vector;
fn sub(self, r: Point) -> Self::Output {
&self - &r
}
}
impl std::ops::Sub<Vector> for Point { impl std::ops::Sub<Vector> for Point {
type Output = Point; type Output = Point;
fn sub(self, r: Vector) -> Self::Output { fn sub(self, r: Vector) -> Self::Output {

View File

@ -1,9 +1,42 @@
use super::{Intersection, Intersections, Matrix, Point, Sphere, Vector}; use std::cmp::Ordering;
use crate::types::{Point, Sphere, Vector};
#[derive(Clone, Debug, PartialEq)]
pub struct Intersection<'a> {
t: f64,
object: &'a Sphere,
}
pub struct Intersections<'a>(Vec<Intersection<'a>>);
impl <'a> Intersections<'a> {
pub fn len(&'a self) -> usize {
self.0.len()
}
pub fn hit(&'a self) -> Option<&Intersection<'a>> {
self.0.iter().find(|i| i.t >= 0.)
}
}
impl <'a> std::ops::Index<usize> for Intersections<'a> {
type Output = Intersection<'a>;
fn index(&self, idx: usize) -> &Intersection<'a> {
&self.0[idx]
}
}
impl <'a> From<Vec<Intersection<'a>>> for Intersections<'a> {
fn from(mut v: Vec<Intersection<'a>>) -> Self {
v.sort_by(|l, r| l.t.partial_cmp(&r.t).unwrap_or(Ordering::Equal));
Self(v)
}
}
#[derive(Debug)]
pub struct Ray { pub struct Ray {
origin: Point, origin: Point,
pub direction: Vector, direction: Vector,
} }
impl Ray { impl Ray {
@ -16,10 +49,9 @@ impl Ray {
} }
pub fn intersect<'a>(&self, s: &'a Sphere) -> Intersections<'a> { pub fn intersect<'a>(&self, s: &'a Sphere) -> Intersections<'a> {
let r2 = self.transform(s.transformation().inverse()); let sphere_to_ray = self.origin - Point::new(0., 0., 0.);
let sphere_to_ray = r2.origin - Point::new(0., 0., 0.); let a = self.direction.dot(&self.direction);
let a = r2.direction.dot(&r2.direction); let b = 2. * self.direction.dot(&sphere_to_ray);
let b = 2. * r2.direction.dot(&sphere_to_ray);
let c = sphere_to_ray.dot(&sphere_to_ray) - 1.; let c = sphere_to_ray.dot(&sphere_to_ray) - 1.;
let discriminant = b * b - 4. * a * c; let discriminant = b * b - 4. * a * c;
@ -31,24 +63,15 @@ impl Ray {
let t2 = (-b + discriminant.sqrt()) / (2. * a); let t2 = (-b + discriminant.sqrt()) / (2. * a);
vec![ vec![
Intersection { t: t1, object: s }, Intersection { t: t1, object: &s },
Intersection { t: t2, object: s }, Intersection { t: t2, object: &s },
] ].into()
.into()
}
pub fn transform(&self, m: Matrix) -> Self {
Self {
origin: &m * self.origin,
direction: m * self.direction,
}
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::transforms::{scaling, translation};
#[test] #[test]
fn computing_point_from_distance() { fn computing_point_from_distance() {
@ -62,7 +85,7 @@ mod tests {
#[test] #[test]
fn ray_intersects_sphere_at_two_points() { fn ray_intersects_sphere_at_two_points() {
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.)); let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
let s = Sphere::default(); let s = Sphere::new();
let xs = r.intersect(&s); let xs = r.intersect(&s);
assert_eq!(xs.len(), 2); assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 4.); assert_eq!(xs[0].t, 4.);
@ -74,7 +97,7 @@ mod tests {
#[test] #[test]
fn ray_tangents_sphere_at_one_point() { fn ray_tangents_sphere_at_one_point() {
let r = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.)); let r = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.));
let s = Sphere::default(); let s = Sphere::new();
let xs = r.intersect(&s); let xs = r.intersect(&s);
assert_eq!(xs.len(), 2); assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 5.); assert_eq!(xs[0].t, 5.);
@ -84,7 +107,7 @@ mod tests {
#[test] #[test]
fn ray_misses_the_sphere() { fn ray_misses_the_sphere() {
let r = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.)); let r = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.));
let s = Sphere::default(); let s = Sphere::new();
let xs = r.intersect(&s); let xs = r.intersect(&s);
assert_eq!(xs.len(), 0); assert_eq!(xs.len(), 0);
} }
@ -92,7 +115,7 @@ mod tests {
#[test] #[test]
fn ray_originates_inside_the_sphere() { fn ray_originates_inside_the_sphere() {
let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.)); let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.));
let s = Sphere::default(); let s = Sphere::new();
let xs = r.intersect(&s); let xs = r.intersect(&s);
assert_eq!(xs.len(), 2); assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, -1.); assert_eq!(xs[0].t, -1.);
@ -102,7 +125,7 @@ mod tests {
#[test] #[test]
fn sphere_is_behind_the_ray() { fn sphere_is_behind_the_ray() {
let r = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.)); let r = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.));
let s = Sphere::default(); let s = Sphere::new();
let xs = r.intersect(&s); let xs = r.intersect(&s);
assert_eq!(xs.len(), 2); assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, -6.); assert_eq!(xs[0].t, -6.);
@ -111,17 +134,17 @@ mod tests {
#[test] #[test]
fn hit_all_intersections_are_positive() { fn hit_all_intersections_are_positive() {
let s = Sphere::default(); let s = Sphere::new();
let i1 = Intersection{ t: 1., object: &s }; let i1 = Intersection{ t: 1., object: &s };
let i2 = Intersection{ t: 2., object: &s }; let i2 = Intersection{ t: 2., object: &s };
let xs = Intersections::from(vec![i1.clone(), i2.clone()]); let xs = Intersections::from(vec![i1.clone(), i2]);
assert_eq!(xs.hit(), Some(&i1)); assert_eq!(xs.hit(), Some(&i1));
} }
#[test] #[test]
fn hit_some_intersections_are_negative() { fn hit_some_intersections_are_negative() {
let s = Sphere::default(); let s = Sphere::new();
let i1 = Intersection{ t: -1., object: &s }; let i1 = Intersection{ t: -1., object: &s };
let i2 = Intersection{ t: 1., object: &s }; let i2 = Intersection{ t: 1., object: &s };
let xs = Intersections::from(vec![i1, i2.clone()]); let xs = Intersections::from(vec![i1, i2.clone()]);
@ -131,7 +154,7 @@ mod tests {
#[test] #[test]
fn hit_all_intersections_are_negative() { fn hit_all_intersections_are_negative() {
let s = Sphere::default(); let s = Sphere::new();
let i1 = Intersection{ t: -2., object: &s }; let i1 = Intersection{ t: -2., object: &s };
let i2 = Intersection{ t: -1., object: &s }; let i2 = Intersection{ t: -1., object: &s };
let xs = Intersections::from(vec![i1, i2]); let xs = Intersections::from(vec![i1, i2]);
@ -141,7 +164,7 @@ mod tests {
#[test] #[test]
fn hit_is_always_lowest_nonnegative() { fn hit_is_always_lowest_nonnegative() {
let s = Sphere::default(); let s = Sphere::new();
let i1 = Intersection{ t: 5., object: &s }; let i1 = Intersection{ t: 5., object: &s };
let i2 = Intersection{ t: 7., object: &s }; let i2 = Intersection{ t: 7., object: &s };
let i3 = Intersection{ t: -3., object: &s }; let i3 = Intersection{ t: -3., object: &s };
@ -150,42 +173,4 @@ mod tests {
assert_eq!(xs.hit(), Some(&i4)); assert_eq!(xs.hit(), Some(&i4));
} }
#[test]
fn translate_a_ray() {
let r = Ray::new(Point::new(1., 2., 3.), Vector::new(0., 1., 0.));
let m = translation(3., 4., 5.);
let r2 = r.transform(m);
assert_eq!(r2.origin, Point::new(4., 6., 8.));
assert_eq!(r2.direction, Vector::new(0., 1., 0.));
}
#[test]
fn scale_a_ray() {
let r = Ray::new(Point::new(1., 2., 3.), Vector::new(0., 1., 0.));
let m = scaling(2., 3., 4.);
let r2 = r.transform(m);
assert_eq!(r2.origin, Point::new(2., 6., 12.));
assert_eq!(r2.direction, Vector::new(0., 3., 0.,));
}
#[test]
fn intersect_scaled_sphere_with_ray() {
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
let mut s = Sphere::default();
*s.transformation_mut() = scaling(2., 2., 2.);
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 3.);
assert_eq!(xs[1].t, 7.);
}
#[test]
fn intersect_translated_sphere_with_ray() {
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
let mut s = Sphere::default();
*s.transformation_mut() = translation(5., 0., 0.);
let xs = r.intersect(&s);
assert_eq!(xs.len(), 0);
}
} }

View File

@ -1,116 +1,13 @@
use super::{Matrix, Point, Vector, Material }; use crate::types::Point;
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct Sphere { pub struct Sphere {
origin: Point, origin: Point,
transformation: Matrix,
material: Material,
} }
impl Sphere { impl Sphere {
pub fn transformation(&self) -> &Matrix { pub fn new() -> Self {
&self.transformation Self{ origin: Point::new(0., 0., 0.) }
}
pub fn transformation_mut(&mut self) -> &mut Matrix{
&mut self.transformation
}
pub fn material(&self) -> &Material {
&self.material
}
pub fn material_mut(&mut self) -> &mut Material {
&mut self.material
}
pub fn normal_at(&self, world_point: &Point) -> Vector {
let inverted_transform = self.transformation.inverse();
let object_point = &inverted_transform * world_point;
let object_normal = (object_point - Point::new(0., 0., 0.)).normalize();
let mut world_normal = inverted_transform.transpose() * *object_normal;
world_normal.3 = 0.;
Vector::from(world_normal).normalize()
} }
} }
impl Default for Sphere {
fn default() -> Self {
Self {
origin: Point::new(0., 0., 0.),
transformation: Matrix::identity(),
material: Default::default(),
}
}
}
#[cfg(test)]
mod test {
use super::*;
use crate::transforms::{rotation_z, scaling, translation};
#[test]
fn sphere_has_default_transformation() {
let s = Sphere::default();
assert_eq!(s.transformation(), &Matrix::identity());
}
#[test]
fn change_a_spheres_transformation() {
let mut s = Sphere::default();
let t = translation(2., 3., 4.);
*s.transformation_mut() = t.clone();
assert_eq!(*s.transformation(), t);
}
#[test]
fn normal_of_sphere_on_x() {
let s = Sphere::default();
let n = s.normal_at(&Point::new(1., 0., 0.));
assert_eq!(n, Vector::new(1., 0., 0.));
}
#[test]
fn normal_of_sphere_on_y() {
let s = Sphere::default();
let n = s.normal_at(&Point::new(0., 1., 0.));
assert_eq!(n, Vector::new(0., 1., 0.));
}
#[test]
fn normal_of_sphere_on_z() {
let s = Sphere::default();
let n = s.normal_at(&Point::new(0., 0., 1.));
assert_eq!(n, Vector::new(0., 0., 1.));
}
#[test]
fn normal_of_sphere_on_nonaxial() {
let s = Sphere::default();
let n = s.normal_at(&Point::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
assert_eq!(n, Vector::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
}
#[test]
fn normal_is_normalized() {
let s = Sphere::default();
let n = s.normal_at(&Point::new(3_f64.sqrt() / 3., 3_f64.sqrt() / 3., 3_f64.sqrt() / 3.));
assert_eq!(n.normalize(), n);
}
#[test]
fn compute_normal_of_translated_sphere() {
let mut s = Sphere::default();
*s.transformation_mut() = translation(0., 1., 0.);
let n = s.normal_at(&Point::new(0., 1.70711, -0.70711));
assert_eq!(n, Vector::new(0., 0.70711, -0.70711));
}
#[test]
fn compute_normal_of_transformed_sphere() {
let mut s = Sphere::default();
*s.transformation_mut() = scaling(1., 0.5, 1.) * rotation_z(std::f64::consts::PI / 5.);
let n = s.normal_at(&Point::new(0., 2_f64.sqrt() / 2., -2_f64.sqrt() / 2.));
assert_eq!(n, Vector::new(0., 0.97014, -0.24254));
}
}

View File

@ -27,49 +27,28 @@ impl PartialEq for Tuple {
} }
} }
impl std::ops::Add<&Tuple> for &Tuple { impl std::ops::Add for Tuple {
type Output = Tuple; type Output = Tuple;
fn add(self, r: &Tuple) -> Self::Output { fn add(self, r: Tuple) -> Self::Output {
Tuple(self.0 + r.0, self.1 + r.1, self.2 + r.2, self.3 + r.3) Tuple(self.0 + r.0, self.1 + r.1, self.2 + r.2, self.3 + r.3)
} }
} }
impl std::ops::Add<Tuple> for Tuple { impl std::ops::Sub for Tuple {
type Output = Tuple;
fn add(self, r: Tuple) -> Self::Output {
&self + &r
}
}
impl std::ops::Sub<&Tuple> for &Tuple {
type Output = Tuple;
fn sub(self, r: &Tuple) -> Self::Output {
Tuple(self.0 - r.0, self.1 - r.1, self.2 - r.2, self.3 - r.3)
}
}
impl std::ops::Sub<Tuple> for Tuple {
type Output = Tuple; type Output = Tuple;
fn sub(self, r: Tuple) -> Self::Output { fn sub(self, r: Tuple) -> Self::Output {
&self - &r Tuple(self.0 - r.0, self.1 - r.1, self.2 - r.2, self.3 - r.3)
}
}
impl std::ops::Neg for &Tuple {
type Output = Tuple;
fn neg(self) -> Self::Output {
Tuple(-self.0, -self.1, -self.2, -self.3)
} }
} }
impl std::ops::Neg for Tuple { impl std::ops::Neg for Tuple {
type Output = Tuple; type Output = Tuple;
fn neg(self) -> Self::Output { fn neg(self) -> Self::Output {
-&self Tuple(-self.0, -self.1, -self.2, -self.3)
} }
} }
impl std::ops::Mul<f64> for &Tuple { impl std::ops::Mul<f64> for Tuple {
type Output = Tuple; type Output = Tuple;
fn mul(self, scalar: f64) -> Self::Output { fn mul(self, scalar: f64) -> Self::Output {
Tuple( Tuple(
@ -81,15 +60,7 @@ impl std::ops::Mul<f64> for &Tuple {
} }
} }
impl std::ops::Mul<f64> for Tuple { impl std::ops::Div<f64> for Tuple {
type Output = Tuple;
#[allow(clippy::op_ref)]
fn mul(self, scalar: f64) -> Self::Output {
&self * scalar
}
}
impl std::ops::Div<f64> for &Tuple {
type Output = Tuple; type Output = Tuple;
fn div(self, scalar: f64) -> Self::Output { fn div(self, scalar: f64) -> Self::Output {
Tuple( Tuple(
@ -100,11 +71,3 @@ impl std::ops::Div<f64> for &Tuple {
) )
} }
} }
impl std::ops::Div<f64> for Tuple {
type Output = Tuple;
#[allow(clippy::op_ref)]
fn div(self, scalar: f64) -> Self::Output {
&self / scalar
}
}

View File

@ -42,10 +42,6 @@ impl Vector {
let z = self.x() * r.y() - self.y() * r.x(); let z = self.x() * r.y() - self.y() * r.x();
Self::new(x, y, z) Self::new(x, y, z)
} }
pub fn reflect(&self, n: &Vector) -> Self {
self - (n * 2. * self.dot(n))
}
} }
impl Default for Vector { impl Default for Vector {
@ -70,36 +66,15 @@ impl std::ops::Deref for Vector {
impl std::ops::Add for Vector { impl std::ops::Add for Vector {
type Output = Vector; type Output = Vector;
fn add(self, r: Self) -> Self::Output { fn add(self, r: Self) -> Self {
Vector::from(self.0 + r.0) Vector::from(self.0 + r.0)
} }
} }
impl std::ops::Sub<&Vector> for &Vector {
type Output = Vector;
fn sub(self, r: &Vector) -> Self::Output {
Vector::from(self.0 - r.0)
}
}
impl std::ops::Sub<Vector> for &Vector {
type Output = Vector;
fn sub(self, r: Vector) -> Self::Output {
self - &r
}
}
impl std::ops::Sub<&Vector> for Vector {
type Output = Vector;
fn sub(self, r: &Vector) -> Self::Output {
&self - r
}
}
impl std::ops::Sub for Vector { impl std::ops::Sub for Vector {
type Output = Vector; type Output = Vector;
fn sub(self, r: Vector) -> Self::Output { fn sub(self, r: Self) -> Self {
&self - &r Vector::from(self.0 - r.0)
} }
} }
@ -112,13 +87,6 @@ impl std::ops::Neg for Vector {
} }
} }
impl std::ops::Mul<f64> for &Vector {
type Output = Vector;
fn mul(self, r: f64) -> Self::Output {
Vector::from(self.0 * r)
}
}
impl std::ops::Mul<f64> for Vector { impl std::ops::Mul<f64> for Vector {
type Output = Vector; type Output = Vector;
fn mul(self, r: f64) -> Self { fn mul(self, r: f64) -> Self {