From 32d66cb34062d217a5716eadb4cb2e4c43470cfa Mon Sep 17 00:00:00 2001 From: Simon Coffey Date: Mon, 22 Apr 2019 19:41:32 +0100 Subject: [PATCH] Add Plane support This required a fair bit of faffing, and I'm not completely sure I know what I'm really doing with the boxing. However! My first polymorphism and traits. I should probably read the book properly, but who's got time for that. Some fun also since rust modulo arithmetic seems to be non-intuitive, and the euclidean versions aren't stable yet. https://github.com/rust-lang/rfcs/blob/master/text/2169-euclidean-modulo.md https://github.com/rust-lang/rust/issues/49048 --- src/light/mod.rs | 4 ++-- src/main.rs | 23 ++++++++++++-------- src/object/mod.rs | 12 ++++++++++ src/object/plane/mod.rs | 47 ++++++++++++++++++++++++++++++++++++++++ src/object/sphere/mod.rs | 17 ++++++++++++--- 5 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 src/object/plane/mod.rs diff --git a/src/light/mod.rs b/src/light/mod.rs index 077e5ff..a368147 100644 --- a/src/light/mod.rs +++ b/src/light/mod.rs @@ -1,5 +1,5 @@ use crate::vector::Vec; -use crate::object::sphere::Sphere; +use crate::object::Object; use crate::ray::Ray; pub struct Light { @@ -12,7 +12,7 @@ impl Light { Light { center, power } } - pub fn illuminate(&self, point: Vec, normal: Vec, objects: &[Sphere]) -> f64 { + pub fn illuminate(&self, point: Vec, normal: Vec, objects: &[Box]) -> f64 { let point_to_light = self.center.subtract(point); let length = point_to_light.length(); diff --git a/src/main.rs b/src/main.rs index 9bc6c74..9f6cbf8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,20 +10,22 @@ mod camera; use vector::Vec; use color::Color; +use object::Object; use object::sphere::Sphere; +use object::plane::Plane; use light::Light; use film::Film; use camera::Camera; use ray::Ray; -fn trace(objects: &[Sphere], lights: &[Light], ray: Ray, remaining_calls: u32) -> Option { +fn trace(objects: &[Box], lights: &[Light], ray: Ray, remaining_calls: u32) -> Option { if remaining_calls <= 0 { return None; } let mut min_t = std::f64::INFINITY; - let mut min_object: Option<&Sphere> = None; + let mut min_object: Option<&Box> = None; for object in objects { if let Some(t) = object.intersect(ray) { @@ -44,7 +46,8 @@ fn trace(objects: &[Sphere], lights: &[Light], ray: Ray, remaining_calls: u32) - acc + light.illuminate(intersection, normal, &objects) ); - let illuminated_color = hit.color.scale(energy).scale(1.0 - hit.reflectance); + let surface_color = hit.color_at(intersection); + let illuminated_color = surface_color.scale(energy).scale(1.0 - hit.reflectance()); let dot = ray.direction.dot(normal); let reflection_direction = ray.direction.subtract(normal.scale(2.0 * dot)); @@ -52,7 +55,7 @@ fn trace(objects: &[Sphere], lights: &[Light], ray: Ray, remaining_calls: u32) - let reflection_ray = Ray { origin: reflection_point, direction: reflection_direction }; if let Some(incoming_color) = trace(objects, lights, reflection_ray, remaining_calls - 1) { - let reflection_color = hit.color.scale(hit.reflectance / 255.0); + let reflection_color = surface_color.scale(hit.reflectance() / 255.0); let reflected_color = Color::new( incoming_color.r * reflection_color.r, incoming_color.g * reflection_color.g, @@ -76,11 +79,13 @@ fn main() { let film = Film::new(Vec::new(-0.8, 1.2, 1.3), Vec::new(1.2, -0.3, 1.3)); let camera = Camera { eye, film }; - let objects = vec![ - Sphere::new(Vec::new(-1.0, 1.0, 5.0), 0.8, Color::new(255.0, 50.0, 50.0), 0.2), - Sphere::new(Vec::new(1.0, 1.0, 5.0), 0.8, Color::new(50.0, 255.0, 100.0), 0.8), - Sphere::new(Vec::new(2.5, 1.0, 5.0), 0.8, Color::new(50.0, 100.0, 255.0), 0.0), - Sphere::new(Vec::new(-1.0, 2.0, 4.0), 0.2, Color::new(220.0, 220.0, 75.0), 0.7), + let objects: std::vec::Vec> = vec![ + Box::new(Sphere::new(Vec::new(-1.0, 1.0, 5.0), 0.8, Color::new(255.0, 50.0, 50.0), 0.2)), + Box::new(Sphere::new(Vec::new(1.0, 1.0, 5.0), 0.8, Color::new(50.0, 255.0, 100.0), 0.8)), + Box::new(Sphere::new(Vec::new(2.5, 1.0, 5.0), 0.8, Color::new(50.0, 100.0, 255.0), 0.0)), + Box::new(Sphere::new(Vec::new(-1.0, 2.0, 4.0), 0.2, Color::new(220.0, 220.0, 75.0), 0.7)), + + Box::new(Plane::new(Vec::new(0.0, -1.0, 0.0), Vec::new(0.0, 1.0, 0.0), Color::new(100.0, 100.0, 100.0), 0.0)), ]; let lights = vec![ diff --git a/src/object/mod.rs b/src/object/mod.rs index 43c3b7c..2a40de2 100644 --- a/src/object/mod.rs +++ b/src/object/mod.rs @@ -1 +1,13 @@ pub mod sphere; +pub mod plane; + +use crate::vector::Vec; +use crate::ray::Ray; +use crate::color::Color; + +pub trait Object { + fn intersect(&self, ray: Ray) -> Option; + fn surface_normal(&self, point: Vec) -> Vec; + fn color_at(&self, point: Vec) -> Color; + fn reflectance(&self) -> f64; +} diff --git a/src/object/plane/mod.rs b/src/object/plane/mod.rs new file mode 100644 index 0000000..5a8e2ef --- /dev/null +++ b/src/object/plane/mod.rs @@ -0,0 +1,47 @@ +use crate::object::Object; +use crate::vector::Vec; +use crate::ray::Ray; +use crate::color::Color; + +#[derive(Debug)] +pub struct Plane { + point: Vec, + normal: Vec, + pub color: Color, + pub reflectance: f64, +} + +impl Plane { + pub fn new(point: Vec, normal: Vec, color: Color, reflectance: f64) -> Self { + Self { point, normal, color, reflectance } + } +} + +impl Object for Plane { + fn intersect(&self, ray: Ray) -> Option { + let ndotl = self.normal.dot(ray.direction); + + if ndotl.abs() < 1e-10 { + None + } else { + let t = self.normal.dot(self.point.subtract(ray.origin)) / ndotl; + if t < 0.0 { None } else { Some(t) } + } + } + + fn surface_normal(&self, _point: Vec) -> Vec { + self.normal + } + + fn color_at(&self, point: Vec) -> Color { + if (point.x.round() + point.z.round()).abs() % 2.0 < 1e-10 { + Color::new(10.0, 10.0, 10.0) + } else { + self.color + } + } + + fn reflectance(&self) -> f64 { + self.reflectance + } +} diff --git a/src/object/sphere/mod.rs b/src/object/sphere/mod.rs index 384e4cc..abd7378 100644 --- a/src/object/sphere/mod.rs +++ b/src/object/sphere/mod.rs @@ -1,8 +1,9 @@ +use crate::object::Object; use crate::vector::Vec; use crate::ray::Ray; use crate::color::Color; -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, PartialEq)] pub struct Sphere { center: Vec, radius: f64, @@ -14,8 +15,10 @@ impl Sphere { pub fn new(center: Vec, radius: f64, color: Color, reflectance: f64) -> Self { Self { center, radius, color, reflectance } } +} - pub fn intersect(&self, ray: Ray) -> Option { +impl Object for Sphere { + fn intersect(&self, ray: Ray) -> Option { let oc = ray.origin.subtract(self.center); let dot = ray.direction.normalize().dot(oc); @@ -39,9 +42,17 @@ impl Sphere { } } - pub fn surface_normal(&self, point: Vec) -> Vec { + fn surface_normal(&self, point: Vec) -> Vec { point.subtract(self.center).normalize() } + + fn color_at(&self, _point: Vec) -> Color { + self.color + } + + fn reflectance(&self) -> f64 { + self.reflectance + } } #[cfg(test)]