445 lines
14 KiB
Rust
445 lines
14 KiB
Rust
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<Pattern>, b: Box<Pattern> },
|
|
/// Linear blend between `a` and `b` along the X-axis.
|
|
Gradient { a: Box<Pattern>, b: Box<Pattern> },
|
|
/// Bullseye pattern in the XZ plane.
|
|
Ring { a: Box<Pattern>, b: Box<Pattern> },
|
|
/// Traditional ray tracer tile floor pattern.
|
|
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)]
|
|
#[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<Matrix4x4, String> {
|
|
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<C> From<C> for Pattern
|
|
where
|
|
C: Into<Color>,
|
|
{
|
|
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<dyn Error>> {
|
|
// 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<dyn Error>> {
|
|
// 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(())
|
|
}
|
|
}
|
|
}
|