diff --git a/rtchallenge/examples/eoc7.rs b/rtchallenge/examples/eoc7.rs index c0ff874..146d84c 100644 --- a/rtchallenge/examples/eoc7.rs +++ b/rtchallenge/examples/eoc7.rs @@ -95,7 +95,7 @@ fn main() -> Result<()> { }; let mut world = World::default(); - world.light = Some(light); + world.lights = vec![light]; world.objects = vec![floor, left_wall, right_wall, middle, right, left]; let image = camera.render(&world); diff --git a/rtchallenge/examples/eoc8.rs b/rtchallenge/examples/eoc8.rs index c7a44cd..ca8abfc 100644 --- a/rtchallenge/examples/eoc8.rs +++ b/rtchallenge/examples/eoc8.rs @@ -35,7 +35,10 @@ fn main() -> Result<()> { let height = 1440; let light_position = Tuple::point(-10., 10., -10.); let light_color = WHITE; - let light = PointLight::new(light_position, light_color); + let light1 = PointLight::new(light_position, light_color); + let light_position = Tuple::point(10., 10., -10.); + let light_color = Color::new(0.0, 0.0, 1.0); + let light2 = PointLight::new(light_position, light_color); let mut camera = Camera::new(width, height, PI / 4.); let from = Tuple::point(0., 1.5, -5.); let to = Tuple::point(0., 1., 0.); @@ -82,7 +85,7 @@ fn main() -> Result<()> { let mut right = Sphere::default(); right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5)); right.material = Material { - color: Color::new(0.5, 1., 0.1), + color: Color::new(1., 1., 1.), diffuse: 0.7, specular: 0.3, ..Material::default() @@ -100,7 +103,7 @@ fn main() -> Result<()> { }; let mut world = World::default(); - world.light = Some(light); + world.lights = vec![light1, light2]; world.objects = vec![floor, left_wall, right_wall, middle, right, left]; let image = camera.render(&world); diff --git a/rtchallenge/src/spheres.rs b/rtchallenge/src/spheres.rs index 4d45558..d420974 100644 --- a/rtchallenge/src/spheres.rs +++ b/rtchallenge/src/spheres.rs @@ -9,7 +9,6 @@ use crate::{ #[derive(Debug, PartialEq, Clone)] /// Sphere represents the unit-sphere (radius of unit 1.) at the origin 0., 0., 0. pub struct Sphere { - // TODO(wathiede): cache inverse to speed up intersect. transform: Matrix4x4, inverse_transform: Matrix4x4, pub material: Material, diff --git a/rtchallenge/src/world.rs b/rtchallenge/src/world.rs index 29a73d8..d4f93c0 100644 --- a/rtchallenge/src/world.rs +++ b/rtchallenge/src/world.rs @@ -6,7 +6,7 @@ use crate::{ rays::Ray, spheres::{intersect, Sphere}, tuples::{Color, Tuple}, - BLACK, WHITE, + Float, BLACK, WHITE, }; /// World holds all drawable objects and the light(s) that illuminate them. @@ -17,13 +17,12 @@ use crate::{ /// /// let w = World::default(); /// assert!(w.objects.is_empty()); -/// assert_eq!(w.light, None); +/// assert_eq!(w.lights.len(), 0); /// ``` #[derive(Clone, Debug, Default)] pub struct World { - // TODO(wathiede): make this a list of abstract Light traits. - pub light: Option, + pub lights: Vec, pub objects: Vec, } @@ -36,7 +35,7 @@ impl World { /// /// let w = World::test_world(); /// assert_eq!(w.objects.len(), 2); - /// assert!(w.light.is_some()); + /// assert!(!w.lights.is_empty()); /// ``` pub fn test_world() -> World { let light = PointLight::new(Tuple::point(-10., 10., -10.), WHITE); @@ -50,7 +49,7 @@ impl World { let mut s2 = Sphere::default(); s2.set_transform(Matrix4x4::scaling(0.5, 0.5, 0.5)); World { - light: Some(light), + lights: vec![light], objects: vec![s1, s2], } } @@ -110,7 +109,7 @@ impl World { /// /// // Shading an intersection from the inside. /// let mut w = World::test_world(); - /// w.light = Some(PointLight::new(Tuple::point(0., 0.25, 0.), WHITE)); + /// w.lights = vec![PointLight::new(Tuple::point(0., 0.25, 0.), WHITE)]; /// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.)); /// let s = &w.objects[1]; /// let i = Intersection::new(0.5, &s); @@ -120,7 +119,7 @@ impl World { /// /// // Shading with an intersection in shadow. /// let mut w = World::default(); - /// w.light = Some(PointLight::new(Tuple::point(0., 0., -10.), WHITE)); + /// w.lights = vec![PointLight::new(Tuple::point(0., 0., -10.), WHITE)]; /// let s1 = Sphere::default(); /// let mut s2 = Sphere::default(); /// s2.set_transform(Matrix4x4::translation(0., 0., 10.)); @@ -132,17 +131,21 @@ impl World { /// assert_eq!(c, Color::new(0.1, 0.1, 0.1)); /// ``` pub fn shade_hit(&self, comps: &PrecomputedData) -> Color { - // TODO(wathiede): support multiple light sources by iterating over all - // the light sources and summing the calls to lighting. - let shadowed = self.is_shadowed(comps.over_point); - lighting( - &comps.object.material, - &self.light.as_ref().expect("World has no lights"), - comps.over_point, - comps.eyev, - comps.normalv, - shadowed, - ) + let c = self + .lights + .iter() + .fold(Color::new(0., 0., 0.), |acc, light| { + let shadowed = self.is_shadowed(comps.over_point, light); + acc + lighting( + &comps.object.material, + light, + comps.over_point, + comps.eyev, + comps.normalv, + shadowed, + ) + }); + c / self.lights.len() as Float } /// Compute color for given ray fired at the world. /// @@ -203,28 +206,24 @@ impl World { /// }; /// /// let w = World::test_world(); + /// let light = &w.lights[0]; /// /// // There is no shadow when nothing is collinear with point and light. /// let p = Tuple::point(0.,10.,0.); - /// assert_eq!(w.is_shadowed(p), false); + /// assert_eq!(w.is_shadowed(p,light), false); /// /// // There shadow when an object is between the point and the light. /// let p = Tuple::point(10.,-10.,10.); - /// assert_eq!(w.is_shadowed(p), true); + /// assert_eq!(w.is_shadowed(p,light), true); /// /// // There is no shadow when an object is behind the light. /// let p = Tuple::point(-20.,20.,-20.); - /// assert_eq!(w.is_shadowed(p), false); + /// assert_eq!(w.is_shadowed(p,light), false); /// /// // There is no shadow when an object is behind the point. /// let p = Tuple::point(-2.,2.,-2.); - /// assert_eq!(w.is_shadowed(p), false); - pub fn is_shadowed(&self, point: Tuple) -> bool { - // TODO(wathiede): how to make this multi light aware? - let light = self - .light - .as_ref() - .expect("cannot compute is_shadowed in world with no light"); + /// assert_eq!(w.is_shadowed(p,light), false); + pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool { let v = light.position - point; let distance = v.magnitude(); let direction = v.normalize();