Compare commits

..

11 Commits

18 changed files with 657 additions and 97 deletions

11
Cargo.lock generated
View File

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

View File

@ -6,6 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
rayon = "1.10.0"
[[bin]]
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 _ = file.write(ppm.as_bytes());

View File

@ -0,0 +1,101 @@
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)]
pub struct PPM(String);
impl From<Canvas> for PPM {
fn from(c: Canvas) -> Self {
impl From<&Canvas> for PPM {
fn from(c: &Canvas) -> Self {
// let v = vec![0.; c.width() * c.height() * 3];
let header = format!("P3\n{} {}\n255\n", c.width(), c.height());
@ -68,7 +68,7 @@ mod tests {
#[test]
fn construct_ppm_header() {
let c = Canvas::new(5, 3);
let ppm = PPM::from(c);
let ppm = PPM::from(&c);
assert!(ppm.starts_with(
"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 0 0 0 0 0 0 0 255\n";
let ppm = PPM::from(c);
let ppm = PPM::from(&c);
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\
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);
}
}

View File

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

View File

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

View File

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

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

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

View File

@ -1,5 +1,8 @@
mod canvas;
mod color;
mod intersections;
mod light;
mod material;
mod matrix;
mod point;
mod ray;
@ -9,6 +12,9 @@ mod vector;
pub use canvas::Canvas;
pub use color::Color;
pub use intersections::{Intersections, Intersection};
pub use light::PointLight;
pub use material::Material;
pub use matrix::Matrix;
pub use point::Point;
pub use ray::Ray;
@ -154,4 +160,26 @@ mod tests {
*c.pixel_mut(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,10 +51,31 @@ impl std::ops::Add<Vector> for Point {
}
}
impl std::ops::Sub for Point {
impl std::ops::Sub<&Point> for &Point {
type Output = Vector;
fn sub(self, r: &Point) -> Self::Output {
Vector::from(self.0 - r.0)
}
}
impl std::ops::Sub<Point> for &Point {
type Output = Vector;
fn sub(self, r: Point) -> Self::Output {
Vector::from(self.0 - r.0)
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
}
}

View File

@ -1,42 +1,9 @@
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)
}
}
use super::{Intersection, Intersections, Matrix, Point, Sphere, Vector};
#[derive(Debug)]
pub struct Ray {
origin: Point,
direction: Vector,
pub direction: Vector,
}
impl Ray {
@ -49,9 +16,10 @@ impl Ray {
}
pub fn intersect<'a>(&self, s: &'a Sphere) -> Intersections<'a> {
let sphere_to_ray = self.origin - Point::new(0., 0., 0.);
let a = self.direction.dot(&self.direction);
let b = 2. * self.direction.dot(&sphere_to_ray);
let r2 = self.transform(s.transformation().inverse());
let sphere_to_ray = r2.origin - Point::new(0., 0., 0.);
let a = r2.direction.dot(&r2.direction);
let b = 2. * r2.direction.dot(&sphere_to_ray);
let c = sphere_to_ray.dot(&sphere_to_ray) - 1.;
let discriminant = b * b - 4. * a * c;
@ -63,15 +31,24 @@ impl Ray {
let t2 = (-b + discriminant.sqrt()) / (2. * a);
vec![
Intersection { t: t1, object: &s },
Intersection { t: t2, object: &s },
].into()
Intersection { t: t1, object: s },
Intersection { t: t2, object: s },
]
.into()
}
pub fn transform(&self, m: Matrix) -> Self {
Self {
origin: &m * self.origin,
direction: m * self.direction,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transforms::{scaling, translation};
#[test]
fn computing_point_from_distance() {
@ -85,7 +62,7 @@ mod tests {
#[test]
fn ray_intersects_sphere_at_two_points() {
let r = Ray::new(Point::new(0., 0., -5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let s = Sphere::default();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 4.);
@ -97,7 +74,7 @@ mod tests {
#[test]
fn ray_tangents_sphere_at_one_point() {
let r = Ray::new(Point::new(0., 1., -5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let s = Sphere::default();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, 5.);
@ -107,7 +84,7 @@ mod tests {
#[test]
fn ray_misses_the_sphere() {
let r = Ray::new(Point::new(0., 2., -5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let s = Sphere::default();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 0);
}
@ -115,7 +92,7 @@ mod tests {
#[test]
fn ray_originates_inside_the_sphere() {
let r = Ray::new(Point::new(0., 0., 0.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let s = Sphere::default();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, -1.);
@ -125,7 +102,7 @@ mod tests {
#[test]
fn sphere_is_behind_the_ray() {
let r = Ray::new(Point::new(0., 0., 5.), Vector::new(0., 0., 1.));
let s = Sphere::new();
let s = Sphere::default();
let xs = r.intersect(&s);
assert_eq!(xs.len(), 2);
assert_eq!(xs[0].t, -6.);
@ -134,17 +111,17 @@ mod tests {
#[test]
fn hit_all_intersections_are_positive() {
let s = Sphere::new();
let s = Sphere::default();
let i1 = Intersection { t: 1., object: &s };
let i2 = Intersection { t: 2., object: &s };
let xs = Intersections::from(vec![i1.clone(), i2]);
let xs = Intersections::from(vec![i1.clone(), i2.clone()]);
assert_eq!(xs.hit(), Some(&i1));
}
#[test]
fn hit_some_intersections_are_negative() {
let s = Sphere::new();
let s = Sphere::default();
let i1 = Intersection { t: -1., object: &s };
let i2 = Intersection { t: 1., object: &s };
let xs = Intersections::from(vec![i1, i2.clone()]);
@ -154,7 +131,7 @@ mod tests {
#[test]
fn hit_all_intersections_are_negative() {
let s = Sphere::new();
let s = Sphere::default();
let i1 = Intersection { t: -2., object: &s };
let i2 = Intersection { t: -1., object: &s };
let xs = Intersections::from(vec![i1, i2]);
@ -164,7 +141,7 @@ mod tests {
#[test]
fn hit_is_always_lowest_nonnegative() {
let s = Sphere::new();
let s = Sphere::default();
let i1 = Intersection { t: 5., object: &s };
let i2 = Intersection { t: 7., object: &s };
let i3 = Intersection { t: -3., object: &s };
@ -173,4 +150,42 @@ mod tests {
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,13 +1,116 @@
use crate::types::Point;
use super::{Matrix, Point, Vector, Material };
#[derive(Debug, PartialEq)]
pub struct Sphere {
origin: Point,
transformation: Matrix,
material: Material,
}
impl Sphere {
pub fn new() -> Self {
Self{ origin: Point::new(0., 0., 0.) }
pub fn transformation(&self) -> &Matrix {
&self.transformation
}
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,28 +27,49 @@ impl PartialEq for Tuple {
}
}
impl std::ops::Add for Tuple {
impl std::ops::Add<&Tuple> for &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)
}
}
impl std::ops::Sub for Tuple {
impl std::ops::Add<Tuple> for Tuple {
type Output = Tuple;
fn sub(self, r: Tuple) -> Self::Output {
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::Neg for Tuple {
impl std::ops::Sub<Tuple> for Tuple {
type Output = Tuple;
fn sub(self, r: Tuple) -> Self::Output {
&self - &r
}
}
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::Mul<f64> for Tuple {
impl std::ops::Neg for Tuple {
type Output = Tuple;
fn neg(self) -> Self::Output {
-&self
}
}
impl std::ops::Mul<f64> for &Tuple {
type Output = Tuple;
fn mul(self, scalar: f64) -> Self::Output {
Tuple(
@ -60,7 +81,15 @@ impl std::ops::Mul<f64> for Tuple {
}
}
impl std::ops::Div<f64> for Tuple {
impl std::ops::Mul<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;
fn div(self, scalar: f64) -> Self::Output {
Tuple(
@ -71,3 +100,11 @@ 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,6 +42,10 @@ impl Vector {
let z = self.x() * r.y() - self.y() * r.x();
Self::new(x, y, z)
}
pub fn reflect(&self, n: &Vector) -> Self {
self - (n * 2. * self.dot(n))
}
}
impl Default for Vector {
@ -66,15 +70,36 @@ impl std::ops::Deref for Vector {
impl std::ops::Add for Vector {
type Output = Vector;
fn add(self, r: Self) -> Self {
fn add(self, r: Self) -> 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 {
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 {
type Output = Vector;
fn sub(self, r: Self) -> Self {
Vector::from(self.0 - r.0)
fn sub(self, r: Vector) -> Self::Output {
&self - &r
}
}
@ -87,6 +112,13 @@ 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 {
type Output = Vector;
fn mul(self, r: f64) -> Self {