From 74fe69188a7665411842844dd5f2ac11a323aa45 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Sun, 25 Jul 2021 14:11:20 -0700 Subject: [PATCH] patterns: add Gradient pattern. --- rtchallenge/src/lib.rs | 2 +- rtchallenge/src/patterns.rs | 163 +++++++++++++++++++++++++----------- 2 files changed, 116 insertions(+), 49 deletions(-) diff --git a/rtchallenge/src/lib.rs b/rtchallenge/src/lib.rs index 63be81c..b89954b 100644 --- a/rtchallenge/src/lib.rs +++ b/rtchallenge/src/lib.rs @@ -45,7 +45,7 @@ pub mod prelude { lights::{PointLight, PointLightBuilder}, materials::{Material, MaterialBuilder}, matrices::{identity, rotation_x, rotation_y, rotation_z, scaling, shearing, translation}, - patterns::stripe_pattern, + patterns::{gradient_pattern, stripe_pattern}, shapes::{plane, sphere, test_shape}, transformations::view_transform, tuples::{point, vector, Color}, diff --git a/rtchallenge/src/patterns.rs b/rtchallenge/src/patterns.rs index 17b47f7..2f216d8 100644 --- a/rtchallenge/src/patterns.rs +++ b/rtchallenge/src/patterns.rs @@ -16,6 +16,8 @@ pub enum ColorMapper { /// 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 }, + /// Linear blend between `a` and `b` along the X-axis. + Gradient { a: Color, b: Color }, } #[derive(Builder, Debug, PartialEq, Clone)] @@ -56,28 +58,6 @@ where } } -/// 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. -/// -/// # Examples -/// ``` -/// use rtchallenge::{ -/// patterns::{stripe_pattern, ColorMapper}, -/// BLACK, WHITE, -/// }; -/// -/// # fn main() -> Result<(), Box> { -/// -/// let pattern = stripe_pattern(BLACK, WHITE).build()?; -/// assert_eq!(pattern.color, ColorMapper::Stripe { a: BLACK, b: WHITE }); -/// -/// # Ok(()) -/// # } -/// ``` -pub fn stripe_pattern(a: Color, b: Color) -> PatternBuilder { - PatternBuilder::default().color(ColorMapper::Stripe { a, b }) -} - /// Builder for creating a material pattern used for testing. The color returned is the pattern space point /// after going through world->object and object->pattern space translation. /// @@ -101,28 +81,53 @@ pub fn test_pattern() -> PatternBuilder { PatternBuilder::default().color(ColorMapper::TestPattern) } +/// 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. +/// +/// # Examples +/// ``` +/// use rtchallenge::{ +/// patterns::{stripe_pattern, ColorMapper}, +/// BLACK, WHITE, +/// }; +/// +/// # fn main() -> Result<(), Box> { +/// +/// let pattern = stripe_pattern(BLACK, WHITE).build()?; +/// assert_eq!(pattern.color, ColorMapper::Stripe { a: BLACK, b: WHITE }); +/// +/// # Ok(()) +/// # } +/// ``` +pub fn stripe_pattern(a: Color, b: Color) -> PatternBuilder { + PatternBuilder::default().color(ColorMapper::Stripe { a, b }) +} + +/// Builder for creating a material pattern that gradually blends between the given colors along +/// the X-axis. +/// +/// # Examples +/// ``` +/// use rtchallenge::{ +/// matrices::Matrix4x4, +/// patterns::{gradient_pattern, ColorMapper}, +/// BLACK, WHITE, +/// }; +/// +/// # fn main() -> Result<(), Box> { +/// +/// let pattern = gradient_pattern(WHITE, BLACK).build()?; +/// assert_eq!(pattern.color, ColorMapper::Gradient { a: WHITE, b: BLACK }); +/// +/// # Ok(()) +/// # } +/// ``` +pub fn gradient_pattern(a: Color, b: Color) -> PatternBuilder { + PatternBuilder::default().color(ColorMapper::Gradient { a, b }) +} + /// Generic implementation for mapping points to colors according to the given [ColorMapper]. impl Pattern { - /// Create a 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. - /// - /// # Examples - /// ``` - /// use rtchallenge::{ - /// patterns::{ColorMapper, Pattern}, - /// BLACK, WHITE, - /// }; - /// - /// let pattern = Pattern::stripe(BLACK, WHITE); - /// assert_eq!(pattern.color, ColorMapper::Stripe { a: BLACK, b: WHITE }); - /// ``` - pub fn stripe(a: Color, b: Color) -> Pattern { - Pattern { - color: ColorMapper::Stripe { a, b }, - ..Pattern::default() - } - } - /// Create a pattern used for testing. The color returned is the pattern space point /// after going through world->object and object->pattern space translation. /// @@ -148,16 +153,60 @@ impl Pattern { ..Pattern::default() } } + + /// Create a 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. + /// + /// # Examples + /// ``` + /// use rtchallenge::{ + /// patterns::{ColorMapper, Pattern}, + /// BLACK, WHITE, + /// }; + /// + /// let pattern = Pattern::stripe(BLACK, WHITE); + /// assert_eq!(pattern.color, ColorMapper::Stripe { a: BLACK, b: WHITE }); + /// ``` + pub fn stripe(a: Color, b: Color) -> Pattern { + Pattern { + color: ColorMapper::Stripe { a, b }, + ..Pattern::default() + } + } + + /// Create a 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. + /// + /// # Examples + /// ``` + /// use rtchallenge::{ + /// patterns::{ColorMapper, Pattern}, + /// BLACK, WHITE, + /// }; + /// + /// let pattern = Pattern::gradient(BLACK, WHITE); + /// assert_eq!(pattern.color, ColorMapper::Gradient { a: BLACK, b: WHITE }); + /// ``` + pub fn gradient(a: Color, b: Color) -> Pattern { + Pattern { + color: ColorMapper::Gradient { a, b }, + ..Pattern::default() + } + } + /// Sample the color at the given point in untranslated object space. /// /// # Examples /// ``` - /// use rtchallenge::{patterns::stripe_pattern, tuples::point, BLACK, WHITE}; + /// use rtchallenge::{patterns::Pattern, tuples::point, BLACK, WHITE}; /// - /// # fn main() -> Result<(), Box> { - /// - /// let pattern = stripe_pattern(WHITE, BLACK).build()?; + /// // A test returns the pattern space coordinates of the point. + /// let pattern = Pattern::test(); + /// let p = point(1., 2., 3.); + /// assert_eq!(pattern.pattern_at(p), [p.x, p.y, p.z].into()); /// + /// // A stripe alternates between two colors. + /// let pattern = Pattern::stripe(WHITE, BLACK); /// for (p, want) in &[ /// // A stripe pattern is constant in y. /// (point(0., 0., 0.), WHITE), @@ -178,8 +227,21 @@ impl Pattern { /// assert_eq!(pattern.pattern_at(*p), *want, "{:?}", p); /// } /// - /// # Ok(()) - /// # } + /// // A gradient linearly interpolates between two colors. + /// let pattern = Pattern::gradient(WHITE, BLACK); + /// assert_eq!(pattern.pattern_at(point(0., 0., 0.)), WHITE); + /// assert_eq!( + /// pattern.pattern_at(point(0.25, 0., 0.)), + /// [0.75, 0.75, 0.75].into() + /// ); + /// assert_eq!( + /// pattern.pattern_at(point(0.5, 0., 0.)), + /// [0.5, 0.5, 0.5].into() + /// ); + /// assert_eq!( + /// pattern.pattern_at(point(0.75, 0., 0.)), + /// [0.25, 0.25, 0.25].into() + /// ); /// ``` pub fn pattern_at(&self, point: Tuple) -> Color { match self.color { @@ -201,6 +263,11 @@ impl Pattern { b } } + ColorMapper::Gradient { a, b } => { + let distance = b - a; + let fraction = point.x - point.x.floor(); + a + distance * fraction + } } } /// Sample the color at the given world point on the given object.