diff --git a/assignment-1b/src/main.rs b/assignment-1b/src/main.rs index ba32daa..fff520a 100644 --- a/assignment-1b/src/main.rs +++ b/assignment-1b/src/main.rs @@ -13,8 +13,6 @@ use std::path::PathBuf; use anyhow::Result; use clap::Parser; -use rayon::prelude::{IntoParallelIterator, ParallelIterator}; -use scene::data::ObjectKind; use scene::Scene; use crate::image::Image; diff --git a/assignment-1b/src/scene/cylinder.rs b/assignment-1b/src/scene/cylinder.rs index 9a6ad16..632fc30 100644 --- a/assignment-1b/src/scene/cylinder.rs +++ b/assignment-1b/src/scene/cylinder.rs @@ -5,7 +5,7 @@ use ordered_float::NotNan; use crate::ray::Ray; use crate::utils::compute_rotation_matrix; -use super::data::{IntersectionContext, ObjectKind}; +use super::{data::ObjectKind, illumination::IntersectionContext}; #[derive(Debug)] pub struct Cylinder { diff --git a/assignment-1b/src/scene/data.rs b/assignment-1b/src/scene/data.rs index 154a1f2..d375357 100644 --- a/assignment-1b/src/scene/data.rs +++ b/assignment-1b/src/scene/data.rs @@ -2,12 +2,12 @@ use std::fmt::Debug; use anyhow::Result; use nalgebra::Vector3; -use ordered_float::NotNan; use crate::image::Color; use crate::ray::Ray; use super::Scene; +use super::illumination::IntersectionContext; pub trait ObjectKind: Debug + Send + Sync { /// Determine where the ray intersects this object, returning the earliest @@ -74,89 +74,7 @@ pub struct DepthCueing { dist_min: f64, } -/// Information about an intersection -#[derive(Derivative)] -#[derivative(Debug, PartialEq, PartialOrd, Ord)] -pub struct IntersectionContext { - /// The time of the intersection in the parametric ray - /// - /// Unfortunately, IEEE floats in Rust don't have total ordering, because - /// NaNs violate ordering properties. The way to remedy this is to ensure we - /// don't have NaNs by wrapping it into this type, which then implements - /// total ordering. - pub time: NotNan, - - /// The intersection point. - #[derivative(PartialEq = "ignore", Ord = "ignore")] - pub point: Vector3, - - /// The normal vector protruding from the surface of the object at the - /// intersection point - #[derivative(PartialEq = "ignore", Ord = "ignore")] - pub normal: Vector3, -} - -impl Eq for IntersectionContext {} - impl Scene { - /// Determine the color that should be used to fill this pixel. - /// - /// - material_idx is the index into the materials list. - /// - intersection_context contains information on vectors where the - /// intersection occurred - /// - /// Also known as Shade_Ray in the slides. - pub fn compute_pixel_color( - &self, - material_idx: usize, - intersection_context: IntersectionContext, - ) -> Color { - // TODO: Does it make sense to make this function fallible from an API - // design standpoint? - let material = match self.materials.get(material_idx) { - Some(v) => v, - None => return self.bkg_color, - }; - - let ambient_component = material.k_a * material.diffuse_color; - - // Diffuse and specular lighting for each separate light - let diffuse_and_specular: Vector3 = self - .lights - .iter() - .map(|light| { - // The vector pointing in the direction of the light - let light_direction = match light.kind { - LightKind::Point { location } => { - location - intersection_context.point - } - LightKind::Directional { direction } => direction, - } - .normalize(); - - let normal = intersection_context.normal.normalize(); - let viewer_direction = self.eye_pos - intersection_context.point; - let halfway_direction = - ((light_direction + viewer_direction) / 2.0).normalize(); - - let diffuse_component = material.k_d - * material.diffuse_color - * normal.dot(&light_direction).max(0.0); - - let specular_component = material.k_s - * material.specular_color - * normal - .dot(&halfway_direction) - .max(0.0) - .powf(material.exponent); - - diffuse_component + specular_component - }) - .sum(); - - ambient_component + diffuse_and_specular - } - /// Determine the boundaries of the viewing window in world coordinates pub fn compute_viewing_window(&self, distance: f64) -> Rect { // Compute viewing directions diff --git a/assignment-1b/src/scene/illumination.rs b/assignment-1b/src/scene/illumination.rs new file mode 100644 index 000000000..5223839 --- /dev/null +++ b/assignment-1b/src/scene/illumination.rs @@ -0,0 +1,90 @@ +use nalgebra::Vector3; +use ordered_float::NotNan; + +use crate::image::Color; + +use super::{Scene, data::LightKind}; + +/// Information about an intersection +#[derive(Derivative)] +#[derivative(Debug, PartialEq, PartialOrd, Ord)] +pub struct IntersectionContext { + /// The time of the intersection in the parametric ray + /// + /// Unfortunately, IEEE floats in Rust don't have total ordering, because + /// NaNs violate ordering properties. The way to remedy this is to ensure we + /// don't have NaNs by wrapping it into this type, which then implements + /// total ordering. + pub time: NotNan, + + /// The intersection point. + #[derivative(PartialEq = "ignore", Ord = "ignore")] + pub point: Vector3, + + /// The normal vector protruding from the surface of the object at the + /// intersection point + #[derivative(PartialEq = "ignore", Ord = "ignore")] + pub normal: Vector3, +} + +impl Eq for IntersectionContext {} + +impl Scene { + /// Determine the color that should be used to fill this pixel. + /// + /// - material_idx is the index into the materials list. + /// - intersection_context contains information on vectors where the + /// intersection occurred + /// + /// Also known as Shade_Ray in the slides. + pub fn compute_pixel_color( + &self, + material_idx: usize, + intersection_context: IntersectionContext, + ) -> Color { + // TODO: Does it make sense to make this function fallible from an API + // design standpoint? + let material = match self.materials.get(material_idx) { + Some(v) => v, + None => return self.bkg_color, + }; + + let ambient_component = material.k_a * material.diffuse_color; + + // Diffuse and specular lighting for each separate light + let diffuse_and_specular: Vector3 = self + .lights + .iter() + .map(|light| { + // The vector pointing in the direction of the light + let light_direction = match light.kind { + LightKind::Point { location } => { + location - intersection_context.point + } + LightKind::Directional { direction } => direction, + } + .normalize(); + + let normal = intersection_context.normal.normalize(); + let viewer_direction = self.eye_pos - intersection_context.point; + let halfway_direction = + ((light_direction + viewer_direction) / 2.0).normalize(); + + let diffuse_component = material.k_d + * material.diffuse_color + * normal.dot(&light_direction).max(0.0); + + let specular_component = material.k_s + * material.specular_color + * normal + .dot(&halfway_direction) + .max(0.0) + .powf(material.exponent); + + diffuse_component + specular_component + }) + .sum(); + + ambient_component + diffuse_and_specular + } +} diff --git a/assignment-1b/src/scene/mod.rs b/assignment-1b/src/scene/mod.rs index 3405b15..16456ee 100644 --- a/assignment-1b/src/scene/mod.rs +++ b/assignment-1b/src/scene/mod.rs @@ -2,6 +2,7 @@ pub mod cylinder; pub mod data; pub mod input_file; pub mod sphere; +pub mod illumination; use nalgebra::Vector3; diff --git a/assignment-1b/src/scene/sphere.rs b/assignment-1b/src/scene/sphere.rs index 3571322..1633ade 100644 --- a/assignment-1b/src/scene/sphere.rs +++ b/assignment-1b/src/scene/sphere.rs @@ -4,7 +4,7 @@ use ordered_float::NotNan; use crate::{ray::Ray, utils::min_f64}; -use super::data::{IntersectionContext, ObjectKind}; +use super::{data::ObjectKind, illumination::IntersectionContext}; #[derive(Debug)] pub struct Sphere {