patterns: add ability to nest patterns

This commit is contained in:
Bill Thiede 2021-08-01 19:08:36 -07:00
parent 9befbd9ad2
commit 926fffa29f
4 changed files with 151 additions and 65 deletions

View File

@ -6,7 +6,10 @@ use structopt::StructOpt;
use rtchallenge::prelude::*; use rtchallenge::prelude::*;
use rtchallenge::{ use rtchallenge::{
camera::RenderStrategy, float::consts::PI, patterns::test_pattern, BLACK, WHITE, camera::RenderStrategy,
float::consts::PI,
patterns::{test_pattern, BLACK_PAT, WHITE_PAT},
WHITE,
}; };
/// End of chapter 10 challenge. /// End of chapter 10 challenge.
@ -61,7 +64,7 @@ fn main() -> Result<()> {
.material( .material(
MaterialBuilder::default() MaterialBuilder::default()
.color( .color(
checkers_pattern(WHITE, BLACK) checkers_pattern(WHITE_PAT, BLACK_PAT)
.transform(translation(1., 0., 0.) * scaling(2., 2., 2.)) .transform(translation(1., 0., 0.) * scaling(2., 2., 2.))
.build()?, .build()?,
) )
@ -90,7 +93,7 @@ fn main() -> Result<()> {
.transform(translation(2., 1., 0.) * sphere_size) .transform(translation(2., 1., 0.) * sphere_size)
.material( .material(
MaterialBuilder::default() MaterialBuilder::default()
.color(stripe_pattern(WHITE, BLACK).build()?) .color(stripe_pattern(WHITE_PAT, BLACK_PAT).build()?)
.diffuse(0.7) .diffuse(0.7)
.specular(0.3) .specular(0.3)
.build()?, .build()?,
@ -102,7 +105,7 @@ fn main() -> Result<()> {
.material( .material(
MaterialBuilder::default() MaterialBuilder::default()
.color( .color(
stripe_pattern(WHITE, BLACK) stripe_pattern(WHITE_PAT, BLACK_PAT)
.transform(scaling(0.2, 1., 1.)) .transform(scaling(0.2, 1., 1.))
.build()?, .build()?,
) )
@ -128,7 +131,7 @@ fn main() -> Result<()> {
.material( .material(
MaterialBuilder::default() MaterialBuilder::default()
.color( .color(
ring_pattern(WHITE, BLACK) ring_pattern(WHITE_PAT, BLACK_PAT)
.transform(scaling(0.2, 0.2, 0.2)) .transform(scaling(0.2, 0.2, 0.2))
.build()?, .build()?,
) )
@ -143,7 +146,7 @@ fn main() -> Result<()> {
.material( .material(
MaterialBuilder::default() MaterialBuilder::default()
.color( .color(
checkers_pattern(WHITE, BLACK) checkers_pattern(WHITE_PAT, BLACK_PAT)
.transform(scaling(0.5, 0.5, 0.5)) .transform(scaling(0.5, 0.5, 0.5))
.build()?, .build()?,
) )

View File

@ -111,7 +111,7 @@ mod tests {
use crate::{ use crate::{
lights::PointLight, lights::PointLight,
materials::{lighting, Material}, materials::{lighting, Material},
patterns::Pattern, patterns::{Pattern, BLACK_PAT, WHITE_PAT},
shapes::Shape, shapes::Shape,
tuples::{point, vector, Color}, tuples::{point, vector, Color},
Float, BLACK, WHITE, Float, BLACK, WHITE,
@ -202,7 +202,7 @@ mod tests {
// Lighting with a pattern applied. // Lighting with a pattern applied.
let object = Shape::sphere(); let object = Shape::sphere();
let m = Material { let m = Material {
color: Pattern::stripe(WHITE, BLACK), color: Pattern::stripe(WHITE_PAT, BLACK_PAT),
ambient: 1., ambient: 1.,
diffuse: 0., diffuse: 0.,
specular: 0., specular: 0.,

View File

@ -160,7 +160,7 @@ impl From<[Float; 16]> for Matrix4x4 {
impl Matrix4x4 { impl Matrix4x4 {
/// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal. /// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal.
pub fn identity() -> Matrix4x4 { pub const fn identity() -> Matrix4x4 {
Matrix4x4::new( Matrix4x4::new(
[1., 0., 0., 0.], [1., 0., 0., 0.],
[0., 1., 0., 0.], [0., 1., 0., 0.],
@ -170,7 +170,7 @@ impl Matrix4x4 {
} }
/// Create a `Matrix4x4` with each of the given rows. /// Create a `Matrix4x4` with each of the given rows.
pub fn new(r0: [Float; 4], r1: [Float; 4], r2: [Float; 4], r3: [Float; 4]) -> Matrix4x4 { pub const fn new(r0: [Float; 4], r1: [Float; 4], r2: [Float; 4], r3: [Float; 4]) -> Matrix4x4 {
Matrix4x4 { Matrix4x4 {
m: [r0, r1, r2, r3], m: [r0, r1, r2, r3],
} }

View File

@ -4,28 +4,54 @@ use crate::{
matrices::Matrix4x4, matrices::Matrix4x4,
shapes::Shape, shapes::Shape,
tuples::{Color, Tuple}, tuples::{Color, Tuple},
WHITE, BLACK, WHITE,
}; };
#[derive(Debug, PartialEq, Copy, Clone)] pub const BLACK_PAT: Pattern = Pattern {
color: ColorMapper::Constant(BLACK),
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
};
pub const WHITE_PAT: Pattern = Pattern {
color: ColorMapper::Constant(WHITE),
transform: Matrix4x4::identity(),
inverse_transform: Matrix4x4::identity(),
};
#[derive(Debug, PartialEq, Clone)]
pub enum ColorMapper { pub enum ColorMapper {
/// TestPattern the color returned is the pattern space point after going through world->object and object->pattern space translation. /// TestPattern the color returned is the pattern space point after going through world->object and object->pattern space translation.
TestPattern, TestPattern,
/// Solid color, the same sampled every where. /// Solid color, the same sampled every where.
Constant(Color), Constant(Color),
/// Pattern that alternates between the given colors along each unit of the X-axis. The strip /// Pattern that alternates between the given patterns along each unit of the X-axis. The
/// extends infinitely in the positive and negative Y and Z axes. /// strip extends infinitely in the positive and negative Y and Z axes.
Stripe { a: Color, b: Color }, Stripe { a: Box<Pattern>, b: Box<Pattern> },
/// Linear blend between `a` and `b` along the X-axis. /// Linear blend between `a` and `b` along the X-axis.
Gradient { a: Color, b: Color }, Gradient { a: Box<Pattern>, b: Box<Pattern> },
/// Bullseye pattern in the XZ plane. /// Bullseye pattern in the XZ plane.
Ring { a: Color, b: Color }, Ring { a: Box<Pattern>, b: Box<Pattern> },
/// Traditional ray tracer tile floor pattern. /// Traditional ray tracer tile floor pattern.
Checkers { a: Color, b: Color }, Checkers { a: Box<Pattern>, b: Box<Pattern> },
}
impl From<Color> for ColorMapper {
fn from(c: Color) -> ColorMapper {
ColorMapper::Constant(c)
}
}
impl From<Color> for Box<Pattern> {
fn from(c: Color) -> Box<Pattern> {
Box::new(Pattern {
color: ColorMapper::Constant(c),
..Pattern::default()
})
}
} }
#[derive(Builder, Debug, PartialEq, Clone)] #[derive(Builder, Debug, PartialEq, Clone)]
#[builder(default, pattern = "owned")] #[builder(default, pattern = "immutable")]
pub struct Pattern { pub struct Pattern {
pub color: ColorMapper, pub color: ColorMapper,
transform: Matrix4x4, transform: Matrix4x4,
@ -70,26 +96,38 @@ pub fn test_pattern() -> PatternBuilder {
/// Builder for creating a material pattern that alternates between the given colors along each unit of the /// Builder for creating a material pattern that alternates between the given colors along each unit of the
/// X-axis. The strip extends infinitely in the positive and negative Y and Z axes. /// X-axis. The strip extends infinitely in the positive and negative Y and Z axes.
pub fn stripe_pattern(a: Color, b: Color) -> PatternBuilder { pub fn stripe_pattern(a: Pattern, b: Pattern) -> PatternBuilder {
PatternBuilder::default().color(ColorMapper::Stripe { a, b }) PatternBuilder::default().color(ColorMapper::Stripe {
a: a.into(),
b: b.into(),
})
} }
/// Builder for creating a material pattern that gradually blends between the given colors along /// Builder for creating a material pattern that gradually blends between the given colors along
/// the X-axis. /// the X-axis.
pub fn gradient_pattern(a: Color, b: Color) -> PatternBuilder { pub fn gradient_pattern(a: Pattern, b: Pattern) -> PatternBuilder {
PatternBuilder::default().color(ColorMapper::Gradient { a, b }) PatternBuilder::default().color(ColorMapper::Gradient {
a: a.into(),
b: b.into(),
})
} }
/// Builder for creating a material pattern that alternates between the given colors in a ring /// Builder for creating a material pattern that alternates between the given colors in a ring
/// shape in the XZ plane. /// shape in the XZ plane.
pub fn ring_pattern(a: Color, b: Color) -> PatternBuilder { pub fn ring_pattern(a: Pattern, b: Pattern) -> PatternBuilder {
PatternBuilder::default().color(ColorMapper::Ring { a, b }) PatternBuilder::default().color(ColorMapper::Ring {
a: a.into(),
b: b.into(),
})
} }
/// Builder for creating a material pattern that alternates between the given colors along the X, Y /// Builder for creating a material pattern that alternates between the given colors along the X, Y
/// and Z pattern. Creates traditional ray tracer tile floor pattern. /// and Z pattern. Creates traditional ray tracer tile floor pattern.
pub fn checkers_pattern(a: Color, b: Color) -> PatternBuilder { pub fn checkers_pattern(a: Pattern, b: Pattern) -> PatternBuilder {
PatternBuilder::default().color(ColorMapper::Checkers { a, b }) PatternBuilder::default().color(ColorMapper::Checkers {
a: a.into(),
b: b.into(),
})
} }
/// Generic implementation for mapping points to colors according to the given [ColorMapper]. /// Generic implementation for mapping points to colors according to the given [ColorMapper].
@ -103,58 +141,75 @@ impl Pattern {
} }
} }
/// Create a pattern that alternates between the given colors along each unit of the /// Create a pattern that alternates between the given Patterns along each unit of the
/// X-axis. The strip extends infinitely in the positive and negative Y and Z axes. /// X-axis. The strip extends infinitely in the positive and negative Y and Z axes.
pub fn stripe(a: Color, b: Color) -> Pattern { pub fn stripe(a: Pattern, b: Pattern) -> Pattern {
Pattern { Pattern {
color: ColorMapper::Stripe { a, b }, color: ColorMapper::Stripe {
a: a.into(),
b: b.into(),
},
..Pattern::default() ..Pattern::default()
} }
} }
/// Create a pattern that gradually blends between the given colors along the X-axis. /// Create a pattern that gradually blends between the given Patterns along the X-axis.
pub fn gradient(a: Color, b: Color) -> Pattern { pub fn gradient(a: Pattern, b: Pattern) -> Pattern {
Pattern { Pattern {
color: ColorMapper::Gradient { a, b }, color: ColorMapper::Gradient {
a: a.into(),
b: b.into(),
},
..Pattern::default() ..Pattern::default()
} }
} }
/// Create a pattern that alternates between the given colors in a ring in the XZ plane. /// Create a pattern that alternates between the given Patterns in a ring in the XZ plane.
pub fn ring(a: Color, b: Color) -> Pattern { pub fn ring(a: Pattern, b: Pattern) -> Pattern {
Pattern { Pattern {
color: ColorMapper::Ring { a, b }, color: ColorMapper::Ring {
a: a.into(),
b: b.into(),
},
..Pattern::default() ..Pattern::default()
} }
} }
/// Create a pattern that alternates between the given colors along the X, Y and Z axis. /// Create a pattern that alternates between the given Patterns along the X, Y and Z axis.
pub fn checkers(a: Color, b: Color) -> Pattern { pub fn checkers(a: Pattern, b: Pattern) -> Pattern {
Pattern { Pattern {
color: ColorMapper::Checkers { a, b }, color: ColorMapper::Checkers {
a: a.into(),
b: b.into(),
},
..Pattern::default() ..Pattern::default()
} }
} }
/// Sample the color at the given point in untranslated object space. /// Sample the color at the given point in untranslated object space.
pub fn pattern_at(&self, point: Tuple) -> Color { pub fn pattern_at(&self, object_point: Tuple) -> Color {
match self.color { let point = self.inverse_transform * object_point;
match &self.color {
ColorMapper::TestPattern => [point.x, point.y, point.z].into(), ColorMapper::TestPattern => [point.x, point.y, point.z].into(),
ColorMapper::Constant(c) => c, ColorMapper::Constant(c) => *c,
ColorMapper::Stripe { a, b } => { ColorMapper::Stripe { a, b } => {
let x = point.x.floor(); let x = point.x.floor();
if x % 2. == 0. { if x % 2. == 0. {
a a.pattern_at(point)
} else { } else {
b b.pattern_at(point)
} }
} }
ColorMapper::Gradient { a, b } => { ColorMapper::Gradient { a, b } => {
let a = a.pattern_at(point);
let b = b.pattern_at(point);
let distance = b - a; let distance = b - a;
let fraction = point.x - point.x.floor(); let fraction = point.x - point.x.floor();
a + distance * fraction a + distance * fraction
} }
ColorMapper::Ring { a, b } => { ColorMapper::Ring { a, b } => {
let a = a.pattern_at(point);
let b = b.pattern_at(point);
let px = point.x; let px = point.x;
let pz = point.z; let pz = point.z;
if (px * px + pz * pz).sqrt().floor() % 2. == 0. { if (px * px + pz * pz).sqrt().floor() % 2. == 0. {
@ -164,6 +219,8 @@ impl Pattern {
} }
} }
ColorMapper::Checkers { a, b } => { ColorMapper::Checkers { a, b } => {
let a = a.pattern_at(point);
let b = b.pattern_at(point);
let d = point.x.floor() + point.y.floor() + point.z.floor(); let d = point.x.floor() + point.y.floor() + point.z.floor();
if d % 2. == 0. { if d % 2. == 0. {
a a
@ -177,8 +234,7 @@ impl Pattern {
/// This function respects the object and the pattern's transform matrix. /// This function respects the object and the pattern's transform matrix.
pub fn pattern_at_object(&self, object: &Shape, world_point: Tuple) -> Color { pub fn pattern_at_object(&self, object: &Shape, world_point: Tuple) -> Color {
let object_point = object.inverse_transform() * world_point; let object_point = object.inverse_transform() * world_point;
let pattern_point = self.inverse_transform * object_point; self.pattern_at(object_point)
self.pattern_at(pattern_point)
} }
pub fn transform(&self) -> Matrix4x4 { pub fn transform(&self) -> Matrix4x4 {
self.transform self.transform
@ -198,7 +254,7 @@ impl Pattern {
mod tests { mod tests {
use crate::{ use crate::{
matrices::identity, matrices::identity,
patterns::{ColorMapper, Pattern}, patterns::{ColorMapper, Pattern, BLACK_PAT, WHITE_PAT},
BLACK, WHITE, BLACK, WHITE,
}; };
@ -210,23 +266,50 @@ mod tests {
#[test] #[test]
fn stripe_create() { fn stripe_create() {
let pattern = Pattern::stripe(BLACK, WHITE); let pattern = Pattern::stripe(BLACK_PAT, WHITE_PAT);
assert_eq!(pattern.color, ColorMapper::Stripe { a: BLACK, b: WHITE }); assert_eq!(
pattern.color,
ColorMapper::Stripe {
a: BLACK.into(),
b: WHITE.into(),
}
);
} }
#[test] #[test]
fn gradient_create() { fn gradient_create() {
let pattern = Pattern::gradient(BLACK, WHITE); println!("SHOE ME SOMETHING");
assert_eq!(pattern.color, ColorMapper::Gradient { a: BLACK, b: WHITE }); println!("* * * 1");
let pattern = Pattern::gradient(BLACK_PAT, WHITE_PAT);
println!("* * * 2");
assert_eq!(
pattern.color,
ColorMapper::Gradient {
a: BLACK.into(),
b: WHITE.into(),
}
);
} }
#[test] #[test]
fn ring_create() { fn ring_create() {
let pattern = Pattern::ring(BLACK, WHITE); let pattern = Pattern::ring(BLACK_PAT, WHITE_PAT);
assert_eq!(pattern.color, ColorMapper::Ring { a: BLACK, b: WHITE }); assert_eq!(
pattern.color,
ColorMapper::Ring {
a: BLACK.into(),
b: WHITE.into()
}
);
} }
#[test] #[test]
fn checkers_create() { fn checkers_create() {
let pattern = Pattern::checkers(BLACK, WHITE); let pattern = Pattern::checkers(BLACK_PAT, WHITE_PAT);
assert_eq!(pattern.color, ColorMapper::Checkers { a: BLACK, b: WHITE }); assert_eq!(
pattern.color,
ColorMapper::Checkers {
a: BLACK.into(),
b: WHITE.into()
}
);
} }
mod pattern_at { mod pattern_at {
@ -244,7 +327,7 @@ mod tests {
#[test] #[test]
fn stripe_alternates_between_two_colors() { fn stripe_alternates_between_two_colors() {
// A stripe alternates between two colors. // A stripe alternates between two colors.
let pattern = Pattern::stripe(WHITE, BLACK); let pattern = Pattern::stripe(WHITE_PAT, BLACK_PAT);
for (p, want) in &[ for (p, want) in &[
// A stripe pattern is constant in y. // A stripe pattern is constant in y.
(point(0., 0., 0.), WHITE), (point(0., 0., 0.), WHITE),
@ -269,7 +352,7 @@ mod tests {
#[test] #[test]
fn gradient_linearly_interpolates_between_colors() { fn gradient_linearly_interpolates_between_colors() {
// A gradient linearly interpolates between two colors. // A gradient linearly interpolates between two colors.
let pattern = Pattern::gradient(WHITE, BLACK); let pattern = Pattern::gradient(WHITE_PAT, BLACK_PAT);
assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE);
assert_eq!( assert_eq!(
pattern.pattern_at(point(0.25, 0., 0.)), pattern.pattern_at(point(0.25, 0., 0.)),
@ -288,7 +371,7 @@ mod tests {
#[test] #[test]
fn ring_extend_in_x_and_z() { fn ring_extend_in_x_and_z() {
// A ring should extend both in x and z. // A ring should extend both in x and z.
let pattern = Pattern::ring(WHITE, BLACK); let pattern = Pattern::ring(WHITE_PAT, BLACK_PAT);
assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE);
assert_eq!(pattern.pattern_at(point(1., 0., 0.)), BLACK); assert_eq!(pattern.pattern_at(point(1., 0., 0.)), BLACK);
assert_eq!(pattern.pattern_at(point(0., 0., 1.)), BLACK); assert_eq!(pattern.pattern_at(point(0., 0., 1.)), BLACK);
@ -299,7 +382,7 @@ mod tests {
#[test] #[test]
fn checkers_repeat_along_x_axis() { fn checkers_repeat_along_x_axis() {
// Checkers should repeat along X-axis. // Checkers should repeat along X-axis.
let pattern = Pattern::checkers(WHITE, BLACK); let pattern = Pattern::checkers(WHITE_PAT, BLACK_PAT);
assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE);
assert_eq!(pattern.pattern_at(point(0.99, 0., 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0.99, 0., 0.)), WHITE);
assert_eq!(pattern.pattern_at(point(1.01, 0., 0.)), BLACK); assert_eq!(pattern.pattern_at(point(1.01, 0., 0.)), BLACK);
@ -308,7 +391,7 @@ mod tests {
#[test] #[test]
fn checkers_repeat_along_y_axis() { fn checkers_repeat_along_y_axis() {
// Checkers should repeat along Y-axis. // Checkers should repeat along Y-axis.
let pattern = Pattern::checkers(WHITE, BLACK); let pattern = Pattern::checkers(WHITE_PAT, BLACK_PAT);
assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE);
assert_eq!(pattern.pattern_at(point(0., 0.99, 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0.99, 0.)), WHITE);
assert_eq!(pattern.pattern_at(point(0., 1.01, 0.)), BLACK); assert_eq!(pattern.pattern_at(point(0., 1.01, 0.)), BLACK);
@ -317,7 +400,7 @@ mod tests {
#[test] #[test]
fn checkers_repeat_along_z_axis() { fn checkers_repeat_along_z_axis() {
// Checkers should repeat along Z-axis. // Checkers should repeat along Z-axis.
let pattern = Pattern::checkers(WHITE, BLACK); let pattern = Pattern::checkers(WHITE_PAT, BLACK_PAT);
assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE);
assert_eq!(pattern.pattern_at(point(0., 0., 0.99)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0., 0.99)), WHITE);
assert_eq!(pattern.pattern_at(point(0., 0., 1.01)), BLACK); assert_eq!(pattern.pattern_at(point(0., 0., 1.01)), BLACK);
@ -329,10 +412,10 @@ mod tests {
use crate::{ use crate::{
matrices::scaling, matrices::scaling,
patterns::stripe_pattern, patterns::{stripe_pattern, BLACK_PAT, WHITE_PAT},
shapes::{Shape, ShapeBuilder}, shapes::{Shape, ShapeBuilder},
tuples::point, tuples::point,
BLACK, WHITE, WHITE,
}; };
#[test] #[test]
@ -341,7 +424,7 @@ mod tests {
let object = ShapeBuilder::sphere() let object = ShapeBuilder::sphere()
.transform(scaling(2., 2., 2.)) .transform(scaling(2., 2., 2.))
.build()?; .build()?;
let pattern = stripe_pattern(WHITE, BLACK).build()?; let pattern = stripe_pattern(WHITE_PAT, BLACK_PAT).build()?;
let c = pattern.pattern_at_object(&object, point(1.5, 0., 0.)); let c = pattern.pattern_at_object(&object, point(1.5, 0., 0.));
assert_eq!(c, WHITE); assert_eq!(c, WHITE);
Ok(()) Ok(())
@ -351,7 +434,7 @@ mod tests {
fn stripes_with_a_pattern_transformation() -> Result<(), Box<dyn Error>> { fn stripes_with_a_pattern_transformation() -> Result<(), Box<dyn Error>> {
// Stripes with a pattern transformation. // Stripes with a pattern transformation.
let object = Shape::sphere(); let object = Shape::sphere();
let mut pattern = stripe_pattern(WHITE, BLACK).build()?; let mut pattern = stripe_pattern(WHITE_PAT, BLACK_PAT).build()?;
pattern.set_transform(scaling(2., 2., 2.)); pattern.set_transform(scaling(2., 2., 2.));
let c = pattern.pattern_at_object(&object, point(1.5, 0., 0.)); let c = pattern.pattern_at_object(&object, point(1.5, 0., 0.));
assert_eq!(c, WHITE); assert_eq!(c, WHITE);