use crate::{ lights::PointLight, tuples::Color, tuples::{dot, reflect, Tuple}, Float, BLACK, WHITE, }; #[derive(Debug, PartialEq, Clone)] pub struct Material { pub color: Color, pub ambient: Float, pub diffuse: Float, pub specular: Float, pub shininess: Float, } impl Default for Material { /// Creates the default material. /// /// # Examples /// ``` /// use rtchallenge::{materials::Material, tuples::Color, WHITE}; /// /// let m = Material::default(); /// assert_eq!( /// m, /// Material { /// color: WHITE, /// ambient: 0.1, /// diffuse: 0.9, /// specular: 0.9, /// shininess: 200., /// } /// ); /// ``` fn default() -> Material { Material { color: WHITE, ambient: 0.1, diffuse: 0.9, specular: 0.9, shininess: 200., } } } /// Compute lighting contributions using the Phong reflection model. /// /// # Examples /// ``` /// use rtchallenge::{ /// lights::PointLight, /// materials::{lighting, Material}, /// tuples::{Color, Tuple}, /// Float, WHITE, /// }; /// /// let in_shadow = false; /// let m = Material::default(); /// let position = Tuple::point(0., 0., 0.); /// /// // Lighting with the eye between the light and the surface. /// let eyev = Tuple::vector(0., 0., -1.); /// let normalv = Tuple::vector(0., 0., -1.); /// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE); /// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(1.9, 1.9, 1.9)); /// /// // Lighting with the eye between the light and the surface, eye offset 45°. /// let eyev = Tuple::vector(0., (2. as Float).sqrt() / 2., -(2. as Float).sqrt() / 2.); /// let normalv = Tuple::vector(0., 0., -1.); /// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE); /// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, WHITE); /// /// // Lighting with the eye opposite surface, light offset 45°. /// let eyev = Tuple::vector(0., 0., -1.); /// let normalv = Tuple::vector(0., 0., -1.); /// let light = PointLight::new(Tuple::point(0., 10., -10.), WHITE); /// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(0.7364, 0.7364, 0.7364)); /// /// // Lighting with the eye in the path of the reflection vector. /// let eyev = Tuple::vector(0., -(2.0 as Float).sqrt() / 2., -(2.0 as Float).sqrt() / 2.); /// let normalv = Tuple::vector(0., 0., -1.); /// let light = PointLight::new(Tuple::point(0., 10., -10.), WHITE); /// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(1.63639, 1.63639, 1.63639)); /// /// // Lighting with the light behind the surface. /// let eyev = Tuple::vector(0., 0., -1.); /// let normalv = Tuple::vector(0., 0., -1.); /// let light = PointLight::new(Tuple::point(0., 0., 10.), WHITE); /// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(0.1, 0.1, 0.1)); /// /// // Lighting with the surface in shadow. /// let in_shadow = true; /// let eyev = Tuple::vector(0., 0., -1.); /// let normalv = Tuple::vector(0., 0., -1.); /// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE); /// let result = lighting(&m, &light, position, eyev, normalv, in_shadow); /// assert_eq!(result, Color::new(0.1, 0.1, 0.1)); /// ``` pub fn lighting( material: &Material, light: &PointLight, point: Tuple, eyev: Tuple, normalv: Tuple, in_shadow: bool, ) -> Color { // Combine the surface color with the light's color. let effective_color = material.color * light.intensity; // Find the direciton of the light source. let lightv = (light.position - point).normalize(); // Compute the ambient distribution. let ambient = effective_color * material.ambient; // This is the cosine of the angle between the light vector an the normal // vector. A negative number means the light is on the other side of the // surface. let light_dot_normal = dot(lightv, normalv); let (diffuse, specular) = if light_dot_normal < 0. { (BLACK, BLACK) } else { // Compute the diffuse contribution. let diffuse = effective_color * material.diffuse * light_dot_normal; // This represents the cosine of the angle between the relfection vector // and the eye vector. A negative number means the light reflects away // from the eye. let reflectv = reflect(-lightv, normalv); let reflect_dot_eye = dot(reflectv, eyev); let specular = if reflect_dot_eye <= 0. { BLACK } else { // Compute the specular contribution. let factor = reflect_dot_eye.powf(material.shininess); light.intensity * material.specular * factor }; (diffuse, specular) }; if in_shadow { ambient } else { ambient + diffuse + specular } }