147 lines
5.0 KiB
Rust
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
|
|
}
|
|
}
|