use std::rc::Rc;
use crate::color::Color;
use crate::light::PointLight;
use crate::shape::Shape;
use crate::texture::{solid_color::SolidColor, Texture};
use crate::tuple::Tuple;
#[derive(Clone)]
pub struct Material {
pub texture: Rc<dyn Texture>,
pub ambient: f64,
pub diffuse: f64,
pub specular: f64,
pub shininess: f64,
}
impl Material {
pub fn lighting(
&self,
shape: Rc<dyn Shape>,
light: &PointLight,
position: Tuple,
eye: Tuple,
normal: Tuple,
in_shadow: bool,
) -> Color {
let color = self.texture.color_at_shape(position, Rc::clone(&shape)) * light.color();
let lightv = (light.position() - position).normalized();
let ambient = color * self.ambient;
let light_dot_normal = lightv.dot(normal);
if in_shadow || light_dot_normal < 0.0 {
return ambient;
}
let diffuse = color * self.diffuse * light_dot_normal;
let reflectv = (-lightv).reflect(normal);
let reflect_dot_eye = reflectv.dot(eye);
if reflect_dot_eye <= 0.0 {
return ambient + diffuse;
}
let factor = reflect_dot_eye.powf(self.shininess);
let specular = light.color() * self.specular * factor;
return ambient + diffuse + specular;
}
}
impl Default for Material {
fn default() -> Self {
Self {
texture: Rc::new(SolidColor::new(Color::new(1.0, 1.0, 1.0))),
ambient: 0.1,
diffuse: 0.9,
specular: 0.9,
shininess: 200.0,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::shape::sphere::Sphere;
#[test]
fn lighting_eye_between_light_and_surface() {
let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
let material = Material::default();
let position = Tuple::point(0.0, 0.0, 0.0);
let eye = Tuple::vector(0.0, 0.0, -1.0);
let normal = Tuple::vector(0.0, 0.0, -1.0);
let light = PointLight::new(Tuple::point(0.0, 0.0, -10.0), Color::new(1.0, 1.0, 1.0));
let result = material.lighting(shape, &light, position, eye, normal, false);
assert_eq!(result, Color::new(1.9, 1.9, 1.9));
}
#[test]
fn lighting_eye_between_light_and_surface_light_offset_45deg() {
let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
let material = Material::default();
let position = Tuple::point(0.0, 0.0, 0.0);
let eye = Tuple::vector(0.0, (2.0 as f64).sqrt() / 2.0, -(2.0 as f64).sqrt() / 2.0);
let normal = Tuple::vector(0.0, 0.0, -1.0);
let light = PointLight::new(Tuple::point(0.0, 0.0, -10.0), Color::new(1.0, 1.0, 1.0));
let result = material.lighting(shape, &light, position, eye, normal, false);
assert_eq!(result, Color::new(1.0, 1.0, 1.0));
}
#[test]
fn lighting_eye_opposite_surface_light_offset_45deg() {
let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
let material = Material::default();
let position = Tuple::point(0.0, 0.0, 0.0);
let eye = Tuple::vector(0.0, 0.0, -1.0);
let normal = Tuple::vector(0.0, 0.0, -1.0);
let light = PointLight::new(Tuple::point(0.0, 10.0, -10.0), Color::new(1.0, 1.0, 1.0));
let result = material.lighting(shape, &light, position, eye, normal, false);
assert_eq!(
result,
Color::new(0.7363961030678927, 0.7363961030678927, 0.7363961030678927)
);
}
#[test]
fn lighting_eye_in_path_reflector() {
let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
let material = Material::default();
let position = Tuple::point(0.0, 0.0, 0.0);
let eye = Tuple::vector(0.0, -(2.0 as f64).sqrt() / 2.0, -(2.0 as f64).sqrt() / 2.0);
let normal = Tuple::vector(0.0, 0.0, -1.0);
let light = PointLight::new(Tuple::point(0.0, 10.0, -10.0), Color::new(1.0, 1.0, 1.0));
let result = material.lighting(shape, &light, position, eye, normal, false);
assert_eq!(
result,
Color::new(1.6363961030678928, 1.6363961030678928, 1.6363961030678928)
);
}
#[test]
fn lighting_light_behind_surface() {
let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
let material = Material::default();
let position = Tuple::point(0.0, 0.0, 0.0);
let eye = Tuple::vector(0.0, 0.0, -1.0);
let normal = Tuple::vector(0.0, 0.0, -1.0);
let light = PointLight::new(Tuple::point(0.0, 0.0, 10.0), Color::new(1.0, 1.0, 1.0));
let result = material.lighting(shape, &light, position, eye, normal, false);
assert_eq!(result, Color::new(0.1, 0.1, 0.1));
}
#[test]
fn lighting_surface_in_shadow() {
let shape: Rc<dyn Shape> = Rc::new(Sphere::new());
let material = Material::default();
let position = Tuple::point(0.0, 0.0, 0.0);
let eye = Tuple::vector(0.0, 0.0, -1.0);
let normal = Tuple::vector(0.0, 0.0, -1.0);
let light = PointLight::new(Tuple::point(0.0, 0.0, -10.0), Color::new(1.0, 1.0, 1.0));
let result = material.lighting(shape, &light, position, eye, normal, true);
assert_eq!(result, Color::new(0.1, 0.1, 0.1));
}
}