From a1de85036d09821a21d8f7b21bb85f83a8ff4cb2 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 12 Aug 2018 20:58:28 -0700 Subject: [PATCH] Support multiple spheres. --- bheisler/src/lib.rs | 68 +++++++++++++++++++++++++++++++-------- bheisler/src/rendering.rs | 26 +++++++++++---- bheisler/src/scene.rs | 24 +++++++++++++- 3 files changed, 96 insertions(+), 22 deletions(-) diff --git a/bheisler/src/lib.rs b/bheisler/src/lib.rs index 09a8722..4c937ab 100644 --- a/bheisler/src/lib.rs +++ b/bheisler/src/lib.rs @@ -7,7 +7,6 @@ pub mod vector; use image::GenericImage; use image::Pixel; -use rendering::Intersectable; use scene::Scene; pub fn render(scene: &Scene) -> image::DynamicImage { @@ -17,8 +16,8 @@ pub fn render(scene: &Scene) -> image::DynamicImage { for y in 0..scene.height { let ray = rendering::Ray::create_prime(x, y, scene); - if scene.sphere.intersect(&ray) { - let c = &scene.sphere.color; + if let Some(intersection) = scene.trace(&ray) { + let c = &intersection.object.color; image.put_pixel(x, y, image::Rgba(c.to_rgba())) } else { image.put_pixel(x, y, black); @@ -38,19 +37,60 @@ fn test_can_render_scene() { width: 800, height: 600, fov: 90.0, - sphere: Sphere { - center: Point { - x: 0.0, - y: 0.0, - z: -5.0, + spheres: vec![ + Sphere { + center: Point { + x: 2.0, + y: 1.0, + z: -4.0, + }, + radius: 1.5, + color: Color { + red: 1.0, + green: 0.2, + blue: 0.2, + }, }, - radius: 1.0, - color: Color { - red: 0.4, - green: 1.0, - blue: 0.4, + Sphere { + center: Point { + x: 0.0, + y: 0.0, + z: -5.0, + }, + radius: 1.0, + color: Color { + red: 0.2, + green: 1.0, + blue: 0.2, + }, }, - }, + Sphere { + center: Point { + x: -3.0, + y: 1.0, + z: -6.0, + }, + radius: 2.0, + color: Color { + red: 0.2, + green: 0.2, + blue: 1.0, + }, + }, + Sphere { + center: Point { + x: 2.0, + y: 1.0, + z: -10.0, + }, + radius: 5.0, + color: Color { + red: 1.0, + green: 1.0, + blue: 1.0, + }, + }, + ], }; let img: image::DynamicImage = render(&scene); diff --git a/bheisler/src/rendering.rs b/bheisler/src/rendering.rs index 8c443e6..29460a6 100644 --- a/bheisler/src/rendering.rs +++ b/bheisler/src/rendering.rs @@ -4,7 +4,7 @@ use scene::Scene; use vector::Vector3; pub trait Intersectable { - fn intersect(&self, ray: &Ray) -> bool; + fn intersect(&self, ray: &Ray) -> Option; } pub struct Sphere { @@ -14,16 +14,28 @@ pub struct Sphere { } impl Intersectable for Sphere { - fn intersect(&self, ray: &Ray) -> bool { + fn intersect(&self, ray: &Ray) -> Option { //Create a line segment between the ray origin and the center of the sphere let l: Vector3 = (self.center - ray.origin).into(); //Use l as a hypotenuse and find the length of the adjacent side - let adj2 = l.dot(&ray.direction); + let adj = l.dot(&ray.direction); //Find the length-squared of the opposite side - //This is equivalent to (but faster than) (l.length() * l.length()) - (adj2 * adj2) - let d2 = l.dot(&l) - (adj2 * adj2); - //If that length-squared is less than radius squared, the ray intersects the sphere - d2 < (self.radius * self.radius) + //This is equivalent to (but faster than) (l.length() * l.length()) - (adj * adj) + let d2 = l.dot(&l) - (adj * adj); + let radius2 = self.radius * self.radius; + if d2 > radius2 { + return None; + } + let thc = (radius2 - d2).sqrt(); + let t0 = adj - thc; + let t1 = adj + thc; + + if t0 < 0.0 && t1 < 0.0 { + return None; + } + + let distance = if t0 < t1 { t0 } else { t1 }; + Some(distance) } } diff --git a/bheisler/src/scene.rs b/bheisler/src/scene.rs index c89562c..62c98c1 100644 --- a/bheisler/src/scene.rs +++ b/bheisler/src/scene.rs @@ -1,5 +1,18 @@ +use rendering::Intersectable; +use rendering::Ray; use rendering::Sphere; +pub struct Intersection<'a> { + pub distance: f32, + pub object: &'a Sphere, +} + +impl<'a> Intersection<'a> { + pub fn new(distance: f32, object: &Sphere) -> Intersection { + Intersection { distance, object } + } +} + pub struct Color { pub red: f32, pub green: f32, @@ -22,5 +35,14 @@ pub struct Scene { pub width: u32, pub height: u32, pub fov: f32, - pub sphere: Sphere, + pub spheres: Vec, +} + +impl Scene { + pub fn trace(&self, ray: &Ray) -> Option { + self.spheres + .iter() + .filter_map(|s| s.intersect(ray).map(|d| Intersection::new(d, s))) + .min_by(|i1, i2| i1.distance.partial_cmp(&i2.distance).unwrap()) + } }