diff --git a/rtchallenge/examples/eoc6.rs b/rtchallenge/examples/eoc6.rs index 5b14e66..6556546 100644 --- a/rtchallenge/examples/eoc6.rs +++ b/rtchallenge/examples/eoc6.rs @@ -44,7 +44,15 @@ fn main() -> Result<()> { let point = r.position(hit.t); let normal = hit.object.normal_at(point); let eye = -r.direction; - let color = lighting(&hit.object.material, &light, point, eye, normal, in_shadow); + let color = lighting( + &hit.object.material, + &hit.object, + &light, + point, + eye, + normal, + in_shadow, + ); c.set(x, y, color); } } diff --git a/rtchallenge/src/materials.rs b/rtchallenge/src/materials.rs index 1b2c3c0..6dc8adf 100644 --- a/rtchallenge/src/materials.rs +++ b/rtchallenge/src/materials.rs @@ -3,6 +3,7 @@ use derive_builder::Builder; use crate::{ lights::PointLight, patterns::StripePattern, + shapes::Shape, tuples::{dot, reflect, Color, Tuple}, Float, BLACK, WHITE, }; @@ -83,6 +84,7 @@ impl Default for Material { /// lights::PointLight, /// materials::{lighting, Material}, /// patterns::stripe_pattern, +/// shapes::Shape, /// tuples::{point, vector, Color}, /// Float, BLACK, WHITE, /// }; @@ -90,40 +92,41 @@ impl Default for Material { /// let in_shadow = false; /// let m = Material::default(); /// let position = point(0., 0., 0.); +/// let object = Shape::sphere(); /// /// // Lighting with the eye between the light and the surface. /// let eyev = vector(0., 0., -1.); /// let normalv = vector(0., 0., -1.); /// let light = PointLight::new(point(0., 0., -10.), WHITE); -/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); +/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(1.9, 1.9, 1.9)); /// /// // Lighting with the eye between the light and the surface, eye offset 45°. /// let eyev = vector(0., (2. as Float).sqrt() / 2., -(2. as Float).sqrt() / 2.); /// let normalv = vector(0., 0., -1.); /// let light = PointLight::new(point(0., 0., -10.), WHITE); -/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); +/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, WHITE); /// /// // Lighting with the eye opposite surface, light offset 45°. /// let eyev = vector(0., 0., -1.); /// let normalv = vector(0., 0., -1.); /// let light = PointLight::new(point(0., 10., -10.), WHITE); -/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); +/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(0.7364, 0.7364, 0.7364)); /// /// // Lighting with the eye in the path of the reflection vector. /// let eyev = vector(0., -(2.0 as Float).sqrt() / 2., -(2.0 as Float).sqrt() / 2.); /// let normalv = vector(0., 0., -1.); /// let light = PointLight::new(point(0., 10., -10.), WHITE); -/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); +/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(1.63639, 1.63639, 1.63639)); /// /// // Lighting with the light behind the surface. /// let eyev = vector(0., 0., -1.); /// let normalv = vector(0., 0., -1.); /// let light = PointLight::new(point(0., 0., 10.), WHITE); -/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); +/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(0.1, 0.1, 0.1)); /// /// // Lighting with the surface in shadow. @@ -131,7 +134,7 @@ impl Default for Material { /// let eyev = vector(0., 0., -1.); /// let normalv = vector(0., 0., -1.); /// let light = PointLight::new(point(0., 0., -10.), WHITE); -/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); +/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(0.1, 0.1, 0.1)); /// /// // Lighting with a pattern applied. @@ -146,13 +149,30 @@ impl Default for Material { /// let eyev = vector(0., 0., -1.); /// let normalv = vector(0., 0., -1.); /// let light = PointLight::new(point(0., 0., -10.), WHITE); -/// let c1 = lighting(&m, &light, point(0.9, 0., 0.), eyev, normalv, false); -/// let c2 = lighting(&m, &light, point(1.1, 0., 0.), eyev, normalv, false); +/// let c1 = lighting( +/// &m, +/// &object, +/// &light, +/// point(0.9, 0., 0.), +/// eyev, +/// normalv, +/// false, +/// ); +/// let c2 = lighting( +/// &m, +/// &object, +/// &light, +/// point(1.1, 0., 0.), +/// eyev, +/// normalv, +/// false, +/// ); /// assert_eq!(c1, WHITE); /// assert_eq!(c2, BLACK); /// ``` pub fn lighting( material: &Material, + object: &Shape, light: &PointLight, point: Tuple, eyev: Tuple, @@ -162,7 +182,7 @@ pub fn lighting( // Combine the surface color with the light's color. let color = match &material.color { ColorMapper::Constant(color) => *color, - ColorMapper::StripePattern(pat) => pat.stripe_at(point), + ColorMapper::StripePattern(pat) => pat.stripe_at_object(object, point), }; let effective_color = color * light.intensity; // Find the direciton of the light source. diff --git a/rtchallenge/src/patterns.rs b/rtchallenge/src/patterns.rs index 78e0b06..438c9a3 100644 --- a/rtchallenge/src/patterns.rs +++ b/rtchallenge/src/patterns.rs @@ -1,9 +1,15 @@ -use crate::tuples::{Color, Tuple}; +use crate::{ + matrices::Matrix4x4, + shapes::Shape, + tuples::{Color, Tuple}, +}; #[derive(Debug, PartialEq, Clone)] pub struct StripePattern { pub a: Color, pub b: Color, + transform: Matrix4x4, + inverse_transform: Matrix4x4, } /// Create a material pattern that alternates between the given colors along each unit of the @@ -18,7 +24,12 @@ pub struct StripePattern { /// assert_eq!(pattern.b, WHITE); /// ``` pub fn stripe_pattern(a: Color, b: Color) -> StripePattern { - StripePattern { a, b } + StripePattern { + a, + b, + transform: Matrix4x4::identity(), + inverse_transform: Matrix4x4::identity(), + } } impl StripePattern { @@ -64,4 +75,54 @@ impl StripePattern { self.b } } + /// Sample the color at the given world point on the given object. + /// This function respects the object and the pattern's transform matrix. + /// + /// # Examples + /// ``` + /// use rtchallenge::{ + /// matrices::scaling, + /// patterns::stripe_pattern, + /// shapes::{Shape, ShapeBuilder}, + /// tuples::point, + /// BLACK, WHITE, + /// }; + /// + /// # fn main() -> Result<(), Box> { + /// + /// // Stripes with an object transformation. + /// let object = ShapeBuilder::sphere() + /// .transform(scaling(2., 2., 2.)) + /// .build()?; + /// let pattern = stripe_pattern(WHITE, BLACK); + /// let c = pattern.stripe_at_object(&object, point(1.5, 0., 0.)); + /// assert_eq!(c, WHITE); + /// + /// // Stripes with an pattern transformation. + /// let object = Shape::sphere(); + /// let mut pattern = stripe_pattern(WHITE, BLACK); + /// pattern.set_transform(scaling(2., 2., 2.)); + /// let c = pattern.stripe_at_object(&object, point(1.5, 0., 0.)); + /// assert_eq!(c, WHITE); + /// + /// # Ok(()) + /// # } + /// ``` + pub fn stripe_at_object(&self, object: &Shape, world_point: Tuple) -> Color { + let object_point = object.inverse_transform() * world_point; + let pattern_point = self.inverse_transform * object_point; + self.stripe_at(pattern_point) + } + pub fn transform(&self) -> Matrix4x4 { + self.transform + } + + pub fn inverse_transform(&self) -> Matrix4x4 { + self.inverse_transform + } + + pub fn set_transform(&mut self, t: Matrix4x4) { + self.transform = t; + self.inverse_transform = t.inverse(); + } } diff --git a/rtchallenge/src/shapes.rs b/rtchallenge/src/shapes.rs index aa330d3..f2b02bd 100644 --- a/rtchallenge/src/shapes.rs +++ b/rtchallenge/src/shapes.rs @@ -347,6 +347,10 @@ impl Shape { self.transform } + pub fn inverse_transform(&self) -> Matrix4x4 { + self.inverse_transform + } + pub fn set_transform(&mut self, t: Matrix4x4) { self.transform = t; self.inverse_transform = t.inverse(); diff --git a/rtchallenge/src/world.rs b/rtchallenge/src/world.rs index 470dde7..22d9cb5 100644 --- a/rtchallenge/src/world.rs +++ b/rtchallenge/src/world.rs @@ -141,6 +141,7 @@ impl World { let shadowed = self.is_shadowed(comps.over_point, light); acc + lighting( &comps.object.material, + &comps.object, light, comps.over_point, comps.eyev,