use nalgebra::{clamp, convert, Vector3}; use super::colour::{ColourRgbF, NamedColour}; use crate::Real; use std::fmt::Debug; type Bsdf = Box, Vector3, ColourRgbF) -> ColourRgbF>; pub trait Material: Debug + Sync + Send { fn bsdf(&self) -> Bsdf; fn sample(&self, _w_o: &Vector3) -> Vec> { vec![] } } #[derive(Debug)] pub struct LambertianMaterial { pub colour: ColourRgbF, pub diffuse_strength: T, } impl LambertianMaterial { pub fn new_dummy() -> LambertianMaterial { LambertianMaterial { colour: ColourRgbF::new(T::one(), T::one(), T::one()), diffuse_strength: T::one(), } } } impl Material for LambertianMaterial { fn bsdf(&self) -> Bsdf { let colour = self.colour * self.diffuse_strength; Box::new( move |_w_o: Vector3, _w_i: Vector3, colour_in: ColourRgbF| colour * colour_in, ) } } #[derive(Debug)] pub struct PhongMaterial { pub colour: ColourRgbF, pub diffuse_strength: T, pub specular_strength: T, pub smoothness: T, } impl Material for PhongMaterial { fn bsdf(&self) -> Bsdf { let smoothness = self.smoothness; let specular_strength = self.specular_strength; let colour = self.colour * self.diffuse_strength; Box::new( move |w_o: Vector3, w_i: Vector3, colour_in: ColourRgbF| { if w_i.z < T::zero() || w_o.z < T::zero() { ColourRgbF::from_vector3(&Vector3::zeros()) } else { let reflection_vector = Vector3::new(-w_i.x, -w_i.y, w_i.z); colour * colour_in + ColourRgbF::from_named(NamedColour::White) * w_o.dot(&reflection_vector).abs().powf(smoothness) * (specular_strength / w_i.dot(&Vector3::z_axis())) } }, ) } } #[derive(Debug)] pub struct ReflectiveMaterial { pub colour: ColourRgbF, pub diffuse_strength: T, pub reflection_strength: T, } impl Material for ReflectiveMaterial { fn bsdf(&self) -> Bsdf { let diffuse_colour_factor = self.colour * self.diffuse_strength; let reflection_strength = self.reflection_strength; Box::new( move |w_o: Vector3, w_i: Vector3, colour_in: ColourRgbF| { if w_i.z < T::zero() || w_o.z < T::zero() { ColourRgbF::new(T::zero(), T::one(), T::one()) } else { let reflection_vector = Vector3::new(-w_o.x, -w_o.y, w_o.z); let reflection_colour = colour_in * reflection_strength; let diffuse_colour = diffuse_colour_factor * colour_in; let sigma: T = convert(0.05); let two: T = convert(2.0); // These are normalized vectors, but sometimes rounding errors cause the // dot product to be slightly above 1 or below 0. The call to clamp // ensures the values stay within the domain of acos, let theta = clamp(w_i.dot(&reflection_vector), T::zero(), T::one()) .abs() .acos(); let reflection_factor = (-(theta.powf(two)) / (two * sigma * sigma)).exp(); reflection_colour * reflection_factor + diffuse_colour * (T::one() - reflection_factor) } }, ) } fn sample(&self, w_o: &Vector3) -> Vec> { vec![Vector3::new(-w_o.x, -w_o.y, w_o.z)] } }