237 lines
8.1 KiB
Rust
237 lines
8.1 KiB
Rust
use derive_builder::Builder;
|
|
|
|
use crate::{
|
|
lights::PointLight,
|
|
patterns::Pattern,
|
|
shapes::Shape,
|
|
tuples::{dot, reflect, Color, Tuple},
|
|
Float, BLACK, WHITE,
|
|
};
|
|
|
|
#[derive(Builder, Debug, PartialEq, Clone)]
|
|
#[builder(default)]
|
|
pub struct Material {
|
|
#[builder(setter(into))]
|
|
pub color: Pattern,
|
|
pub ambient: Float,
|
|
pub diffuse: Float,
|
|
pub specular: Float,
|
|
pub shininess: Float,
|
|
pub reflective: Float,
|
|
pub transparency: Float,
|
|
pub refractive_index: Float,
|
|
}
|
|
|
|
impl Default for Material {
|
|
/// Creates the default material.
|
|
fn default() -> Material {
|
|
Material {
|
|
color: WHITE.into(),
|
|
ambient: 0.1,
|
|
diffuse: 0.9,
|
|
specular: 0.9,
|
|
shininess: 200.,
|
|
reflective: 0.0,
|
|
transparency: 0.0,
|
|
refractive_index: 1.0,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Compute lighting contributions using the Phong reflection model.
|
|
pub fn lighting(
|
|
material: &Material,
|
|
object: &Shape,
|
|
light: &PointLight,
|
|
point: Tuple,
|
|
eyev: Tuple,
|
|
normalv: Tuple,
|
|
in_shadow: bool,
|
|
) -> Color {
|
|
// Combine the surface color with the light's color.
|
|
let color = material.color.pattern_at_object(object, point);
|
|
let effective_color = 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
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use crate::{materials::Material, WHITE};
|
|
|
|
#[test]
|
|
fn default() {
|
|
let m = Material::default();
|
|
assert_eq!(
|
|
m,
|
|
Material {
|
|
color: WHITE.into(),
|
|
ambient: 0.1,
|
|
diffuse: 0.9,
|
|
specular: 0.9,
|
|
shininess: 200.,
|
|
reflective: 0.0,
|
|
transparency: 0.0,
|
|
refractive_index: 1.0,
|
|
}
|
|
);
|
|
}
|
|
|
|
mod lighting {
|
|
use crate::{
|
|
lights::PointLight,
|
|
materials::{lighting, Material},
|
|
patterns::{Pattern, BLACK_PAT, WHITE_PAT},
|
|
shapes::Shape,
|
|
tuples::{point, vector, Color},
|
|
Float, BLACK, WHITE,
|
|
};
|
|
|
|
#[test]
|
|
fn eye_between_light_and_surface() {
|
|
let in_shadow = false;
|
|
let m = Material::default();
|
|
let position = point(0., 0., 0.);
|
|
let object = Shape::sphere();
|
|
|
|
// Lighting with the eye between the light and the surface.
|
|
let eyev = vector(0., 0., -1.);
|
|
let normalv = vector(0., 0., -1.);
|
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
|
assert_eq!(result, Color::new(1.9, 1.9, 1.9));
|
|
}
|
|
|
|
#[test]
|
|
fn eye_between_light_and_surface_offset_45() {
|
|
// Lighting with the eye between the light and the surface, eye offset 45°.
|
|
let in_shadow = false;
|
|
let m = Material::default();
|
|
let position = point(0., 0., 0.);
|
|
let object = Shape::sphere();
|
|
let eyev = vector(0., (2. as Float).sqrt() / 2., -(2. as Float).sqrt() / 2.);
|
|
let normalv = vector(0., 0., -1.);
|
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
|
assert_eq!(result, WHITE);
|
|
}
|
|
#[test]
|
|
fn eye_opposite_surface_light_offset_45() {
|
|
// Lighting with the eye opposite surface, light offset 45°.
|
|
let in_shadow = false;
|
|
let m = Material::default();
|
|
let position = point(0., 0., 0.);
|
|
let object = Shape::sphere();
|
|
let eyev = vector(0., 0., -1.);
|
|
let normalv = vector(0., 0., -1.);
|
|
let light = PointLight::new(point(0., 10., -10.), WHITE);
|
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
|
assert_eq!(result, Color::new(0.7364, 0.7364, 0.7364));
|
|
}
|
|
#[test]
|
|
fn eye_in_path_of_reflection_vector() {
|
|
// Lighting with the eye in the path of the reflection vector.
|
|
let in_shadow = false;
|
|
let m = Material::default();
|
|
let position = point(0., 0., 0.);
|
|
let object = Shape::sphere();
|
|
let eyev = vector(0., -(2.0 as Float).sqrt() / 2., -(2.0 as Float).sqrt() / 2.);
|
|
let normalv = vector(0., 0., -1.);
|
|
let light = PointLight::new(point(0., 10., -10.), WHITE);
|
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
|
assert_eq!(result, Color::new(1.63639, 1.63639, 1.63639));
|
|
}
|
|
#[test]
|
|
fn light_behind_surface() {
|
|
// Lighting with the light behind the surface.
|
|
let in_shadow = false;
|
|
let m = Material::default();
|
|
let position = point(0., 0., 0.);
|
|
let object = Shape::sphere();
|
|
let eyev = vector(0., 0., -1.);
|
|
let normalv = vector(0., 0., -1.);
|
|
let light = PointLight::new(point(0., 0., 10.), WHITE);
|
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
|
assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
|
}
|
|
#[test]
|
|
fn surface_in_shadow() {
|
|
// Lighting with the surface in shadow.
|
|
let m = Material::default();
|
|
let position = point(0., 0., 0.);
|
|
let object = Shape::sphere();
|
|
let in_shadow = true;
|
|
let eyev = vector(0., 0., -1.);
|
|
let normalv = vector(0., 0., -1.);
|
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
|
assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
|
}
|
|
#[test]
|
|
fn pattern_applied() {
|
|
// Lighting with a pattern applied.
|
|
let object = Shape::sphere();
|
|
let m = Material {
|
|
color: Pattern::stripe(WHITE_PAT, BLACK_PAT),
|
|
ambient: 1.,
|
|
diffuse: 0.,
|
|
specular: 0.,
|
|
..Material::default()
|
|
};
|
|
let eyev = vector(0., 0., -1.);
|
|
let normalv = vector(0., 0., -1.);
|
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
|
let c1 = lighting(
|
|
&m,
|
|
&object,
|
|
&light,
|
|
point(0.9, 0., 0.),
|
|
eyev,
|
|
normalv,
|
|
false,
|
|
);
|
|
let c2 = lighting(
|
|
&m,
|
|
&object,
|
|
&light,
|
|
point(1.1, 0., 0.),
|
|
eyev,
|
|
normalv,
|
|
false,
|
|
);
|
|
assert_eq!(c1, WHITE);
|
|
assert_eq!(c2, BLACK);
|
|
}
|
|
}
|
|
}
|