patterns: implement object and pattern transformation awareness.

This commit is contained in:
Bill Thiede 2021-07-25 11:22:36 -07:00
parent bfa3282a37
commit 8b79876aee
5 changed files with 106 additions and 12 deletions

View File

@ -44,7 +44,15 @@ fn main() -> Result<()> {
let point = r.position(hit.t);
let normal = hit.object.normal_at(point);
let eye = -r.direction;
let color = lighting(&hit.object.material, &light, point, eye, normal, in_shadow);
let color = lighting(
&hit.object.material,
&hit.object,
&light,
point,
eye,
normal,
in_shadow,
);
c.set(x, y, color);
}
}

View File

@ -3,6 +3,7 @@ use derive_builder::Builder;
use crate::{
lights::PointLight,
patterns::StripePattern,
shapes::Shape,
tuples::{dot, reflect, Color, Tuple},
Float, BLACK, WHITE,
};
@ -83,6 +84,7 @@ impl Default for Material {
/// lights::PointLight,
/// materials::{lighting, Material},
/// patterns::stripe_pattern,
/// shapes::Shape,
/// tuples::{point, vector, Color},
/// Float, BLACK, WHITE,
/// };
@ -90,40 +92,41 @@ impl Default for Material {
/// 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, &light, position, eyev, normalv, in_shadow);
/// let result = lighting(&m, &object, &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 = 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, &light, position, eyev, normalv, in_shadow);
/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
/// assert_eq!(result, WHITE);
///
/// // Lighting with the eye opposite surface, light offset 45°.
/// 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, &light, position, eyev, normalv, in_shadow);
/// let result = lighting(&m, &object, &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 = 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, &light, position, eyev, normalv, in_shadow);
/// let result = lighting(&m, &object, &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 = vector(0., 0., -1.);
/// let normalv = vector(0., 0., -1.);
/// let light = PointLight::new(point(0., 0., 10.), WHITE);
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
/// assert_eq!(result, Color::new(0.1, 0.1, 0.1));
///
/// // Lighting with the surface in shadow.
@ -131,7 +134,7 @@ impl Default for Material {
/// 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, &light, position, eyev, normalv, in_shadow);
/// let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
/// assert_eq!(result, Color::new(0.1, 0.1, 0.1));
///
/// // Lighting with a pattern applied.
@ -146,13 +149,30 @@ impl Default for Material {
/// 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, &light, point(0.9, 0., 0.), eyev, normalv, false);
/// let c2 = lighting(&m, &light, point(1.1, 0., 0.), eyev, normalv, false);
/// 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);
/// ```
pub fn lighting(
material: &Material,
object: &Shape,
light: &PointLight,
point: Tuple,
eyev: Tuple,
@ -162,7 +182,7 @@ pub fn lighting(
// Combine the surface color with the light's color.
let color = match &material.color {
ColorMapper::Constant(color) => *color,
ColorMapper::StripePattern(pat) => pat.stripe_at(point),
ColorMapper::StripePattern(pat) => pat.stripe_at_object(object, point),
};
let effective_color = color * light.intensity;
// Find the direciton of the light source.

View File

@ -1,9 +1,15 @@
use crate::tuples::{Color, Tuple};
use crate::{
matrices::Matrix4x4,
shapes::Shape,
tuples::{Color, Tuple},
};
#[derive(Debug, PartialEq, Clone)]
pub struct StripePattern {
pub a: Color,
pub b: Color,
transform: Matrix4x4,
inverse_transform: Matrix4x4,
}
/// Create a material pattern that alternates between the given colors along each unit of the
@ -18,7 +24,12 @@ pub struct StripePattern {
/// assert_eq!(pattern.b, WHITE);
/// ```
pub fn stripe_pattern(a: Color, b: Color) -> StripePattern {
StripePattern { a, b }
StripePattern {
a,
b,
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
}
}
impl StripePattern {
@ -64,4 +75,54 @@ impl StripePattern {
self.b
}
}
/// Sample the color at the given world point on the given object.
/// This function respects the object and the pattern's transform matrix.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// matrices::scaling,
/// patterns::stripe_pattern,
/// shapes::{Shape, ShapeBuilder},
/// tuples::point,
/// BLACK, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
///
/// // Stripes with an object transformation.
/// let object = ShapeBuilder::sphere()
/// .transform(scaling(2., 2., 2.))
/// .build()?;
/// let pattern = stripe_pattern(WHITE, BLACK);
/// let c = pattern.stripe_at_object(&object, point(1.5, 0., 0.));
/// assert_eq!(c, WHITE);
///
/// // Stripes with an pattern transformation.
/// let object = Shape::sphere();
/// let mut pattern = stripe_pattern(WHITE, BLACK);
/// pattern.set_transform(scaling(2., 2., 2.));
/// let c = pattern.stripe_at_object(&object, point(1.5, 0., 0.));
/// assert_eq!(c, WHITE);
///
/// # Ok(())
/// # }
/// ```
pub fn stripe_at_object(&self, object: &Shape, world_point: Tuple) -> Color {
let object_point = object.inverse_transform() * world_point;
let pattern_point = self.inverse_transform * object_point;
self.stripe_at(pattern_point)
}
pub fn transform(&self) -> Matrix4x4 {
self.transform
}
pub fn inverse_transform(&self) -> Matrix4x4 {
self.inverse_transform
}
pub fn set_transform(&mut self, t: Matrix4x4) {
self.transform = t;
self.inverse_transform = t.inverse();
}
}

View File

@ -347,6 +347,10 @@ impl Shape {
self.transform
}
pub fn inverse_transform(&self) -> Matrix4x4 {
self.inverse_transform
}
pub fn set_transform(&mut self, t: Matrix4x4) {
self.transform = t;
self.inverse_transform = t.inverse();

View File

@ -141,6 +141,7 @@ impl World {
let shadowed = self.is_shadowed(comps.over_point, light);
acc + lighting(
&comps.object.material,
&comps.object,
light,
comps.over_point,
comps.eyev,