diff --git a/ray-tracer/src/bin/sphere.rs b/ray-tracer/src/bin/sphere.rs index 3fef19e..6af0048 100644 --- a/ray-tracer/src/bin/sphere.rs +++ b/ray-tracer/src/bin/sphere.rs @@ -7,7 +7,7 @@ const SIZE: usize = 1000; fn main() { let canvas = Arc::new(RwLock::new(Canvas::new(SIZE, SIZE))); let mut sphere = Sphere::default(); - sphere.set_transformation(shearing(1., 0., 0., 0., 0., 0.)); + *sphere.transformation_mut() = shearing(1., 0., 0., 0., 0., 0.); let camera = Point::new(0., 1., -5.); let wall_z = 10.; diff --git a/ray-tracer/src/types/color.rs b/ray-tracer/src/types/color.rs index 50c8586..a7682f6 100644 --- a/ray-tracer/src/types/color.rs +++ b/ray-tracer/src/types/color.rs @@ -22,7 +22,6 @@ impl Color { pub fn blue(&self) -> f64 { self.0.2 } - } impl Default for Color { diff --git a/ray-tracer/src/types/light.rs b/ray-tracer/src/types/light.rs new file mode 100644 index 0000000..ac36641 --- /dev/null +++ b/ray-tracer/src/types/light.rs @@ -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 } + } +} diff --git a/ray-tracer/src/types/material.rs b/ray-tracer/src/types/material.rs new file mode 100644 index 0000000..85dbd23 --- /dev/null +++ b/ray-tracer/src/types/material.rs @@ -0,0 +1,118 @@ +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: &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); + 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); + 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 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)); + } +} diff --git a/ray-tracer/src/types/mod.rs b/ray-tracer/src/types/mod.rs index 34bc3c6..b0edbc0 100644 --- a/ray-tracer/src/types/mod.rs +++ b/ray-tracer/src/types/mod.rs @@ -1,6 +1,8 @@ mod canvas; mod color; mod intersections; +mod light; +mod material; mod matrix; mod point; mod ray; @@ -11,6 +13,8 @@ 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; diff --git a/ray-tracer/src/types/point.rs b/ray-tracer/src/types/point.rs index cc8b5d2..ae8a8f2 100644 --- a/ray-tracer/src/types/point.rs +++ b/ray-tracer/src/types/point.rs @@ -58,6 +58,20 @@ impl std::ops::Sub<&Point> for &Point { } } +impl std::ops::Sub 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 for Point { type Output = Vector; fn sub(self, r: Point) -> Self::Output { diff --git a/ray-tracer/src/types/ray.rs b/ray-tracer/src/types/ray.rs index b4d65e9..9956615 100644 --- a/ray-tracer/src/types/ray.rs +++ b/ray-tracer/src/types/ray.rs @@ -172,7 +172,7 @@ mod tests { 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.set_transformation(scaling(2., 2., 2.)); + *s.transformation_mut() = scaling(2., 2., 2.); let xs = r.intersect(&s); assert_eq!(xs.len(), 2); assert_eq!(xs[0].t, 3.); @@ -183,7 +183,7 @@ mod tests { 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.set_transformation(translation(5., 0., 0.)); + *s.transformation_mut() = translation(5., 0., 0.); let xs = r.intersect(&s); assert_eq!(xs.len(), 0); } diff --git a/ray-tracer/src/types/sphere.rs b/ray-tracer/src/types/sphere.rs index 3e9dc44..eafd26d 100644 --- a/ray-tracer/src/types/sphere.rs +++ b/ray-tracer/src/types/sphere.rs @@ -1,9 +1,10 @@ -use crate::types::{Matrix, Point, Vector}; +use super::{Matrix, Point, Vector, Material }; #[derive(Debug, PartialEq)] pub struct Sphere { origin: Point, transformation: Matrix, + material: Material, } impl Sphere { @@ -11,8 +12,16 @@ impl Sphere { &self.transformation } - pub fn set_transformation(&mut self, m: Matrix) { - self.transformation = m + 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 { @@ -30,6 +39,7 @@ impl Default for Sphere { Self { origin: Point::new(0., 0., 0.), transformation: Matrix::identity(), + material: Default::default(), } } } @@ -49,7 +59,7 @@ mod test { fn change_a_spheres_transformation() { let mut s = Sphere::default(); let t = translation(2., 3., 4.); - s.set_transformation(t.clone()); + *s.transformation_mut() = t.clone(); assert_eq!(*s.transformation(), t); } @@ -91,7 +101,7 @@ mod test { #[test] fn compute_normal_of_translated_sphere() { let mut s = Sphere::default(); - s.set_transformation(translation(0., 1., 0.)); + *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)); } @@ -99,7 +109,7 @@ mod test { #[test] fn compute_normal_of_transformed_sphere() { let mut s = Sphere::default(); - s.set_transformation(scaling(1., 0.5, 1.) * rotation_z(std::f64::consts::PI / 5.)); + *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)); }