raytracers/rtchallenge/src/materials.rs

147 lines
5.0 KiB
Rust

use crate::{
lights::PointLight,
tuples::Color,
tuples::{dot, reflect, Tuple},
BLACK, WHITE,
};
#[derive(Debug, PartialEq, Clone)]
pub struct Material {
pub color: Color,
pub ambient: f32,
pub diffuse: f32,
pub specular: f32,
pub shininess: f32,
}
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},
/// 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_f32.sqrt() / 2., -2_f32.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_f32.sqrt() / 2., -2_f32.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.6363853, 1.6363853, 1.6363853));
///
/// // 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
}
}