patterns: move tests from doctest to unit.

This commit is contained in:
Bill Thiede 2021-07-30 19:55:42 -07:00
parent 3aea76b35c
commit 5d6b3e6d57

View File

@ -64,112 +64,30 @@ where
/// 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.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// matrices::Matrix4x4,
/// patterns::{test_pattern, ColorMapper},
/// BLACK, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
///
/// let pattern = test_pattern().build()?;
/// assert_eq!(pattern.transform(), Matrix4x4::identity());
///
/// # Ok(())
/// # }
/// ```
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<std::error::Error>> {
///
/// 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<dyn std::error::Error>> {
///
/// 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 })
}
/// Builder for creating a material pattern that alternates between the given colors in a ring
/// shape in the XZ plane.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// patterns::{ring_pattern, ColorMapper},
/// BLACK, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
///
/// let pattern = ring_pattern(BLACK, WHITE).build()?;
/// assert_eq!(pattern.color, ColorMapper::Ring { a: BLACK, b: WHITE });
///
/// # Ok(())
/// # }
/// ```
pub fn ring_pattern(a: Color, b: Color) -> PatternBuilder {
PatternBuilder::default().color(ColorMapper::Ring { a, b })
}
/// 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.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// patterns::{checkers_pattern, ColorMapper},
/// BLACK, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
///
/// let pattern = checkers_pattern(BLACK, WHITE).build()?;
/// assert_eq!(pattern.color, ColorMapper::Checkers { a: BLACK, b: WHITE });
///
/// # Ok(())
/// # }
/// ```
pub fn checkers_pattern(a: Color, b: Color) -> PatternBuilder {
PatternBuilder::default().color(ColorMapper::Checkers { a, b })
}
@ -178,23 +96,6 @@ pub fn checkers_pattern(a: Color, b: Color) -> PatternBuilder {
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.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// matrices::Matrix4x4,
/// patterns::{ColorMapper, Pattern},
/// BLACK, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
///
/// let pattern = Pattern::test();
/// assert_eq!(pattern.transform(), Matrix4x4::identity());
///
/// # Ok(())
/// # }
/// ```
pub fn test() -> Pattern {
Pattern {
color: ColorMapper::TestPattern,
@ -204,17 +105,6 @@ 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 },
@ -223,17 +113,6 @@ impl Pattern {
}
/// Create a pattern that gradually blends between the given colors along the X-axis.
///
/// # 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 },
@ -242,17 +121,6 @@ impl Pattern {
}
/// Create a pattern that alternates between the given colors in a ring in the XZ plane.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// patterns::{ColorMapper, Pattern},
/// BLACK, WHITE,
/// };
///
/// let pattern = Pattern::ring(BLACK, WHITE);
/// assert_eq!(pattern.color, ColorMapper::Ring { a: BLACK, b: WHITE });
/// ```
pub fn ring(a: Color, b: Color) -> Pattern {
Pattern {
color: ColorMapper::Ring { a, b },
@ -261,17 +129,6 @@ impl Pattern {
}
/// Create a pattern that alternates between the given colors along the X, Y and Z axis.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// patterns::{ColorMapper, Pattern},
/// BLACK, WHITE,
/// };
///
/// let pattern = Pattern::checkers(BLACK, WHITE);
/// assert_eq!(pattern.color, ColorMapper::Checkers { a: BLACK, b: WHITE });
/// ```
pub fn checkers(a: Color, b: Color) -> Pattern {
Pattern {
color: ColorMapper::Checkers { a, b },
@ -280,80 +137,6 @@ impl Pattern {
}
/// Sample the color at the given point in untranslated object space.
///
/// # Examples
/// ```
/// use rtchallenge::{patterns::Pattern, tuples::point, BLACK, WHITE};
///
/// // 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),
/// (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);
/// }
///
/// // 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()
/// );
///
/// // A ring should extend both in x and z.
/// let pattern = Pattern::ring(WHITE, BLACK);
/// 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);
///
/// // Checkers should repeat along X-axis.
/// let pattern = Pattern::checkers(WHITE, BLACK);
/// 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);
///
/// // Checkers should repeat along Y-axis.
/// let pattern = Pattern::checkers(WHITE, BLACK);
/// 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);
///
/// // Checkers should repeat along Z-axis.
/// let pattern = Pattern::checkers(WHITE, BLACK);
/// 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);
/// ```
pub fn pattern_at(&self, point: Tuple) -> Color {
match self.color {
ColorMapper::TestPattern => [point.x, point.y, point.z].into(),
@ -392,37 +175,6 @@ impl Pattern {
}
/// Sample the color at the given world point on the given object.
/// This function respects the object and the pattern's transform matrix.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// matrices::scaling,
/// patterns::stripe_pattern,
/// shapes::{Shape, ShapeBuilder},
/// tuples::point,
/// BLACK, WHITE,
/// };
///
/// # fn main() -> Result<(), Box<std::error::Error>> {
///
/// // Stripes with an object transformation.
/// let object = ShapeBuilder::sphere()
/// .transform(scaling(2., 2., 2.))
/// .build()?;
/// let pattern = stripe_pattern(WHITE, BLACK).build()?;
/// let c = pattern.pattern_at_object(&object, point(1.5, 0., 0.));
/// assert_eq!(c, WHITE);
///
/// // Stripes with an pattern transformation.
/// let object = Shape::sphere();
/// let mut pattern = stripe_pattern(WHITE, BLACK).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(())
/// # }
/// ```
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;
@ -441,3 +193,169 @@ impl Pattern {
self.inverse_transform = t.inverse();
}
}
#[cfg(test)]
mod tests {
use crate::{
matrices::identity,
patterns::{ColorMapper, Pattern},
BLACK, WHITE,
};
#[test]
fn test_create() {
let pattern = Pattern::test();
assert_eq!(pattern.transform(), identity());
}
#[test]
fn stripe_create() {
let pattern = Pattern::stripe(BLACK, WHITE);
assert_eq!(pattern.color, ColorMapper::Stripe { a: BLACK, b: WHITE });
}
#[test]
fn gradient_create() {
let pattern = Pattern::gradient(BLACK, WHITE);
assert_eq!(pattern.color, ColorMapper::Gradient { a: BLACK, b: WHITE });
}
#[test]
fn ring_create() {
let pattern = Pattern::ring(BLACK, WHITE);
assert_eq!(pattern.color, ColorMapper::Ring { a: BLACK, b: WHITE });
}
#[test]
fn checkers_create() {
let pattern = Pattern::checkers(BLACK, WHITE);
assert_eq!(pattern.color, ColorMapper::Checkers { a: BLACK, b: WHITE });
}
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, BLACK);
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, 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()
);
}
#[test]
fn ring_extend_in_x_and_z() {
// A ring should extend both in x and z.
let pattern = Pattern::ring(WHITE, BLACK);
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, BLACK);
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, BLACK);
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, BLACK);
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,
shapes::{Shape, ShapeBuilder},
tuples::point,
BLACK, 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, BLACK).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, BLACK).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(())
}
}
}