Compare commits
4 Commits
9befbd9ad2
...
270a7ec349
| Author | SHA1 | Date | |
|---|---|---|---|
| 270a7ec349 | |||
| de6cd0da4d | |||
| 7a80179f41 | |||
| 926fffa29f |
@ -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()?,
|
||||
)
|
||||
|
||||
218
rtchallenge/examples/eoc11.rs
Normal file
218
rtchallenge/examples/eoc11.rs
Normal file
@ -0,0 +1,218 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use rtchallenge::prelude::*;
|
||||
|
||||
use rtchallenge::{
|
||||
camera::RenderStrategy,
|
||||
float::consts::PI,
|
||||
patterns::{test_pattern, BLACK_PAT, WHITE_PAT},
|
||||
WHITE,
|
||||
};
|
||||
|
||||
/// End of chapter 11 challenge.
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(name = "eoc11")]
|
||||
struct Opt {
|
||||
/// Strategy for casting rays into image.
|
||||
#[structopt(long, default_value = "rayon")]
|
||||
render_strategy: RenderStrategy,
|
||||
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
|
||||
/// times randomly across the pixel.
|
||||
#[structopt(short, long, default_value = "0")]
|
||||
samples: usize,
|
||||
/// Rendered image width in pixels.
|
||||
#[structopt(short, long, default_value = "2560")]
|
||||
width: usize,
|
||||
/// Rendered image height in pixels.
|
||||
#[structopt(short, long, default_value = "1440")]
|
||||
height: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let start = Instant::now();
|
||||
let opt = Opt::from_args();
|
||||
|
||||
let light1 = PointLightBuilder::default()
|
||||
.position(point(-5., 5., -5.))
|
||||
.intensity(WHITE)
|
||||
.build()?;
|
||||
let light2 = PointLightBuilder::default()
|
||||
.position(point(5., 5., -5.))
|
||||
.intensity([0.2, 0.2, 0.6])
|
||||
.build()?;
|
||||
let light3 = PointLightBuilder::default()
|
||||
.position(point(0., 2., -5.))
|
||||
.intensity([0.2, 0.2, 0.1])
|
||||
.build()?;
|
||||
|
||||
let from = point(2., 8., -10.);
|
||||
let to = point(2., 1., -1.);
|
||||
let up = point(0., 1., 0.);
|
||||
let camera = CameraBuilder::default()
|
||||
.hsize(opt.width)
|
||||
.vsize(opt.height)
|
||||
.field_of_view(PI / 6.)
|
||||
.transform(view_transform(from, to, up))
|
||||
.render_strategy(opt.render_strategy)
|
||||
.samples_per_pixel(opt.samples)
|
||||
.build()?;
|
||||
|
||||
let floor = plane()
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
checkers_pattern(
|
||||
ring_pattern([0.8, 0.8, 0.2].into(), [0.8, 0.2, 0.8].into())
|
||||
.transform(scaling(1. / 8., 1. / 8., 1. / 8.))
|
||||
.build()?,
|
||||
ring_pattern([0.2, 0.8, 0.2].into(), [0.8, 0.2, 0.2].into())
|
||||
.transform(scaling(1. / 4., 1. / 4., 1. / 4.))
|
||||
.build()?,
|
||||
)
|
||||
.transform(translation(0., 1., 0.) * scaling(2., 2., 2.))
|
||||
.build()?,
|
||||
)
|
||||
.specular(0.)
|
||||
.reflective(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
let sphere_size = scaling(0.5, 0.5, 0.5);
|
||||
|
||||
let x1y1 = sphere()
|
||||
.transform(translation(1., 1., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
gradient_pattern([0., 0., 1.].into(), [1., 1., 0.].into())
|
||||
.transform(scaling(2., 1., 1.) * translation(-0.5, 0., 0.))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.reflective(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y1 = sphere()
|
||||
.transform(translation(2., 1., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(stripe_pattern(WHITE_PAT, BLACK_PAT).build()?)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x3y1 = sphere()
|
||||
.transform(translation(3., 1., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
stripe_pattern(WHITE_PAT, BLACK_PAT)
|
||||
.transform(scaling(0.2, 1., 1.))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.0)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x1y2 = sphere()
|
||||
.transform(translation(1., 2., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(test_pattern().build()?)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y2 = sphere()
|
||||
.transform(translation(2., 2., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
ring_pattern(WHITE_PAT, BLACK_PAT)
|
||||
.transform(scaling(0.2, 0.2, 0.2))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x3y2 = sphere()
|
||||
.transform(translation(3., 2., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
checkers_pattern(WHITE_PAT, BLACK_PAT)
|
||||
.transform(scaling(0.5, 0.5, 0.5))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x1y2z1 = sphere()
|
||||
.transform(translation(1., 2., -1.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([0.8, 0.2, 0.2])
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.transparency(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y2z1 = sphere()
|
||||
.transform(translation(2., 2., -1.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([0.8, 0.2, 0.2])
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.transparency(0.9)
|
||||
.refractive_index(1.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y4z1 = sphere()
|
||||
.transform(translation(2., 4., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([0.2, 0.2, 0.8])
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let world = WorldBuilder::default()
|
||||
.lights(vec![light1, light2, light3])
|
||||
.objects(vec![
|
||||
floor, x1y1, x2y1, x3y1, x1y2, x2y2, x3y2, x1y2z1, x2y2z1, x2y4z1,
|
||||
])
|
||||
.build()?;
|
||||
|
||||
let image = camera.render(&world);
|
||||
|
||||
let path = "/tmp/eoc11.png";
|
||||
println!("saving output to {}", path);
|
||||
image.write_to_file(path)?;
|
||||
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
|
||||
Ok(())
|
||||
}
|
||||
232
rtchallenge/examples/eoc12.rs
Normal file
232
rtchallenge/examples/eoc12.rs
Normal file
@ -0,0 +1,232 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use rtchallenge::prelude::*;
|
||||
|
||||
use rtchallenge::{
|
||||
camera::RenderStrategy,
|
||||
float::consts::PI,
|
||||
patterns::{test_pattern, BLACK_PAT, WHITE_PAT},
|
||||
WHITE,
|
||||
};
|
||||
|
||||
/// End of chapter 12 challenge.
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(name = "eoc12")]
|
||||
struct Opt {
|
||||
/// Strategy for casting rays into image.
|
||||
#[structopt(long, default_value = "rayon")]
|
||||
render_strategy: RenderStrategy,
|
||||
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
|
||||
/// times randomly across the pixel.
|
||||
#[structopt(short, long, default_value = "0")]
|
||||
samples: usize,
|
||||
/// Rendered image width in pixels.
|
||||
#[structopt(short, long, default_value = "2560")]
|
||||
width: usize,
|
||||
/// Rendered image height in pixels.
|
||||
#[structopt(short, long, default_value = "1440")]
|
||||
height: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let start = Instant::now();
|
||||
let opt = Opt::from_args();
|
||||
|
||||
let light1 = PointLightBuilder::default()
|
||||
.position(point(-5., 5., -5.))
|
||||
.intensity(WHITE)
|
||||
.build()?;
|
||||
let light2 = PointLightBuilder::default()
|
||||
.position(point(5., 5., -5.))
|
||||
.intensity([0.2, 0.2, 0.6])
|
||||
.build()?;
|
||||
let light3 = PointLightBuilder::default()
|
||||
.position(point(0., 2., -5.))
|
||||
.intensity([0.2, 0.2, 0.1])
|
||||
.build()?;
|
||||
|
||||
let from = point(2., 8., -10.);
|
||||
let to = point(2., 1., -1.);
|
||||
let up = point(0., 1., 0.);
|
||||
let camera = CameraBuilder::default()
|
||||
.hsize(opt.width)
|
||||
.vsize(opt.height)
|
||||
.field_of_view(PI / 6.)
|
||||
.transform(view_transform(from, to, up))
|
||||
.render_strategy(opt.render_strategy)
|
||||
.samples_per_pixel(opt.samples)
|
||||
.build()?;
|
||||
|
||||
let floor = plane()
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
checkers_pattern(
|
||||
ring_pattern([0.8, 0.8, 0.2].into(), [0.8, 0.2, 0.8].into())
|
||||
.transform(scaling(1. / 8., 1. / 8., 1. / 8.))
|
||||
.build()?,
|
||||
ring_pattern([0.2, 0.8, 0.2].into(), [0.8, 0.2, 0.2].into())
|
||||
.transform(scaling(1. / 4., 1. / 4., 1. / 4.))
|
||||
.build()?,
|
||||
)
|
||||
.transform(translation(0., 1., 0.) * scaling(2., 2., 2.))
|
||||
.build()?,
|
||||
)
|
||||
.specular(0.)
|
||||
.reflective(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
let sphere_size = scaling(0.5, 0.5, 0.5);
|
||||
|
||||
let x1y1 = sphere()
|
||||
.transform(translation(1., 1., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
gradient_pattern([0., 0., 1.].into(), [1., 1., 0.].into())
|
||||
.transform(scaling(2., 1., 1.) * translation(-0.5, 0., 0.))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.reflective(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y1 = sphere()
|
||||
.transform(translation(2., 1., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(stripe_pattern(WHITE_PAT, BLACK_PAT).build()?)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x3y1 = sphere()
|
||||
.transform(translation(3., 1., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
stripe_pattern(WHITE_PAT, BLACK_PAT)
|
||||
.transform(scaling(0.2, 1., 1.))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.0)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x1y2 = sphere()
|
||||
.transform(translation(1., 2., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(test_pattern().build()?)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y2 = sphere()
|
||||
.transform(translation(2., 2., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
ring_pattern(WHITE_PAT, BLACK_PAT)
|
||||
.transform(scaling(0.2, 0.2, 0.2))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x3y2 = sphere()
|
||||
.transform(translation(3., 2., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color(
|
||||
checkers_pattern(WHITE_PAT, BLACK_PAT)
|
||||
.transform(scaling(0.5, 0.5, 0.5))
|
||||
.build()?,
|
||||
)
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x1y2z1 = sphere()
|
||||
.transform(translation(1., 2., -1.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([0.8, 0.2, 0.2])
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.transparency(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y2z1 = sphere()
|
||||
.transform(translation(2., 2., -1.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([0.8, 0.2, 0.2])
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.transparency(0.9)
|
||||
.refractive_index(1.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x3y2z1 = cube()
|
||||
.transform(
|
||||
translation(3., 2., -1.) * (sphere_size * scaling(0.5, 0.5, 0.5)) * rotation_y(PI / 4.),
|
||||
)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([0.8, 0.8, 0.2])
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.reflective(0.5)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let x2y4z1 = sphere()
|
||||
.transform(translation(2., 4., 0.) * sphere_size)
|
||||
.material(
|
||||
MaterialBuilder::default()
|
||||
.color([0.2, 0.2, 0.8])
|
||||
.diffuse(0.7)
|
||||
.specular(0.3)
|
||||
.build()?,
|
||||
)
|
||||
.build()?;
|
||||
|
||||
let world = WorldBuilder::default()
|
||||
.lights(vec![light1, light2, light3])
|
||||
.objects(vec![
|
||||
floor, x1y1, x2y1, x3y1, x1y2, x2y2, x3y2, x1y2z1, x2y2z1, x2y4z1, x3y2z1,
|
||||
])
|
||||
.build()?;
|
||||
|
||||
let image = camera.render(&world);
|
||||
|
||||
let path = "/tmp/eoc12.png";
|
||||
println!("saving output to {}", path);
|
||||
image.write_to_file(path)?;
|
||||
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
|
||||
Ok(())
|
||||
}
|
||||
@ -46,7 +46,7 @@ pub mod prelude {
|
||||
materials::{Material, MaterialBuilder},
|
||||
matrices::{identity, rotation_x, rotation_y, rotation_z, scaling, shearing, translation},
|
||||
patterns::{checkers_pattern, gradient_pattern, ring_pattern, stripe_pattern},
|
||||
shapes::{plane, sphere, test_shape},
|
||||
shapes::{cube, plane, sphere, test_shape},
|
||||
transformations::view_transform,
|
||||
tuples::{point, vector, Color},
|
||||
world::{World, WorldBuilder},
|
||||
|
||||
@ -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.,
|
||||
|
||||
@ -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],
|
||||
}
|
||||
|
||||
@ -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<Pattern>, b: Box<Pattern> },
|
||||
/// Linear blend between `a` and `b` along the X-axis.
|
||||
Gradient { a: Color, b: Color },
|
||||
Gradient { a: Box<Pattern>, b: Box<Pattern> },
|
||||
/// Bullseye pattern in the XZ plane.
|
||||
Ring { a: Color, b: Color },
|
||||
Ring { a: Box<Pattern>, b: Box<Pattern> },
|
||||
/// Traditional ray tracer tile floor pattern.
|
||||
Checkers { a: Color, b: Color },
|
||||
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 = "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<dyn Error>> {
|
||||
// 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);
|
||||
|
||||
@ -23,6 +23,8 @@ pub enum Geometry {
|
||||
Sphere,
|
||||
/// Flat surface that extends infinitely in the XZ axes.
|
||||
Plane,
|
||||
/// AABB cube at origin from -1,1 in each direction.
|
||||
Cube,
|
||||
}
|
||||
|
||||
impl Default for Geometry {
|
||||
@ -38,6 +40,7 @@ impl PartialEq for Geometry {
|
||||
(TestShape(l), TestShape(r)) => *l.lock().unwrap() == *r.lock().unwrap(),
|
||||
(Sphere, Sphere) => true,
|
||||
(Plane, Plane) => true,
|
||||
(Cube, Cube) => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
@ -70,6 +73,11 @@ pub fn test_shape() -> ShapeBuilder {
|
||||
ShapeBuilder::test_shape()
|
||||
}
|
||||
|
||||
/// Short hand for creating a ShapeBuilder with a cube geometry.
|
||||
pub fn cube() -> ShapeBuilder {
|
||||
ShapeBuilder::cube()
|
||||
}
|
||||
|
||||
/// Helper for producing a sphere with a glassy material.
|
||||
pub fn glass_sphere() -> ShapeBuilder {
|
||||
ShapeBuilder::sphere().material(
|
||||
@ -95,6 +103,11 @@ impl ShapeBuilder {
|
||||
TestData::default(),
|
||||
))))
|
||||
}
|
||||
/// Short hand for creating a ShapeBuilder with a cube geometry.
|
||||
pub fn cube() -> ShapeBuilder {
|
||||
ShapeBuilder::default().geometry(Geometry::Cube)
|
||||
}
|
||||
|
||||
fn default_inverse_transform(&self) -> Result<Matrix4x4, String> {
|
||||
Ok(self.transform.unwrap_or(Matrix4x4::identity()).inverse())
|
||||
}
|
||||
@ -121,7 +134,6 @@ impl Shape {
|
||||
geometry: Geometry::TestShape(Arc::new(Mutex::new(TestData::default()))),
|
||||
}
|
||||
}
|
||||
/// # Examples
|
||||
pub fn sphere() -> Shape {
|
||||
Shape {
|
||||
transform: Matrix4x4::identity(),
|
||||
@ -138,6 +150,14 @@ impl Shape {
|
||||
geometry: Geometry::Plane,
|
||||
}
|
||||
}
|
||||
pub fn cube() -> Shape {
|
||||
Shape {
|
||||
transform: Matrix4x4::identity(),
|
||||
inverse_transform: Matrix4x4::identity(),
|
||||
material: Material::default(),
|
||||
geometry: Geometry::Cube,
|
||||
}
|
||||
}
|
||||
/// Find the normal at the point on the sphere.
|
||||
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
||||
let object_point = self.inverse_transform * world_point;
|
||||
@ -145,6 +165,7 @@ impl Shape {
|
||||
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
|
||||
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
||||
Geometry::TestShape(_) => object_point,
|
||||
Geometry::Cube => cube::local_normal_at(object_point),
|
||||
};
|
||||
let mut world_normal = self.inverse_transform.transpose() * object_normal;
|
||||
world_normal.w = 0.;
|
||||
@ -175,6 +196,7 @@ pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||
Geometry::Sphere => sphere::intersect(shape, &local_ray),
|
||||
Geometry::Plane => plane::intersect(shape, &local_ray),
|
||||
Geometry::TestShape(_) => test_shape::intersect(shape, &local_ray),
|
||||
Geometry::Cube => cube::intersect(shape, &local_ray),
|
||||
}
|
||||
}
|
||||
|
||||
@ -235,6 +257,129 @@ mod plane {
|
||||
}
|
||||
}
|
||||
|
||||
mod cube {
|
||||
use crate::{
|
||||
intersections::{Intersection, Intersections},
|
||||
rays::Ray,
|
||||
shapes::Shape,
|
||||
tuples::{vector, Tuple},
|
||||
Float, EPSILON,
|
||||
};
|
||||
|
||||
fn check_axis(origin: Float, direction: Float) -> (Float, Float) {
|
||||
let tmin_numerator = -1. - origin;
|
||||
let tmax_numerator = 1. - origin;
|
||||
|
||||
let (tmin, tmax) = if direction.abs() >= EPSILON {
|
||||
(tmin_numerator / direction, tmax_numerator / direction)
|
||||
} else {
|
||||
(
|
||||
tmin_numerator * Float::INFINITY,
|
||||
tmax_numerator * Float::INFINITY,
|
||||
)
|
||||
};
|
||||
|
||||
if tmin > tmax {
|
||||
return (tmax, tmin);
|
||||
}
|
||||
(tmin, tmax)
|
||||
}
|
||||
|
||||
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||
let (xtmin, xtmax) = check_axis(ray.origin.x, ray.direction.x);
|
||||
let (ytmin, ytmax) = check_axis(ray.origin.y, ray.direction.y);
|
||||
let (ztmin, ztmax) = check_axis(ray.origin.z, ray.direction.z);
|
||||
|
||||
let tmin = xtmin.max(ytmin).max(ztmin);
|
||||
let tmax = xtmax.min(ytmax).min(ztmax);
|
||||
|
||||
if tmin > tmax {
|
||||
return Intersections::default();
|
||||
}
|
||||
Intersections::new(vec![
|
||||
Intersection::new(tmin, &shape),
|
||||
Intersection::new(tmax, &shape),
|
||||
])
|
||||
}
|
||||
pub fn local_normal_at(point: Tuple) -> Tuple {
|
||||
let x = point.x.abs();
|
||||
let y = point.y.abs();
|
||||
let z = point.z.abs();
|
||||
let maxc = x.max(y).max(z);
|
||||
if maxc == x {
|
||||
return vector(point.x, 0., 0.);
|
||||
}
|
||||
if maxc == y {
|
||||
return vector(0., point.y, 0.);
|
||||
}
|
||||
vector(0., 0., point.z)
|
||||
}
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::{
|
||||
rays::Ray,
|
||||
shapes::Shape,
|
||||
tuples::{point, vector},
|
||||
EPSILON,
|
||||
};
|
||||
|
||||
use super::{intersect, local_normal_at};
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_cube() {
|
||||
let c = Shape::cube();
|
||||
for (name, o, d, t1, t2) in [
|
||||
("+x", point(5., 0.5, 0.), vector(-1., 0., 0.), 4., 6.),
|
||||
("-x", point(-5., 0.5, 0.), vector(1., 0., 0.), 4., 6.),
|
||||
("+y", point(0.5, 5., 0.), vector(0., -1., 0.), 4., 6.),
|
||||
("-y", point(0.5, -5., 0.), vector(0., 1., 0.), 4., 6.),
|
||||
("+z", point(0.5, 0., 5.), vector(0., 0., -1.), 4., 6.),
|
||||
("-z", point(0.5, 0., -5.), vector(0., 0., 1.), 4., 6.),
|
||||
("inside", point(0., 0.5, 0.), vector(0., 0., 1.), -1., 1.),
|
||||
] {
|
||||
let r = Ray::new(o, d);
|
||||
let xs = intersect(&c, &r);
|
||||
assert_eq!(xs.len(), 2, "{}", name);
|
||||
assert!((xs[0].t - t1).abs() < EPSILON, "{} t1 {}", name, xs[0].t);
|
||||
assert!((xs[1].t - t2).abs() < EPSILON, "{} t2 {}", name, xs[1].t);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_misses_cube() {
|
||||
let c = Shape::cube();
|
||||
for (o, d) in [
|
||||
(point(-2., 0., 0.), vector(0.2673, 0.5345, 0.8018)),
|
||||
(point(0., -2., 0.), vector(0.8018, 0.2673, 0.5345)),
|
||||
(point(0., 0., -2.), vector(0.5345, 0.8018, 0.2673)),
|
||||
(point(2., 0., 2.), vector(0., 0., -1.)),
|
||||
(point(0., 2., 2.), vector(0., -1., 0.)),
|
||||
(point(2., 2., -5.), vector(-1., 0., 0.)),
|
||||
] {
|
||||
let r = Ray::new(o, d);
|
||||
let xs = intersect(&c, &r);
|
||||
assert_eq!(xs.len(), 0, "({:?}, {:?})", o, d);
|
||||
}
|
||||
}
|
||||
#[test]
|
||||
fn normal_cube_surface() {
|
||||
for (p, n) in [
|
||||
(point(1., 0.8, -0.8), vector(1., 0., 0.)),
|
||||
(point(-1., -0.2, 0.9), vector(-1., 0., 0.)),
|
||||
(point(-0.4, 1., -0.1), vector(0., 1., 0.)),
|
||||
(point(0.3, -1., -0.7), vector(0., -1., 0.)),
|
||||
(point(-0.6, 0.3, 1.), vector(0., 0., 1.)),
|
||||
(point(0.4, 0.4, -1.), vector(0., 0., -1.)),
|
||||
(point(1., 1., 1.), vector(1., 0., 0.)),
|
||||
(point(-1., -1., -1.), vector(-1., 0., 0.)),
|
||||
] {
|
||||
let normal = local_normal_at(p);
|
||||
assert_eq!(n, normal);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
mod shape_builder {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user