From 926fffa29fb688e1a566be295f2ec6c5751b0db4 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 1 Aug 2021 19:08:36 -0700 Subject: [PATCH] patterns: add ability to nest patterns --- rtchallenge/examples/eoc10.rs | 15 +-- rtchallenge/src/materials.rs | 4 +- rtchallenge/src/matrices.rs | 4 +- rtchallenge/src/patterns.rs | 193 ++++++++++++++++++++++++---------- 4 files changed, 151 insertions(+), 65 deletions(-) diff --git a/rtchallenge/examples/eoc10.rs b/rtchallenge/examples/eoc10.rs index 145ef7a..6ed250a 100644 --- a/rtchallenge/examples/eoc10.rs +++ b/rtchallenge/examples/eoc10.rs @@ -6,7 +6,10 @@ use structopt::StructOpt; use rtchallenge::prelude::*; 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. @@ -61,7 +64,7 @@ fn main() -> Result<()> { .material( MaterialBuilder::default() .color( - checkers_pattern(WHITE, BLACK) + checkers_pattern(WHITE_PAT, BLACK_PAT) .transform(translation(1., 0., 0.) * scaling(2., 2., 2.)) .build()?, ) @@ -90,7 +93,7 @@ fn main() -> Result<()> { .transform(translation(2., 1., 0.) * sphere_size) .material( MaterialBuilder::default() - .color(stripe_pattern(WHITE, BLACK).build()?) + .color(stripe_pattern(WHITE_PAT, BLACK_PAT).build()?) .diffuse(0.7) .specular(0.3) .build()?, @@ -102,7 +105,7 @@ fn main() -> Result<()> { .material( MaterialBuilder::default() .color( - stripe_pattern(WHITE, BLACK) + stripe_pattern(WHITE_PAT, BLACK_PAT) .transform(scaling(0.2, 1., 1.)) .build()?, ) @@ -128,7 +131,7 @@ fn main() -> Result<()> { .material( MaterialBuilder::default() .color( - ring_pattern(WHITE, BLACK) + ring_pattern(WHITE_PAT, BLACK_PAT) .transform(scaling(0.2, 0.2, 0.2)) .build()?, ) @@ -143,7 +146,7 @@ fn main() -> Result<()> { .material( MaterialBuilder::default() .color( - checkers_pattern(WHITE, BLACK) + checkers_pattern(WHITE_PAT, BLACK_PAT) .transform(scaling(0.5, 0.5, 0.5)) .build()?, ) diff --git a/rtchallenge/src/materials.rs b/rtchallenge/src/materials.rs index 729677d..36aa665 100644 --- a/rtchallenge/src/materials.rs +++ b/rtchallenge/src/materials.rs @@ -111,7 +111,7 @@ mod tests { use crate::{ lights::PointLight, materials::{lighting, Material}, - patterns::Pattern, + patterns::{Pattern, BLACK_PAT, WHITE_PAT}, shapes::Shape, tuples::{point, vector, Color}, Float, BLACK, WHITE, @@ -202,7 +202,7 @@ mod tests { // Lighting with a pattern applied. let object = Shape::sphere(); let m = Material { - color: Pattern::stripe(WHITE, BLACK), + color: Pattern::stripe(WHITE_PAT, BLACK_PAT), ambient: 1., diffuse: 0., specular: 0., diff --git a/rtchallenge/src/matrices.rs b/rtchallenge/src/matrices.rs index ba02aed..447ecfc 100644 --- a/rtchallenge/src/matrices.rs +++ b/rtchallenge/src/matrices.rs @@ -160,7 +160,7 @@ impl From<[Float; 16]> for Matrix4x4 { impl Matrix4x4 { /// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal. - pub fn identity() -> Matrix4x4 { + pub const fn identity() -> Matrix4x4 { Matrix4x4::new( [1., 0., 0., 0.], [0., 1., 0., 0.], @@ -170,7 +170,7 @@ impl Matrix4x4 { } /// 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 { m: [r0, r1, r2, r3], } diff --git a/rtchallenge/src/patterns.rs b/rtchallenge/src/patterns.rs index fbb83d6..17faa78 100644 --- a/rtchallenge/src/patterns.rs +++ b/rtchallenge/src/patterns.rs @@ -4,28 +4,54 @@ use crate::{ matrices::Matrix4x4, shapes::Shape, 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 { /// TestPattern the color returned is the pattern space point after going through world->object and object->pattern space translation. TestPattern, /// Solid color, the same sampled every where. Constant(Color), - /// 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. - Stripe { a: Color, b: Color }, + /// 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. + Stripe { a: Box, b: Box }, /// Linear blend between `a` and `b` along the X-axis. - Gradient { a: Color, b: Color }, + Gradient { a: Box, b: Box }, /// Bullseye pattern in the XZ plane. - Ring { a: Color, b: Color }, + Ring { a: Box, b: Box }, /// Traditional ray tracer tile floor pattern. - Checkers { a: Color, b: Color }, + Checkers { a: Box, b: Box }, +} + +impl From for ColorMapper { + fn from(c: Color) -> ColorMapper { + ColorMapper::Constant(c) + } +} + +impl From for Box { + fn from(c: Color) -> Box { + Box::new(Pattern { + color: ColorMapper::Constant(c), + ..Pattern::default() + }) + } } #[derive(Builder, Debug, PartialEq, Clone)] -#[builder(default, pattern = "owned")] +#[builder(default, pattern = "immutable")] pub struct Pattern { pub color: ColorMapper, 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 /// X-axis. The strip extends infinitely in the positive and negative Y and Z axes. -pub fn stripe_pattern(a: Color, b: Color) -> PatternBuilder { - PatternBuilder::default().color(ColorMapper::Stripe { a, b }) +pub fn stripe_pattern(a: Pattern, b: Pattern) -> PatternBuilder { + 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 /// the X-axis. -pub fn gradient_pattern(a: Color, b: Color) -> PatternBuilder { - PatternBuilder::default().color(ColorMapper::Gradient { a, b }) +pub fn gradient_pattern(a: Pattern, b: Pattern) -> PatternBuilder { + 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 /// shape in the XZ plane. -pub fn ring_pattern(a: Color, b: Color) -> PatternBuilder { - PatternBuilder::default().color(ColorMapper::Ring { a, b }) +pub fn ring_pattern(a: Pattern, b: Pattern) -> PatternBuilder { + 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 /// and Z pattern. Creates traditional ray tracer tile floor pattern. -pub fn checkers_pattern(a: Color, b: Color) -> PatternBuilder { - PatternBuilder::default().color(ColorMapper::Checkers { a, b }) +pub fn checkers_pattern(a: Pattern, b: Pattern) -> PatternBuilder { + PatternBuilder::default().color(ColorMapper::Checkers { + a: a.into(), + b: b.into(), + }) } /// 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. - pub fn stripe(a: Color, b: Color) -> Pattern { + pub fn stripe(a: Pattern, b: Pattern) -> Pattern { Pattern { - color: ColorMapper::Stripe { a, b }, + color: ColorMapper::Stripe { + a: a.into(), + b: b.into(), + }, ..Pattern::default() } } - /// Create a pattern that gradually blends between the given colors along the X-axis. - pub fn gradient(a: Color, b: Color) -> Pattern { + /// Create a pattern that gradually blends between the given Patterns along the X-axis. + pub fn gradient(a: Pattern, b: Pattern) -> Pattern { Pattern { - color: ColorMapper::Gradient { a, b }, + color: ColorMapper::Gradient { + a: a.into(), + b: b.into(), + }, ..Pattern::default() } } - /// Create a pattern that alternates between the given colors in a ring in the XZ plane. - pub fn ring(a: Color, b: Color) -> Pattern { + /// Create a pattern that alternates between the given Patterns in a ring in the XZ plane. + pub fn ring(a: Pattern, b: Pattern) -> Pattern { Pattern { - color: ColorMapper::Ring { a, b }, + color: ColorMapper::Ring { + a: a.into(), + b: b.into(), + }, ..Pattern::default() } } - /// Create a pattern that alternates between the given colors along the X, Y and Z axis. - pub fn checkers(a: Color, b: Color) -> Pattern { + /// Create a pattern that alternates between the given Patterns along the X, Y and Z axis. + pub fn checkers(a: Pattern, b: Pattern) -> Pattern { Pattern { - color: ColorMapper::Checkers { a, b }, + color: ColorMapper::Checkers { + a: a.into(), + b: b.into(), + }, ..Pattern::default() } } /// Sample the color at the given point in untranslated object space. - pub fn pattern_at(&self, point: Tuple) -> Color { - match self.color { + pub fn pattern_at(&self, object_point: Tuple) -> Color { + let point = self.inverse_transform * object_point; + match &self.color { ColorMapper::TestPattern => [point.x, point.y, point.z].into(), - ColorMapper::Constant(c) => c, + ColorMapper::Constant(c) => *c, ColorMapper::Stripe { a, b } => { let x = point.x.floor(); if x % 2. == 0. { - a + a.pattern_at(point) } else { - b + b.pattern_at(point) } } ColorMapper::Gradient { a, b } => { + let a = a.pattern_at(point); + let b = b.pattern_at(point); let distance = b - a; let fraction = point.x - point.x.floor(); a + distance * fraction } ColorMapper::Ring { a, b } => { + let a = a.pattern_at(point); + let b = b.pattern_at(point); let px = point.x; let pz = point.z; if (px * px + pz * pz).sqrt().floor() % 2. == 0. { @@ -164,6 +219,8 @@ impl Pattern { } } 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(); if d % 2. == 0. { a @@ -177,8 +234,7 @@ impl Pattern { /// This function respects the object and the pattern's transform matrix. pub fn pattern_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.pattern_at(pattern_point) + self.pattern_at(object_point) } pub fn transform(&self) -> Matrix4x4 { self.transform @@ -198,7 +254,7 @@ impl Pattern { mod tests { use crate::{ matrices::identity, - patterns::{ColorMapper, Pattern}, + patterns::{ColorMapper, Pattern, BLACK_PAT, WHITE_PAT}, BLACK, WHITE, }; @@ -210,23 +266,50 @@ mod tests { #[test] fn stripe_create() { - let pattern = Pattern::stripe(BLACK, WHITE); - assert_eq!(pattern.color, ColorMapper::Stripe { a: BLACK, b: WHITE }); + let pattern = Pattern::stripe(BLACK_PAT, WHITE_PAT); + assert_eq!( + pattern.color, + ColorMapper::Stripe { + a: BLACK.into(), + b: WHITE.into(), + } + ); } #[test] fn gradient_create() { - let pattern = Pattern::gradient(BLACK, WHITE); - assert_eq!(pattern.color, ColorMapper::Gradient { a: BLACK, b: WHITE }); + println!("SHOE ME SOMETHING"); + 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] fn ring_create() { - let pattern = Pattern::ring(BLACK, WHITE); - assert_eq!(pattern.color, ColorMapper::Ring { a: BLACK, b: WHITE }); + let pattern = Pattern::ring(BLACK_PAT, WHITE_PAT); + assert_eq!( + pattern.color, + ColorMapper::Ring { + a: BLACK.into(), + b: WHITE.into() + } + ); } #[test] fn checkers_create() { - let pattern = Pattern::checkers(BLACK, WHITE); - assert_eq!(pattern.color, ColorMapper::Checkers { a: BLACK, b: WHITE }); + let pattern = Pattern::checkers(BLACK_PAT, WHITE_PAT); + assert_eq!( + pattern.color, + ColorMapper::Checkers { + a: BLACK.into(), + b: WHITE.into() + } + ); } mod pattern_at { @@ -244,7 +327,7 @@ mod tests { #[test] fn 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 &[ // A stripe pattern is constant in y. (point(0., 0., 0.), WHITE), @@ -269,7 +352,7 @@ mod tests { #[test] fn gradient_linearly_interpolates_between_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.25, 0., 0.)), @@ -288,7 +371,7 @@ mod tests { #[test] fn ring_extend_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(1., 0., 0.)), BLACK); assert_eq!(pattern.pattern_at(point(0., 0., 1.)), BLACK); @@ -299,7 +382,7 @@ mod tests { #[test] fn checkers_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.99, 0., 0.)), WHITE); assert_eq!(pattern.pattern_at(point(1.01, 0., 0.)), BLACK); @@ -308,7 +391,7 @@ mod tests { #[test] fn checkers_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.99, 0.)), WHITE); assert_eq!(pattern.pattern_at(point(0., 1.01, 0.)), BLACK); @@ -317,7 +400,7 @@ mod tests { #[test] fn checkers_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.99)), WHITE); assert_eq!(pattern.pattern_at(point(0., 0., 1.01)), BLACK); @@ -329,10 +412,10 @@ mod tests { use crate::{ matrices::scaling, - patterns::stripe_pattern, + patterns::{stripe_pattern, BLACK_PAT, WHITE_PAT}, shapes::{Shape, ShapeBuilder}, tuples::point, - BLACK, WHITE, + WHITE, }; #[test] @@ -341,7 +424,7 @@ mod tests { let object = ShapeBuilder::sphere() .transform(scaling(2., 2., 2.)) .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.)); assert_eq!(c, WHITE); Ok(()) @@ -351,7 +434,7 @@ mod tests { fn stripes_with_a_pattern_transformation() -> Result<(), Box> { // Stripes with a pattern transformation. 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.)); let c = pattern.pattern_at_object(&object, point(1.5, 0., 0.)); assert_eq!(c, WHITE);