use derive_builder::Builder; use crate::{ matrices::Matrix4x4, shapes::Shape, tuples::{Color, Tuple}, BLACK, WHITE, }; 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 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: Box, b: Box }, /// Bullseye pattern in the XZ plane. Ring { a: Box, b: Box }, /// Traditional ray tracer tile floor pattern. 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 = "immutable")] pub struct Pattern { pub color: ColorMapper, transform: Matrix4x4, #[builder(private, default = "self.default_inverse_transform()?")] inverse_transform: Matrix4x4, } impl PatternBuilder { fn default_inverse_transform(&self) -> Result { Ok(self.transform.unwrap_or(Matrix4x4::identity()).inverse()) } } impl Default for Pattern { fn default() -> Pattern { Pattern { color: ColorMapper::Constant(WHITE), transform: Matrix4x4::identity(), inverse_transform: Matrix4x4::identity(), } } } /// Creates a [Pattern] with a color type of [ColorMapper::Constant] from the given [Color] impl From for Pattern where C: Into, { fn from(c: C) -> Self { Pattern { color: ColorMapper::Constant(c.into()), ..Pattern::default() } } } /// 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. 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. 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: 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: 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: 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]. impl Pattern { /// Create a pattern used for testing. The color returned is the pattern space point /// after going through world->object and object->pattern space translation. pub fn test() -> Pattern { Pattern { color: ColorMapper::TestPattern, ..Pattern::default() } } /// 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: Pattern, b: Pattern) -> Pattern { Pattern { color: ColorMapper::Stripe { a: a.into(), b: b.into(), }, ..Pattern::default() } } /// 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: a.into(), b: b.into(), }, ..Pattern::default() } } /// 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: a.into(), b: b.into(), }, ..Pattern::default() } } /// 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: a.into(), b: b.into(), }, ..Pattern::default() } } /// Sample the color at the given point in untranslated object space. 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::Stripe { a, b } => { let x = point.x.floor(); if x % 2. == 0. { a.pattern_at(point) } else { 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. { a } else { 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(); if d % 2. == 0. { a } else { b } } } } /// Sample the color at the given world point on the given object. /// 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; self.pattern_at(object_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(); } } #[cfg(test)] mod tests { use crate::{ matrices::identity, patterns::{ColorMapper, Pattern, BLACK_PAT, WHITE_PAT}, BLACK, WHITE, }; #[test] fn test_create() { let pattern = Pattern::test(); assert_eq!(pattern.transform(), identity()); } #[test] fn stripe_create() { let pattern = Pattern::stripe(BLACK_PAT, WHITE_PAT); assert_eq!( pattern.color, ColorMapper::Stripe { a: BLACK.into(), b: WHITE.into(), } ); } #[test] fn gradient_create() { 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_PAT, WHITE_PAT); assert_eq!( pattern.color, ColorMapper::Ring { a: BLACK.into(), b: WHITE.into() } ); } #[test] fn checkers_create() { let pattern = Pattern::checkers(BLACK_PAT, WHITE_PAT); assert_eq!( pattern.color, ColorMapper::Checkers { a: BLACK.into(), b: WHITE.into() } ); } mod pattern_at { use super::*; use crate::tuples::point; #[test] fn test_returns_coordinates() { // 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()); } #[test] fn stripe_alternates_between_two_colors() { // A stripe alternates between two colors. let pattern = Pattern::stripe(WHITE_PAT, BLACK_PAT); for (p, want) in &[ // A stripe pattern is constant in y. (point(0., 0., 0.), WHITE), (point(0., 1., 0.), WHITE), (point(0., 2., 0.), WHITE), // A stripe pattern is constant in z. (point(0., 0., 0.), WHITE), (point(0., 0., 1.), WHITE), (point(0., 0., 2.), WHITE), // A stripe pattern alternates in z. (point(0., 0., 0.), WHITE), (point(0.9, 0., 0.), WHITE), (point(1., 0., 0.), BLACK), (point(-0.1, 0., 0.), BLACK), (point(-1., 0., 0.), BLACK), (point(-1.1, 0., 0.), WHITE), ] { assert_eq!(pattern.pattern_at(*p), *want, "{:?}", p); } } #[test] fn gradient_linearly_interpolates_between_colors() { // A gradient linearly interpolates between two colors. 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.)), [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() ); } #[test] fn ring_extend_in_x_and_z() { // A ring should extend both in x and z. 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); // 0.708 is slight more than 2.sqrt()/2. assert_eq!(pattern.pattern_at(point(0.708, 0., 0.708)), BLACK); } #[test] fn checkers_repeat_along_x_axis() { // Checkers should repeat along X-axis. 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); } #[test] fn checkers_repeat_along_y_axis() { // Checkers should repeat along Y-axis. 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); } #[test] fn checkers_repeat_along_z_axis() { // Checkers should repeat along Z-axis. 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); } } mod pattern_at_object { use std::error::Error; use crate::{ matrices::scaling, patterns::{stripe_pattern, BLACK_PAT, WHITE_PAT}, shapes::{Shape, ShapeBuilder}, tuples::point, WHITE, }; #[test] fn stripes_with_an_object_transformation() -> Result<(), Box> { // Stripes with an object transformation. let object = ShapeBuilder::sphere() .transform(scaling(2., 2., 2.)) .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(()) } #[test] fn stripes_with_a_pattern_transformation() -> Result<(), Box> { // Stripes with a pattern transformation. let object = Shape::sphere(); 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); Ok(()) } } }