Compare commits
30 Commits
62ad827507
...
9befbd9ad2
| Author | SHA1 | Date | |
|---|---|---|---|
| 9befbd9ad2 | |||
| c882fc81e5 | |||
| 1c2caf2cc5 | |||
| 9006671a26 | |||
| 3838efd134 | |||
| e3d8988658 | |||
| e4846de25b | |||
| 4352c03d20 | |||
| 1d5e5a164b | |||
| f476822bcd | |||
| 135a519526 | |||
| 5d6b3e6d57 | |||
| 3aea76b35c | |||
| cd2a4770ca | |||
| 5debb16d10 | |||
| 42e8ebe3bd | |||
| 1d61f59935 | |||
| 7f36aecf5e | |||
| 0c7bbae4a3 | |||
| eaae65712b | |||
| 68709da6c2 | |||
| 77215193fa | |||
| 74fe69188a | |||
| bdcee49d5a | |||
| 2e4e8b3dcd | |||
| b9f2c3f0ec | |||
| 8b79876aee | |||
| bfa3282a37 | |||
| 3e383c4dbd | |||
| c158d92252 |
@ -8,7 +8,6 @@ edition = "2018"
|
|||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = [ "float-as-double" ]
|
default = [ "float-as-double" ]
|
||||||
disable-inverse-cache = []
|
|
||||||
float-as-double = []
|
float-as-double = []
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
|||||||
168
rtchallenge/examples/eoc10.rs
Normal file
168
rtchallenge/examples/eoc10.rs
Normal file
@ -0,0 +1,168 @@
|
|||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use anyhow::Result;
|
||||||
|
use structopt::StructOpt;
|
||||||
|
|
||||||
|
use rtchallenge::prelude::*;
|
||||||
|
|
||||||
|
use rtchallenge::{
|
||||||
|
camera::RenderStrategy, float::consts::PI, patterns::test_pattern, BLACK, WHITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// End of chapter 10 challenge.
|
||||||
|
#[derive(StructOpt, Debug)]
|
||||||
|
#[structopt(name = "eoc10")]
|
||||||
|
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., 2., -10.);
|
||||||
|
let to = point(2., 1., 0.);
|
||||||
|
let up = point(0., 1., 0.);
|
||||||
|
let camera = CameraBuilder::default()
|
||||||
|
.hsize(opt.width)
|
||||||
|
.vsize(opt.height)
|
||||||
|
.field_of_view(PI / 4.)
|
||||||
|
.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(WHITE, BLACK)
|
||||||
|
.transform(translation(1., 0., 0.) * scaling(2., 2., 2.))
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.specular(0.)
|
||||||
|
.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)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let x2y1 = sphere()
|
||||||
|
.transform(translation(2., 1., 0.) * sphere_size)
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.color(stripe_pattern(WHITE, BLACK).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, BLACK)
|
||||||
|
.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, BLACK)
|
||||||
|
.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, BLACK)
|
||||||
|
.transform(scaling(0.5, 0.5, 0.5))
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.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])
|
||||||
|
.build()?;
|
||||||
|
|
||||||
|
let image = camera.render(&world);
|
||||||
|
|
||||||
|
let path = "/tmp/eoc10.png";
|
||||||
|
println!("saving output to {}", path);
|
||||||
|
image.write_to_file(path)?;
|
||||||
|
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@ -22,7 +22,7 @@ fn main() -> Result<()> {
|
|||||||
let half = wall_size / 2.;
|
let half = wall_size / 2.;
|
||||||
let mut shape = Shape::sphere();
|
let mut shape = Shape::sphere();
|
||||||
shape.material = Material {
|
shape.material = Material {
|
||||||
color: Color::new(1., 0.2, 1.),
|
color: Color::new(1., 0.2, 1.).into(),
|
||||||
specular: 0.5,
|
specular: 0.5,
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
shininess: 30.,
|
shininess: 30.,
|
||||||
@ -44,7 +44,15 @@ fn main() -> Result<()> {
|
|||||||
let point = r.position(hit.t);
|
let point = r.position(hit.t);
|
||||||
let normal = hit.object.normal_at(point);
|
let normal = hit.object.normal_at(point);
|
||||||
let eye = -r.direction;
|
let eye = -r.direction;
|
||||||
let color = lighting(&hit.object.material, &light, point, eye, normal, in_shadow);
|
let color = lighting(
|
||||||
|
&hit.object.material,
|
||||||
|
&hit.object,
|
||||||
|
&light,
|
||||||
|
point,
|
||||||
|
eye,
|
||||||
|
normal,
|
||||||
|
in_shadow,
|
||||||
|
);
|
||||||
c.set(x, y, color);
|
c.set(x, y, color);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -42,7 +42,7 @@ fn main() -> Result<()> {
|
|||||||
let mut floor = Shape::sphere();
|
let mut floor = Shape::sphere();
|
||||||
floor.set_transform(Matrix4x4::scaling(10., 0.01, 10.));
|
floor.set_transform(Matrix4x4::scaling(10., 0.01, 10.));
|
||||||
floor.material = Material {
|
floor.material = Material {
|
||||||
color: Color::new(1., 0.9, 0.9),
|
color: Color::new(1., 0.9, 0.9).into(),
|
||||||
specular: 0.,
|
specular: 0.,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
};
|
};
|
||||||
@ -68,7 +68,7 @@ fn main() -> Result<()> {
|
|||||||
let mut middle = Shape::sphere();
|
let mut middle = Shape::sphere();
|
||||||
middle.set_transform(Matrix4x4::translation(-0.5, 1., 0.5));
|
middle.set_transform(Matrix4x4::translation(-0.5, 1., 0.5));
|
||||||
middle.material = Material {
|
middle.material = Material {
|
||||||
color: Color::new(0.1, 1., 0.5),
|
color: Color::new(0.1, 1., 0.5).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.3,
|
specular: 0.3,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
@ -77,7 +77,7 @@ fn main() -> Result<()> {
|
|||||||
let mut right = Shape::sphere();
|
let mut right = Shape::sphere();
|
||||||
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
|
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
|
||||||
right.material = Material {
|
right.material = Material {
|
||||||
color: Color::new(0.5, 1., 0.1),
|
color: Color::new(0.5, 1., 0.1).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.3,
|
specular: 0.3,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
@ -88,7 +88,7 @@ fn main() -> Result<()> {
|
|||||||
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
|
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
|
||||||
);
|
);
|
||||||
left.material = Material {
|
left.material = Material {
|
||||||
color: Color::new(1., 0.8, 0.1),
|
color: Color::new(1., 0.8, 0.1).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.3,
|
specular: 0.3,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
|
|||||||
@ -54,7 +54,7 @@ fn main() -> Result<()> {
|
|||||||
let mut floor = Shape::sphere();
|
let mut floor = Shape::sphere();
|
||||||
floor.set_transform(Matrix4x4::scaling(10., 0.01, 10.));
|
floor.set_transform(Matrix4x4::scaling(10., 0.01, 10.));
|
||||||
floor.material = Material {
|
floor.material = Material {
|
||||||
color: Color::new(1., 0.9, 0.9),
|
color: Color::new(1., 0.9, 0.9).into(),
|
||||||
specular: 0.,
|
specular: 0.,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
};
|
};
|
||||||
@ -80,7 +80,7 @@ fn main() -> Result<()> {
|
|||||||
let mut middle = Shape::sphere();
|
let mut middle = Shape::sphere();
|
||||||
middle.set_transform(Matrix4x4::translation(-0.5, 1., 0.5));
|
middle.set_transform(Matrix4x4::translation(-0.5, 1., 0.5));
|
||||||
middle.material = Material {
|
middle.material = Material {
|
||||||
color: Color::new(0.1, 1., 0.5),
|
color: Color::new(0.1, 1., 0.5).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.3,
|
specular: 0.3,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
@ -89,7 +89,7 @@ fn main() -> Result<()> {
|
|||||||
let mut right = Shape::sphere();
|
let mut right = Shape::sphere();
|
||||||
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
|
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
|
||||||
right.material = Material {
|
right.material = Material {
|
||||||
color: Color::new(1., 1., 1.),
|
color: Color::new(1., 1., 1.).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.0,
|
specular: 0.0,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
@ -100,7 +100,7 @@ fn main() -> Result<()> {
|
|||||||
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
|
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
|
||||||
);
|
);
|
||||||
left.material = Material {
|
left.material = Material {
|
||||||
color: Color::new(1., 0.8, 0.1),
|
color: Color::new(1., 0.8, 0.1).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.3,
|
specular: 0.3,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
|
|||||||
@ -11,19 +11,24 @@ use rtchallenge::{camera::RenderStrategy, float::consts::PI, WHITE};
|
|||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
#[structopt(name = "eoc9")]
|
#[structopt(name = "eoc9")]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
|
/// Strategy for casting rays into image.
|
||||||
#[structopt(long, default_value = "rayon")]
|
#[structopt(long, default_value = "rayon")]
|
||||||
render_strategy: RenderStrategy,
|
render_strategy: RenderStrategy,
|
||||||
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
|
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
|
||||||
/// times randomly across the pixel.
|
/// times randomly across the pixel.
|
||||||
#[structopt(short, long, default_value = "0")]
|
#[structopt(short, long, default_value = "0")]
|
||||||
samples: usize,
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
let width = 2560;
|
|
||||||
let height = 1440;
|
|
||||||
|
|
||||||
let light1 = PointLightBuilder::default()
|
let light1 = PointLightBuilder::default()
|
||||||
.position(point(-5., 5., -5.))
|
.position(point(-5., 5., -5.))
|
||||||
@ -42,8 +47,8 @@ fn main() -> Result<()> {
|
|||||||
let to = point(0., 1., 0.);
|
let to = point(0., 1., 0.);
|
||||||
let up = point(0., 1., 0.);
|
let up = point(0., 1., 0.);
|
||||||
let camera = CameraBuilder::default()
|
let camera = CameraBuilder::default()
|
||||||
.hsize(width)
|
.hsize(opt.width)
|
||||||
.vsize(height)
|
.vsize(opt.height)
|
||||||
.field_of_view(PI / 4.)
|
.field_of_view(PI / 4.)
|
||||||
.transform(view_transform(from, to, up))
|
.transform(view_transform(from, to, up))
|
||||||
.render_strategy(opt.render_strategy)
|
.render_strategy(opt.render_strategy)
|
||||||
|
|||||||
@ -20,19 +20,24 @@ use rtchallenge::{
|
|||||||
#[derive(StructOpt, Debug)]
|
#[derive(StructOpt, Debug)]
|
||||||
#[structopt(name = "eoc9")]
|
#[structopt(name = "eoc9")]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
|
/// Strategy for casting rays into image.
|
||||||
#[structopt(long, default_value = "rayon")]
|
#[structopt(long, default_value = "rayon")]
|
||||||
render_strategy: RenderStrategy,
|
render_strategy: RenderStrategy,
|
||||||
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
|
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
|
||||||
/// times randomly across the pixel.
|
/// times randomly across the pixel.
|
||||||
#[structopt(short, long, default_value = "0")]
|
#[structopt(short, long, default_value = "0")]
|
||||||
samples: usize,
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
let start = Instant::now();
|
let start = Instant::now();
|
||||||
let opt = Opt::from_args();
|
let opt = Opt::from_args();
|
||||||
let width = 2560;
|
|
||||||
let height = 1440;
|
|
||||||
|
|
||||||
let light_position = Tuple::point(-5., 5., -5.);
|
let light_position = Tuple::point(-5., 5., -5.);
|
||||||
let light_color = WHITE;
|
let light_color = WHITE;
|
||||||
@ -44,7 +49,7 @@ fn main() -> Result<()> {
|
|||||||
let light_color = Color::new(0.2, 0.2, 0.1);
|
let light_color = Color::new(0.2, 0.2, 0.1);
|
||||||
let light3 = PointLight::new(light_position, light_color);
|
let light3 = PointLight::new(light_position, light_color);
|
||||||
|
|
||||||
let mut camera = Camera::new(width, height, PI / 4.);
|
let mut camera = Camera::new(opt.width, opt.height, PI / 4.);
|
||||||
let from = Tuple::point(0., 1.5, -5.);
|
let from = Tuple::point(0., 1.5, -5.);
|
||||||
let to = Tuple::point(0., 1., 0.);
|
let to = Tuple::point(0., 1., 0.);
|
||||||
let up = Tuple::point(0., 1., 0.);
|
let up = Tuple::point(0., 1., 0.);
|
||||||
@ -54,14 +59,14 @@ fn main() -> Result<()> {
|
|||||||
|
|
||||||
let mut floor = Shape::plane();
|
let mut floor = Shape::plane();
|
||||||
floor.material = Material {
|
floor.material = Material {
|
||||||
color: Color::new(1., 0.2, 0.2),
|
color: Color::new(1., 0.2, 0.2).into(),
|
||||||
specular: 0.,
|
specular: 0.,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
};
|
};
|
||||||
let mut ceiling = Shape::plane();
|
let mut ceiling = Shape::plane();
|
||||||
ceiling.set_transform(Matrix4x4::translation(0., 6., 0.) * Matrix4x4::rotation_x(PI));
|
ceiling.set_transform(Matrix4x4::translation(0., 6., 0.) * Matrix4x4::rotation_x(PI));
|
||||||
ceiling.material = Material {
|
ceiling.material = Material {
|
||||||
color: Color::new(0.6, 0.6, 0.8),
|
color: Color::new(0.6, 0.6, 0.8).into(),
|
||||||
specular: 0.2,
|
specular: 0.2,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
};
|
};
|
||||||
@ -69,7 +74,7 @@ fn main() -> Result<()> {
|
|||||||
let mut middle = Shape::sphere();
|
let mut middle = Shape::sphere();
|
||||||
middle.set_transform(Matrix4x4::translation(-0.5, 0.5, 0.5));
|
middle.set_transform(Matrix4x4::translation(-0.5, 0.5, 0.5));
|
||||||
middle.material = Material {
|
middle.material = Material {
|
||||||
color: Color::new(0.1, 1., 0.5),
|
color: Color::new(0.1, 1., 0.5).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.3,
|
specular: 0.3,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
@ -78,7 +83,7 @@ fn main() -> Result<()> {
|
|||||||
let mut right = Shape::sphere();
|
let mut right = Shape::sphere();
|
||||||
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
|
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
|
||||||
right.material = Material {
|
right.material = Material {
|
||||||
color: Color::new(1., 1., 1.),
|
color: Color::new(1., 1., 1.).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.0,
|
specular: 0.0,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
@ -89,7 +94,7 @@ fn main() -> Result<()> {
|
|||||||
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
|
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
|
||||||
);
|
);
|
||||||
left.material = Material {
|
left.material = Material {
|
||||||
color: Color::new(1., 0.8, 0.1),
|
color: Color::new(1., 0.8, 0.1).into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.3,
|
specular: 0.3,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
|
|||||||
@ -22,6 +22,8 @@ use crate::{
|
|||||||
Float, BLACK,
|
Float, BLACK,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MAX_DEPTH_RECURSION: usize = 5;
|
||||||
|
|
||||||
#[derive(Copy, Clone, StructOpt, Debug, Deserialize)]
|
#[derive(Copy, Clone, StructOpt, Debug, Deserialize)]
|
||||||
#[serde(rename_all = "kebab-case")]
|
#[serde(rename_all = "kebab-case")]
|
||||||
pub enum RenderStrategy {
|
pub enum RenderStrategy {
|
||||||
@ -116,27 +118,6 @@ enum Response {
|
|||||||
impl Camera {
|
impl Camera {
|
||||||
/// Create a camera with a canvas of pixel hsize (height) and vsize (width)
|
/// Create a camera with a canvas of pixel hsize (height) and vsize (width)
|
||||||
/// with the given field of view (in radians).
|
/// with the given field of view (in radians).
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{camera::Camera, float::consts::PI, matrices::Matrix4x4, EPSILON};
|
|
||||||
///
|
|
||||||
/// let hsize = 160;
|
|
||||||
/// let vsize = 120;
|
|
||||||
/// let field_of_view = PI / 2.;
|
|
||||||
/// let c = Camera::new(hsize, vsize, field_of_view);
|
|
||||||
/// assert_eq!(c.hsize(), 160);
|
|
||||||
/// assert_eq!(c.vsize(), 120);
|
|
||||||
/// assert_eq!(c.transform(), Matrix4x4::identity());
|
|
||||||
///
|
|
||||||
/// // Pixel size for a horizontal canvas.
|
|
||||||
/// let c = Camera::new(200, 150, PI / 2.);
|
|
||||||
/// assert!((c.pixel_size() - 0.010).abs() < EPSILON);
|
|
||||||
///
|
|
||||||
/// // Pixel size for a horizontal canvas.
|
|
||||||
/// let c = Camera::new(150, 200, PI / 2.);
|
|
||||||
/// assert!((c.pixel_size() - 0.010).abs() < EPSILON);
|
|
||||||
/// ```
|
|
||||||
pub fn new(hsize: usize, vsize: usize, field_of_view: Float) -> Camera {
|
pub fn new(hsize: usize, vsize: usize, field_of_view: Float) -> Camera {
|
||||||
let half_view = (field_of_view / 2.).tan();
|
let half_view = (field_of_view / 2.).tan();
|
||||||
let aspect = hsize as Float / vsize as Float;
|
let aspect = hsize as Float / vsize as Float;
|
||||||
@ -206,47 +187,17 @@ impl Camera {
|
|||||||
|
|
||||||
/// Calculate ray that starts at the camera and passes through the (x,y)
|
/// Calculate ray that starts at the camera and passes through the (x,y)
|
||||||
/// pixel on the canvas.
|
/// pixel on the canvas.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// camera::Camera, float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Constructing a ray through the center of the canvas.
|
|
||||||
/// let c = Camera::new(201, 101, PI / 2.);
|
|
||||||
/// let r = c.ray_for_pixel(100, 50);
|
|
||||||
/// assert_eq!(r.origin, Tuple::point(0., 0., 0.));
|
|
||||||
/// assert_eq!(r.direction, Tuple::vector(0., 0., -1.));
|
|
||||||
///
|
|
||||||
/// // Constructing a ray through the corner of the canvas.
|
|
||||||
/// let c = Camera::new(201, 101, PI / 2.);
|
|
||||||
/// let r = c.ray_for_pixel(0, 0);
|
|
||||||
/// assert_eq!(r.origin, Tuple::point(0., 0., 0.));
|
|
||||||
/// assert_eq!(r.direction, Tuple::vector(0.66519, 0.33259, -0.66851));
|
|
||||||
///
|
|
||||||
/// // Constructing a ray when the camera is transformed.
|
|
||||||
/// let mut c = Camera::new(201, 101, PI / 2.);
|
|
||||||
/// c.set_transform(Matrix4x4::rotation_y(PI / 4.) * Matrix4x4::translation(0., -2., 5.));
|
|
||||||
/// let r = c.ray_for_pixel(100, 50);
|
|
||||||
/// assert_eq!(r.origin, Tuple::point(0., 2., -5.));
|
|
||||||
/// assert_eq!(
|
|
||||||
/// r.direction,
|
|
||||||
/// Tuple::vector((2. as Float).sqrt() / 2., 0., -(2. as Float).sqrt() / 2.)
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
#[cfg(not(feature = "disable-inverse-cache"))]
|
|
||||||
pub fn ray_for_pixel(&self, px: usize, py: usize) -> Ray {
|
pub fn ray_for_pixel(&self, px: usize, py: usize) -> Ray {
|
||||||
// The offset from the edge of the canvas to the pixel's corner.
|
// The offset from the edge of the canvas to the pixel's corner.
|
||||||
let xoffset = (px as Float + 0.5) * self.pixel_size;
|
let xoffset = (px as Float + 0.5) * self.pixel_size;
|
||||||
let yoffset = (py as Float + 0.5) * self.pixel_size;
|
let yoffset = (py as Float + 0.5) * self.pixel_size;
|
||||||
|
|
||||||
// The untransformed coordinates of the pixle in world space.
|
// The untransformed coordinates of the pixel in world space.
|
||||||
// (Remember that the camera looks toward -z, so +x is to the left.)
|
// (Remember that the camera looks toward -z, so +x is to the left.)
|
||||||
let world_x = self.half_width - xoffset;
|
let world_x = self.half_width - xoffset;
|
||||||
let world_y = self.half_height - yoffset;
|
let world_y = self.half_height - yoffset;
|
||||||
|
|
||||||
// Using the camera matrix, transofmrm the canvas point and the origin,
|
// Using the camera matrix, transform the canvas point and the origin,
|
||||||
// and then compute the ray's direction vector.
|
// and then compute the ray's direction vector.
|
||||||
// (Remember that the canvas is at z>=-1).
|
// (Remember that the canvas is at z>=-1).
|
||||||
let pixel = self.inverse_transform * Tuple::point(world_x, world_y, -1.);
|
let pixel = self.inverse_transform * Tuple::point(world_x, world_y, -1.);
|
||||||
@ -255,48 +206,8 @@ impl Camera {
|
|||||||
|
|
||||||
Ray::new(origin, direction)
|
Ray::new(origin, direction)
|
||||||
}
|
}
|
||||||
#[cfg(feature = "disable-inverse-cache")]
|
|
||||||
pub fn ray_for_pixel(&self, px: usize, py: usize) -> Ray {
|
|
||||||
// The offset from the edge of the canvas to the pixel's corner.
|
|
||||||
let xoffset = (px as Float + 0.5) * self.pixel_size;
|
|
||||||
let yoffset = (py as Float + 0.5) * self.pixel_size;
|
|
||||||
|
|
||||||
// The untransformed coordinates of the pixle in world space.
|
|
||||||
// (Remember that the camera looks toward -z, so +x is to the left.)
|
|
||||||
let world_x = self.half_width - xoffset;
|
|
||||||
let world_y = self.half_height - yoffset;
|
|
||||||
|
|
||||||
// Using the camera matrix, transofmrm the canvas point and the origin,
|
|
||||||
// and then compute the ray's direction vector.
|
|
||||||
// (Remember that the canvas is at z>=-1).
|
|
||||||
let pixel = self.transform.inverse() * Tuple::point(world_x, world_y, -1.);
|
|
||||||
let origin = self.transform.inverse() * Tuple::point(0., 0., 0.);
|
|
||||||
let direction = (pixel - origin).normalize();
|
|
||||||
|
|
||||||
Ray::new(origin, direction)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Use camera to render an image of the given world.
|
/// Use camera to render an image of the given world.
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// camera::Camera,
|
|
||||||
/// float::consts::PI,
|
|
||||||
/// transformations::view_transform,
|
|
||||||
/// tuples::{Color, Tuple},
|
|
||||||
/// world::World,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Rendering a world with a camera.
|
|
||||||
/// let w = World::test_world();
|
|
||||||
/// let mut c = Camera::new(11, 11, PI / 2.);
|
|
||||||
/// let from = Tuple::point(0., 0., -5.);
|
|
||||||
/// let to = Tuple::point(0., 0., 0.);
|
|
||||||
/// let up = Tuple::vector(0., 1., 0.);
|
|
||||||
/// c.set_transform(view_transform(from, to, up));
|
|
||||||
/// let image = c.render(&w);
|
|
||||||
/// assert_eq!(image.get(5, 5), Color::new(0.38066, 0.47583, 0.2855));
|
|
||||||
/// ```
|
|
||||||
pub fn render(&self, w: &World) -> Canvas {
|
pub fn render(&self, w: &World) -> Canvas {
|
||||||
use RenderStrategy::*;
|
use RenderStrategy::*;
|
||||||
|
|
||||||
@ -416,12 +327,12 @@ impl Camera {
|
|||||||
let color = self
|
let color = self
|
||||||
.supersample_rays_for_pixel(x, y, self.samples_per_pixel)
|
.supersample_rays_for_pixel(x, y, self.samples_per_pixel)
|
||||||
.iter()
|
.iter()
|
||||||
.map(|ray| w.color_at(&ray))
|
.map(|ray| w.color_at(&ray, MAX_DEPTH_RECURSION))
|
||||||
.fold(BLACK, |acc, c| acc + c);
|
.fold(BLACK, |acc, c| acc + c);
|
||||||
color / self.samples_per_pixel as Float
|
color / self.samples_per_pixel as Float
|
||||||
} else {
|
} else {
|
||||||
let ray = self.ray_for_pixel(x, y);
|
let ray = self.ray_for_pixel(x, y);
|
||||||
w.color_at(&ray)
|
w.color_at(&ray, MAX_DEPTH_RECURSION)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -456,3 +367,70 @@ fn render_worker_task(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
camera::Camera,
|
||||||
|
float::consts::PI,
|
||||||
|
matrices::Matrix4x4,
|
||||||
|
transformations::view_transform,
|
||||||
|
tuples::{point, vector},
|
||||||
|
world::World,
|
||||||
|
Float, EPSILON,
|
||||||
|
};
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
let hsize = 160;
|
||||||
|
let vsize = 120;
|
||||||
|
let field_of_view = PI / 2.;
|
||||||
|
let c = Camera::new(hsize, vsize, field_of_view);
|
||||||
|
assert_eq!(c.hsize(), 160);
|
||||||
|
assert_eq!(c.vsize(), 120);
|
||||||
|
assert_eq!(c.transform(), Matrix4x4::identity());
|
||||||
|
|
||||||
|
// Pixel size for a horizontal canvas.
|
||||||
|
let c = Camera::new(200, 150, PI / 2.);
|
||||||
|
assert!((c.pixel_size() - 0.010).abs() < EPSILON);
|
||||||
|
|
||||||
|
// Pixel size for a horizontal canvas.
|
||||||
|
let c = Camera::new(150, 200, PI / 2.);
|
||||||
|
assert!((c.pixel_size() - 0.010).abs() < EPSILON);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_for_pixel() {
|
||||||
|
// Constructing a ray through the center of the canvas.
|
||||||
|
let c = Camera::new(201, 101, PI / 2.);
|
||||||
|
let r = c.ray_for_pixel(100, 50);
|
||||||
|
assert_eq!(r.origin, point(0., 0., 0.));
|
||||||
|
assert_eq!(r.direction, vector(0., 0., -1.));
|
||||||
|
|
||||||
|
// Constructing a ray through the corner of the canvas.
|
||||||
|
let c = Camera::new(201, 101, PI / 2.);
|
||||||
|
let r = c.ray_for_pixel(0, 0);
|
||||||
|
assert_eq!(r.origin, point(0., 0., 0.));
|
||||||
|
assert_eq!(r.direction, vector(0.66519, 0.33259, -0.66851));
|
||||||
|
|
||||||
|
// Constructing a ray when the camera is transformed.
|
||||||
|
let mut c = Camera::new(201, 101, PI / 2.);
|
||||||
|
c.set_transform(Matrix4x4::rotation_y(PI / 4.) * Matrix4x4::translation(0., -2., 5.));
|
||||||
|
let r = c.ray_for_pixel(100, 50);
|
||||||
|
assert_eq!(r.origin, point(0., 2., -5.));
|
||||||
|
assert_eq!(
|
||||||
|
r.direction,
|
||||||
|
vector((2. as Float).sqrt() / 2., 0., -(2. as Float).sqrt() / 2.)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn render() {
|
||||||
|
// Rendering a world with a camera.
|
||||||
|
let w = World::test_world();
|
||||||
|
let mut c = Camera::new(11, 11, PI / 2.);
|
||||||
|
let from = point(0., 0., -5.);
|
||||||
|
let to = point(0., 0., 0.);
|
||||||
|
let up = vector(0., 1., 0.);
|
||||||
|
c.set_transform(view_transform(from, to, up));
|
||||||
|
let image = c.render(&w);
|
||||||
|
assert_eq!(image.get(5, 5), [0.38066, 0.47583, 0.2855].into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,7 @@ use std::ops::Index;
|
|||||||
use crate::{
|
use crate::{
|
||||||
rays::Ray,
|
rays::Ray,
|
||||||
shapes::Shape,
|
shapes::Shape,
|
||||||
tuples::{dot, Tuple},
|
tuples::{dot, reflect, Tuple},
|
||||||
Float, EPSILON,
|
Float, EPSILON,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -20,50 +20,22 @@ impl<'i> PartialEq for Intersection<'i> {
|
|||||||
|
|
||||||
impl<'i> Intersection<'i> {
|
impl<'i> Intersection<'i> {
|
||||||
/// Create new `Intersection` at the given `t` that hits the given `object`.
|
/// Create new `Intersection` at the given `t` that hits the given `object`.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{intersections::Intersection, shapes::Shape};
|
|
||||||
///
|
|
||||||
/// // An intersection ecapsulates t and object.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let i = Intersection::new(3.5, &s);
|
|
||||||
/// assert_eq!(i.t, 3.5);
|
|
||||||
/// assert_eq!(i.object, &s);
|
|
||||||
/// ```
|
|
||||||
pub fn new(t: Float, object: &Shape) -> Intersection {
|
pub fn new(t: Float, object: &Shape) -> Intersection {
|
||||||
Intersection { t, object }
|
Intersection { t, object }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Aggregates `Intersection`s.
|
/// Aggregates `Intersection`s.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// intersections::{Intersection, Intersections},
|
|
||||||
/// rays::Ray,
|
|
||||||
/// shapes::{intersect, Shape},
|
|
||||||
/// tuples::Tuple,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let i1 = Intersection::new(1., &s);
|
|
||||||
/// let i2 = Intersection::new(2., &s);
|
|
||||||
/// let xs = Intersections::new(vec![i1, i2]);
|
|
||||||
/// assert_eq!(xs.len(), 2);
|
|
||||||
/// assert_eq!(xs[0].t, 1.);
|
|
||||||
/// assert_eq!(xs[1].t, 2.);
|
|
||||||
///
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// assert_eq!(xs.len(), 2);
|
|
||||||
/// assert_eq!(xs[0].object, &s);
|
|
||||||
/// assert_eq!(xs[1].object, &s);
|
|
||||||
/// ```
|
|
||||||
#[derive(Debug, Default, PartialEq)]
|
#[derive(Debug, Default, PartialEq)]
|
||||||
pub struct Intersections<'i>(Vec<Intersection<'i>>);
|
pub struct Intersections<'i>(Vec<Intersection<'i>>);
|
||||||
|
|
||||||
|
/// Create an [Intersections] from a single [Intersection] to aid in tests.
|
||||||
|
impl<'i> From<Intersection<'i>> for Intersections<'i> {
|
||||||
|
fn from(i: Intersection<'i>) -> Intersections<'i> {
|
||||||
|
Intersections::new(vec![i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<'i> Intersections<'i> {
|
impl<'i> Intersections<'i> {
|
||||||
pub fn new(xs: Vec<Intersection<'i>>) -> Intersections {
|
pub fn new(xs: Vec<Intersection<'i>>) -> Intersections {
|
||||||
Intersections(xs)
|
Intersections(xs)
|
||||||
@ -72,50 +44,6 @@ impl<'i> Intersections<'i> {
|
|||||||
self.0.len()
|
self.0.len()
|
||||||
}
|
}
|
||||||
/// Finds nearest hit for this collection of intersections.
|
/// Finds nearest hit for this collection of intersections.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// intersections::{Intersection, Intersections},
|
|
||||||
/// rays::Ray,
|
|
||||||
/// shapes::{intersect, Shape},
|
|
||||||
/// tuples::Tuple,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // The hit, when all intersections have positive t.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let i1 = Intersection::new(1., &s);
|
|
||||||
/// let i2 = Intersection::new(2., &s);
|
|
||||||
/// let xs = Intersections::new(vec![i2, i1.clone()]);
|
|
||||||
/// let i = xs.hit();
|
|
||||||
/// assert_eq!(i, Some(&i1));
|
|
||||||
///
|
|
||||||
/// // The hit, when some intersections have negative t.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let i1 = Intersection::new(-1., &s);
|
|
||||||
/// let i2 = Intersection::new(1., &s);
|
|
||||||
/// let xs = Intersections::new(vec![i2.clone(), i1]);
|
|
||||||
/// let i = xs.hit();
|
|
||||||
/// assert_eq!(i, Some(&i2));
|
|
||||||
///
|
|
||||||
/// // The hit, when all intersections have negative t.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let i1 = Intersection::new(-2., &s);
|
|
||||||
/// let i2 = Intersection::new(-1., &s);
|
|
||||||
/// let xs = Intersections::new(vec![i2, i1]);
|
|
||||||
/// let i = xs.hit();
|
|
||||||
/// assert_eq!(i, None);
|
|
||||||
///
|
|
||||||
/// // The hit is always the lowest nonnegative intersection.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let i1 = Intersection::new(5., &s);
|
|
||||||
/// let i2 = Intersection::new(7., &s);
|
|
||||||
/// let i3 = Intersection::new(-3., &s);
|
|
||||||
/// let i4 = Intersection::new(2., &s);
|
|
||||||
/// let xs = Intersections::new(vec![i1, i2, i3, i4.clone()]);
|
|
||||||
/// let i = xs.hit();
|
|
||||||
/// assert_eq!(i, Some(&i4));
|
|
||||||
/// ```
|
|
||||||
pub fn hit(&self) -> Option<&Intersection> {
|
pub fn hit(&self) -> Option<&Intersection> {
|
||||||
self.0.iter().filter(|i| i.t > 0.).min_by(|i1, i2| {
|
self.0.iter().filter(|i| i.t > 0.).min_by(|i1, i2| {
|
||||||
i1.t.partial_cmp(&i2.t)
|
i1.t.partial_cmp(&i2.t)
|
||||||
@ -145,81 +73,358 @@ pub struct PrecomputedData<'i> {
|
|||||||
pub t: Float,
|
pub t: Float,
|
||||||
pub object: &'i Shape,
|
pub object: &'i Shape,
|
||||||
pub point: Tuple,
|
pub point: Tuple,
|
||||||
|
pub under_point: Tuple,
|
||||||
pub over_point: Tuple,
|
pub over_point: Tuple,
|
||||||
pub eyev: Tuple,
|
pub eyev: Tuple,
|
||||||
pub normalv: Tuple,
|
pub normalv: Tuple,
|
||||||
|
pub reflectv: Tuple,
|
||||||
pub inside: bool,
|
pub inside: bool,
|
||||||
|
pub n1: Float,
|
||||||
|
pub n2: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Precomputes data common to all intersections.
|
/// Precomputes data common to all intersections.
|
||||||
///
|
pub fn prepare_computations<'i>(
|
||||||
/// # Examples
|
hit: &'i Intersection,
|
||||||
/// ```
|
r: &Ray,
|
||||||
/// use rtchallenge::{
|
xs: &Intersections,
|
||||||
/// intersections::{prepare_computations, Intersection, Intersections},
|
) -> PrecomputedData<'i> {
|
||||||
/// rays::Ray,
|
let point = r.position(hit.t);
|
||||||
/// matrices::Matrix4x4,
|
let normalv = hit.object.normal_at(point);
|
||||||
/// shapes::{intersect, Shape},
|
|
||||||
/// tuples::Tuple,
|
|
||||||
/// EPSILON
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Precomputing the state of an intersection.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let shape = Shape::sphere();
|
|
||||||
/// let i = Intersection::new(4., &shape);
|
|
||||||
/// let comps = prepare_computations(&i, &r);
|
|
||||||
/// assert_eq!(comps.t, i.t);
|
|
||||||
/// assert_eq!(comps.object, i.object);
|
|
||||||
/// assert_eq!(comps.point, Tuple::point(0., 0., -1.));
|
|
||||||
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
|
|
||||||
/// assert_eq!(comps.normalv, Tuple::vector(0., 0., -1.));
|
|
||||||
///
|
|
||||||
/// // The hit, when an intersection occurs on the outside.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let shape = Shape::sphere();
|
|
||||||
/// let i = Intersection::new(4., &shape);
|
|
||||||
/// let comps = prepare_computations(&i, &r);
|
|
||||||
/// assert_eq!(comps.inside, false);
|
|
||||||
///
|
|
||||||
/// // The hit, when an intersection occurs on the inside.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let shape = Shape::sphere();
|
|
||||||
/// let i = Intersection::new(1., &shape);
|
|
||||||
/// let comps = prepare_computations(&i, &r);
|
|
||||||
/// assert_eq!(comps.point, Tuple::point(0., 0., 1.));
|
|
||||||
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
|
|
||||||
/// assert_eq!(comps.inside, true);
|
|
||||||
//// // Normal would have been (0, 0, 1), but is inverted when inside.
|
|
||||||
/// assert_eq!(comps.normalv, Tuple::vector(0., 0., -1.));
|
|
||||||
///
|
|
||||||
/// // The hit should offset the point.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let mut shape = Shape::sphere();
|
|
||||||
/// shape .set_transform(Matrix4x4::translation(0.,0.,1.));
|
|
||||||
/// let i = Intersection::new(5., &shape);
|
|
||||||
/// let comps = prepare_computations(&i, &r);
|
|
||||||
/// assert!(comps.over_point.z< -EPSILON/2.);
|
|
||||||
/// assert!(comps.point.z>comps.over_point.z);
|
|
||||||
/// ```
|
|
||||||
pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData<'i> {
|
|
||||||
let point = r.position(i.t);
|
|
||||||
let normalv = i.object.normal_at(point);
|
|
||||||
let eyev = -r.direction;
|
let eyev = -r.direction;
|
||||||
let (inside, normalv) = if dot(normalv, eyev) < 0. {
|
let (inside, normalv) = if dot(normalv, eyev) < 0. {
|
||||||
(true, -normalv)
|
(true, -normalv)
|
||||||
} else {
|
} else {
|
||||||
(false, normalv)
|
(false, normalv)
|
||||||
};
|
};
|
||||||
|
let reflectv = reflect(r.direction, normalv);
|
||||||
|
|
||||||
|
let mut n1 = -1.;
|
||||||
|
let mut n2 = -1.;
|
||||||
|
let mut containers: Vec<&Shape> = Vec::new();
|
||||||
|
for i in xs.0.iter() {
|
||||||
|
if hit == i {
|
||||||
|
if containers.is_empty() {
|
||||||
|
n1 = 1.;
|
||||||
|
} else {
|
||||||
|
n1 = containers.last().unwrap().material.refractive_index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
match containers.iter().position(|o| o == &i.object) {
|
||||||
|
Some(idx) => {
|
||||||
|
containers.remove(idx);
|
||||||
|
}
|
||||||
|
None => containers.push(i.object),
|
||||||
|
}
|
||||||
|
|
||||||
|
if hit == i {
|
||||||
|
if containers.is_empty() {
|
||||||
|
n2 = 1.;
|
||||||
|
} else {
|
||||||
|
n2 = containers.last().unwrap().material.refractive_index;
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
let over_point = point + normalv * EPSILON;
|
let over_point = point + normalv * EPSILON;
|
||||||
|
let under_point = point - normalv * EPSILON;
|
||||||
PrecomputedData {
|
PrecomputedData {
|
||||||
t: i.t,
|
t: hit.t,
|
||||||
object: i.object,
|
object: hit.object,
|
||||||
point,
|
point,
|
||||||
over_point,
|
over_point,
|
||||||
|
under_point,
|
||||||
normalv,
|
normalv,
|
||||||
|
reflectv,
|
||||||
inside,
|
inside,
|
||||||
eyev,
|
eyev,
|
||||||
|
n1,
|
||||||
|
n2,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute Schlick approximation to Fresnel's equations.
|
||||||
|
pub fn schlick(comps: &PrecomputedData) -> Float {
|
||||||
|
// Find the cosine of the angle between the eye and normal vectors.
|
||||||
|
let mut cos = dot(comps.eyev, comps.normalv);
|
||||||
|
// Total internal reflection can only occur if n1 > n2.
|
||||||
|
if comps.n1 > comps.n2 {
|
||||||
|
let n = comps.n1 / comps.n2;
|
||||||
|
let sin2_t = n * n * (1. - cos * cos);
|
||||||
|
if sin2_t > 1. {
|
||||||
|
return 1.;
|
||||||
|
}
|
||||||
|
// Compute cosine of theta_t using trig identity.
|
||||||
|
let cos_t = (1. - sin2_t).sqrt();
|
||||||
|
// When n1 > n2 use cos(theta_t) instead.
|
||||||
|
cos = cos_t;
|
||||||
|
}
|
||||||
|
|
||||||
|
let r0 = (comps.n1 - comps.n2) / (comps.n1 + comps.n2);
|
||||||
|
let r0 = r0 * r0;
|
||||||
|
r0 + (1. - r0) * (1. - cos) * (1. - cos) * (1. - cos) * (1. - cos) * (1. - cos)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
intersections::{prepare_computations, schlick, Intersection, Intersections},
|
||||||
|
materials::MaterialBuilder,
|
||||||
|
matrices::{scaling, translation},
|
||||||
|
rays::Ray,
|
||||||
|
shapes::{glass_sphere, intersect, Shape},
|
||||||
|
tuples::{point, vector},
|
||||||
|
Float, EPSILON,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersection() {
|
||||||
|
// An intersection ecapsulates t and object.
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let i = Intersection::new(3.5, &s);
|
||||||
|
assert_eq!(i.t, 3.5);
|
||||||
|
assert_eq!(i.object, &s);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn intersections() {
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let i1 = Intersection::new(1., &s);
|
||||||
|
let i2 = Intersection::new(2., &s);
|
||||||
|
let xs = Intersections::new(vec![i1, i2]);
|
||||||
|
assert_eq!(xs.len(), 2);
|
||||||
|
assert_eq!(xs[0].t, 1.);
|
||||||
|
assert_eq!(xs[1].t, 2.);
|
||||||
|
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let xs = intersect(&s, &r);
|
||||||
|
assert_eq!(xs.len(), 2);
|
||||||
|
assert_eq!(xs[0].object, &s);
|
||||||
|
assert_eq!(xs[1].object, &s);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod hit {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn when_all_intersections_have_positive_t() {
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let i1 = Intersection::new(1., &s);
|
||||||
|
let i2 = Intersection::new(2., &s);
|
||||||
|
let xs = Intersections::new(vec![i2, i1.clone()]);
|
||||||
|
let i = xs.hit();
|
||||||
|
assert_eq!(i, Some(&i1));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn when_some_intersections_have_negative_t() {
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let i1 = Intersection::new(-1., &s);
|
||||||
|
let i2 = Intersection::new(1., &s);
|
||||||
|
let xs = Intersections::new(vec![i2.clone(), i1]);
|
||||||
|
let i = xs.hit();
|
||||||
|
assert_eq!(i, Some(&i2));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn when_all_intersections_have_negative_t() {
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let i1 = Intersection::new(-2., &s);
|
||||||
|
let i2 = Intersection::new(-1., &s);
|
||||||
|
let xs = Intersections::new(vec![i2, i1]);
|
||||||
|
let i = xs.hit();
|
||||||
|
assert_eq!(i, None);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn always_the_lowest_nonnegative_intersection() {
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let i1 = Intersection::new(5., &s);
|
||||||
|
let i2 = Intersection::new(7., &s);
|
||||||
|
let i3 = Intersection::new(-3., &s);
|
||||||
|
let i4 = Intersection::new(2., &s);
|
||||||
|
let xs = Intersections::new(vec![i1, i2, i3, i4.clone()]);
|
||||||
|
let i = xs.hit();
|
||||||
|
assert_eq!(i, Some(&i4));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod prepare_computations {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn precomputing_the_state_of_intersection() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let shape = Shape::sphere();
|
||||||
|
let xs = Intersections::from(Intersection::new(4., &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
assert_eq!(comps.t, xs[0].t);
|
||||||
|
assert_eq!(comps.object, xs[0].object);
|
||||||
|
assert_eq!(comps.point, point(0., 0., -1.));
|
||||||
|
assert_eq!(comps.eyev, vector(0., 0., -1.));
|
||||||
|
assert_eq!(comps.normalv, vector(0., 0., -1.));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hit_when_intersection_occurs_outside() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The hit, when an intersection occurs on the outside.
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let shape = Shape::sphere();
|
||||||
|
let xs = Intersections::from(Intersection::new(4., &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
assert_eq!(comps.inside, false);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hit_when_intersection_occurs_inside() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The hit, when an intersection occurs on the inside.
|
||||||
|
let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
|
||||||
|
let shape = Shape::sphere();
|
||||||
|
let xs = Intersections::from(Intersection::new(1., &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
assert_eq!(comps.point, point(0., 0., 1.));
|
||||||
|
assert_eq!(comps.eyev, vector(0., 0., -1.));
|
||||||
|
assert_eq!(comps.inside, true);
|
||||||
|
// Normal would have been (0, 0, 1), but is inverted when inside.
|
||||||
|
assert_eq!(comps.normalv, vector(0., 0., -1.));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn hit_should_offset_the_point() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The hit should offset the point.
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let mut shape = Shape::sphere();
|
||||||
|
shape.set_transform(translation(0., 0., 1.));
|
||||||
|
let xs = Intersections::from(Intersection::new(5., &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
assert!(comps.over_point.z < -EPSILON / 2.);
|
||||||
|
assert!(comps.point.z > comps.over_point.z);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn precomputing_the_reflection_vector() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Precomputing the reflection vector.
|
||||||
|
let shape = Shape::plane();
|
||||||
|
let r = Ray::new(
|
||||||
|
point(0., 1., -1.),
|
||||||
|
vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||||
|
);
|
||||||
|
let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
assert_eq!(
|
||||||
|
comps.reflectv,
|
||||||
|
vector(0., (2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.)
|
||||||
|
);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn finding_n1_and_n2() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Finding n1 and n2 at various intersections.
|
||||||
|
let a = glass_sphere()
|
||||||
|
.transform(scaling(2., 2., 2.))
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.transparency(1.)
|
||||||
|
.refractive_index(1.5)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
let b = glass_sphere()
|
||||||
|
.transform(translation(0., 0., -0.25))
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.transparency(1.)
|
||||||
|
.refractive_index(2.)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
let c = glass_sphere()
|
||||||
|
.transform(translation(0., 0., 0.25))
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.transparency(1.)
|
||||||
|
.refractive_index(2.5)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
let r = Ray::new(point(0., 0., -4.), vector(0., 0., 1.));
|
||||||
|
let xs = Intersections::new(vec![
|
||||||
|
Intersection::new(2., &a),
|
||||||
|
Intersection::new(2.75, &b),
|
||||||
|
Intersection::new(3.25, &c),
|
||||||
|
Intersection::new(4.75, &b),
|
||||||
|
Intersection::new(5.25, &c),
|
||||||
|
Intersection::new(6., &a),
|
||||||
|
]);
|
||||||
|
for (index, n1, n2) in &[
|
||||||
|
(0, 1.0, 1.5),
|
||||||
|
(1, 1.5, 2.0),
|
||||||
|
(2, 2.0, 2.5),
|
||||||
|
(3, 2.5, 2.5),
|
||||||
|
(4, 2.5, 1.5),
|
||||||
|
(5, 1.5, 1.0),
|
||||||
|
] {
|
||||||
|
let comps = prepare_computations(&xs[*index], &r, &xs);
|
||||||
|
assert_eq!(comps.n1, *n1);
|
||||||
|
assert_eq!(comps.n2, *n2);
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn under_point_offset_below_surface() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The under point is offset below the surface.
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let shape = glass_sphere().transform(translation(0., 0., 1.)).build()?;
|
||||||
|
let xs = Intersections::from(Intersection::new(5., &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
assert!(comps.under_point.z > EPSILON / 2.);
|
||||||
|
assert!(comps.point.z < comps.under_point.z);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod schlick {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn under_total_reflection() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let shape = glass_sphere().build()?;
|
||||||
|
let r = Ray::new(point(0., 0., (2 as Float).sqrt() / 2.), vector(0., 1., 0.));
|
||||||
|
let xs = Intersections::new(vec![
|
||||||
|
Intersection::new(-(2 as Float).sqrt() / 2., &shape),
|
||||||
|
Intersection::new((2 as Float).sqrt() / 2., &shape),
|
||||||
|
]);
|
||||||
|
let comps = prepare_computations(&xs[1], &r, &xs);
|
||||||
|
let reflectance = schlick(&comps);
|
||||||
|
assert_eq!(reflectance, 1.);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn perpendicular_viewing_angle() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let shape = glass_sphere().build()?;
|
||||||
|
let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.));
|
||||||
|
let xs = Intersections::new(vec![
|
||||||
|
Intersection::new(-1., &shape),
|
||||||
|
Intersection::new(1., &shape),
|
||||||
|
]);
|
||||||
|
let comps = prepare_computations(&xs[1], &r, &xs);
|
||||||
|
let reflectance = schlick(&comps);
|
||||||
|
assert!((reflectance - 0.04).abs() < EPSILON);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn small_angle_n2_greater_n1() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let shape = glass_sphere().build()?;
|
||||||
|
let r = Ray::new(point(0., 0.99, -2.), vector(0., 0., 1.));
|
||||||
|
let xs = Intersections::new(vec![Intersection::new(1.8589, &shape)]);
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let reflectance = schlick(&comps);
|
||||||
|
assert!((reflectance - 0.48873).abs() < EPSILON);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,6 +4,7 @@ pub mod intersections;
|
|||||||
pub mod lights;
|
pub mod lights;
|
||||||
pub mod materials;
|
pub mod materials;
|
||||||
pub mod matrices;
|
pub mod matrices;
|
||||||
|
pub mod patterns;
|
||||||
pub mod rays;
|
pub mod rays;
|
||||||
pub mod shapes;
|
pub mod shapes;
|
||||||
pub mod transformations;
|
pub mod transformations;
|
||||||
@ -44,6 +45,7 @@ pub mod prelude {
|
|||||||
lights::{PointLight, PointLightBuilder},
|
lights::{PointLight, PointLightBuilder},
|
||||||
materials::{Material, MaterialBuilder},
|
materials::{Material, MaterialBuilder},
|
||||||
matrices::{identity, rotation_x, rotation_y, rotation_z, scaling, shearing, translation},
|
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::{plane, sphere, test_shape},
|
||||||
transformations::view_transform,
|
transformations::view_transform,
|
||||||
tuples::{point, vector, Color},
|
tuples::{point, vector, Color},
|
||||||
|
|||||||
@ -13,21 +13,6 @@ pub struct PointLight {
|
|||||||
impl PointLight {
|
impl PointLight {
|
||||||
/// Creates a new `PositionLight` at the given `position` and with the given
|
/// Creates a new `PositionLight` at the given `position` and with the given
|
||||||
/// `intensity`.
|
/// `intensity`.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// lights::PointLight,
|
|
||||||
/// tuples::{Color, Tuple},
|
|
||||||
/// WHITE,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let intensity = WHITE;
|
|
||||||
/// let position = Tuple::point(0., 0., 0.);
|
|
||||||
/// let light = PointLight::new(position, intensity);
|
|
||||||
/// assert_eq!(light.position, position);
|
|
||||||
/// assert_eq!(light.intensity, intensity);
|
|
||||||
/// ```
|
|
||||||
pub fn new<C>(position: Tuple, intensity: C) -> PointLight
|
pub fn new<C>(position: Tuple, intensity: C) -> PointLight
|
||||||
where
|
where
|
||||||
C: Into<Color>,
|
C: Into<Color>,
|
||||||
@ -38,3 +23,17 @@ impl PointLight {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{lights::PointLight, tuples::point, WHITE};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn new() {
|
||||||
|
let intensity = WHITE;
|
||||||
|
let position = point(0., 0., 0.);
|
||||||
|
let light = PointLight::new(position, intensity);
|
||||||
|
assert_eq!(light.position, position);
|
||||||
|
assert_eq!(light.intensity, intensity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,8 +2,9 @@ use derive_builder::Builder;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
lights::PointLight,
|
lights::PointLight,
|
||||||
tuples::Color,
|
patterns::Pattern,
|
||||||
tuples::{dot, reflect, Tuple},
|
shapes::Shape,
|
||||||
|
tuples::{dot, reflect, Color, Tuple},
|
||||||
Float, BLACK, WHITE,
|
Float, BLACK, WHITE,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -11,103 +12,36 @@ use crate::{
|
|||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub struct Material {
|
pub struct Material {
|
||||||
#[builder(setter(into))]
|
#[builder(setter(into))]
|
||||||
pub color: Color,
|
pub color: Pattern,
|
||||||
pub ambient: Float,
|
pub ambient: Float,
|
||||||
pub diffuse: Float,
|
pub diffuse: Float,
|
||||||
pub specular: Float,
|
pub specular: Float,
|
||||||
pub shininess: Float,
|
pub shininess: Float,
|
||||||
|
pub reflective: Float,
|
||||||
|
pub transparency: Float,
|
||||||
|
pub refractive_index: Float,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Material {
|
impl Default for Material {
|
||||||
/// Creates the default material.
|
/// Creates the default material.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{materials::Material, tuples::Color, WHITE};
|
|
||||||
///
|
|
||||||
/// let m = Material::default();
|
|
||||||
/// assert_eq!(
|
|
||||||
/// m,
|
|
||||||
/// Material {
|
|
||||||
/// color: WHITE,
|
|
||||||
/// ambient: 0.1,
|
|
||||||
/// diffuse: 0.9,
|
|
||||||
/// specular: 0.9,
|
|
||||||
/// shininess: 200.,
|
|
||||||
/// }
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
fn default() -> Material {
|
fn default() -> Material {
|
||||||
Material {
|
Material {
|
||||||
color: WHITE,
|
color: WHITE.into(),
|
||||||
ambient: 0.1,
|
ambient: 0.1,
|
||||||
diffuse: 0.9,
|
diffuse: 0.9,
|
||||||
specular: 0.9,
|
specular: 0.9,
|
||||||
shininess: 200.,
|
shininess: 200.,
|
||||||
|
reflective: 0.0,
|
||||||
|
transparency: 0.0,
|
||||||
|
refractive_index: 1.0,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute lighting contributions using the Phong reflection model.
|
/// Compute lighting contributions using the Phong reflection model.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// lights::PointLight,
|
|
||||||
/// materials::{lighting, Material},
|
|
||||||
/// tuples::{Color, Tuple},
|
|
||||||
/// Float, WHITE,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let in_shadow = false;
|
|
||||||
/// let m = Material::default();
|
|
||||||
/// let position = Tuple::point(0., 0., 0.);
|
|
||||||
///
|
|
||||||
/// // Lighting with the eye between the light and the surface.
|
|
||||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE);
|
|
||||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
|
||||||
/// assert_eq!(result, Color::new(1.9, 1.9, 1.9));
|
|
||||||
///
|
|
||||||
/// // Lighting with the eye between the light and the surface, eye offset 45°.
|
|
||||||
/// let eyev = Tuple::vector(0., (2. as Float).sqrt() / 2., -(2. as Float).sqrt() / 2.);
|
|
||||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE);
|
|
||||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
|
||||||
/// assert_eq!(result, WHITE);
|
|
||||||
///
|
|
||||||
/// // Lighting with the eye opposite surface, light offset 45°.
|
|
||||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let light = PointLight::new(Tuple::point(0., 10., -10.), WHITE);
|
|
||||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
|
||||||
/// assert_eq!(result, Color::new(0.7364, 0.7364, 0.7364));
|
|
||||||
///
|
|
||||||
/// // Lighting with the eye in the path of the reflection vector.
|
|
||||||
/// let eyev = Tuple::vector(0., -(2.0 as Float).sqrt() / 2., -(2.0 as Float).sqrt() / 2.);
|
|
||||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let light = PointLight::new(Tuple::point(0., 10., -10.), WHITE);
|
|
||||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
|
||||||
/// assert_eq!(result, Color::new(1.63639, 1.63639, 1.63639));
|
|
||||||
///
|
|
||||||
/// // Lighting with the light behind the surface.
|
|
||||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let light = PointLight::new(Tuple::point(0., 0., 10.), WHITE);
|
|
||||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
|
||||||
/// assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
|
||||||
///
|
|
||||||
/// // Lighting with the surface in shadow.
|
|
||||||
/// let in_shadow = true;
|
|
||||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
|
||||||
/// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE);
|
|
||||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
|
||||||
/// assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
|
||||||
/// ```
|
|
||||||
pub fn lighting(
|
pub fn lighting(
|
||||||
material: &Material,
|
material: &Material,
|
||||||
|
object: &Shape,
|
||||||
light: &PointLight,
|
light: &PointLight,
|
||||||
point: Tuple,
|
point: Tuple,
|
||||||
eyev: Tuple,
|
eyev: Tuple,
|
||||||
@ -115,7 +49,8 @@ pub fn lighting(
|
|||||||
in_shadow: bool,
|
in_shadow: bool,
|
||||||
) -> Color {
|
) -> Color {
|
||||||
// Combine the surface color with the light's color.
|
// Combine the surface color with the light's color.
|
||||||
let effective_color = material.color * light.intensity;
|
let color = material.color.pattern_at_object(object, point);
|
||||||
|
let effective_color = color * light.intensity;
|
||||||
// Find the direciton of the light source.
|
// Find the direciton of the light source.
|
||||||
let lightv = (light.position - point).normalize();
|
let lightv = (light.position - point).normalize();
|
||||||
// Compute the ambient distribution.
|
// Compute the ambient distribution.
|
||||||
@ -149,3 +84,153 @@ pub fn lighting(
|
|||||||
ambient + diffuse + specular
|
ambient + diffuse + specular
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{materials::Material, WHITE};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default() {
|
||||||
|
let m = Material::default();
|
||||||
|
assert_eq!(
|
||||||
|
m,
|
||||||
|
Material {
|
||||||
|
color: WHITE.into(),
|
||||||
|
ambient: 0.1,
|
||||||
|
diffuse: 0.9,
|
||||||
|
specular: 0.9,
|
||||||
|
shininess: 200.,
|
||||||
|
reflective: 0.0,
|
||||||
|
transparency: 0.0,
|
||||||
|
refractive_index: 1.0,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
mod lighting {
|
||||||
|
use crate::{
|
||||||
|
lights::PointLight,
|
||||||
|
materials::{lighting, Material},
|
||||||
|
patterns::Pattern,
|
||||||
|
shapes::Shape,
|
||||||
|
tuples::{point, vector, Color},
|
||||||
|
Float, BLACK, WHITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eye_between_light_and_surface() {
|
||||||
|
let in_shadow = false;
|
||||||
|
let m = Material::default();
|
||||||
|
let position = point(0., 0., 0.);
|
||||||
|
let object = Shape::sphere();
|
||||||
|
|
||||||
|
// Lighting with the eye between the light and the surface.
|
||||||
|
let eyev = vector(0., 0., -1.);
|
||||||
|
let normalv = vector(0., 0., -1.);
|
||||||
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
||||||
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
||||||
|
assert_eq!(result, Color::new(1.9, 1.9, 1.9));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eye_between_light_and_surface_offset_45() {
|
||||||
|
// Lighting with the eye between the light and the surface, eye offset 45°.
|
||||||
|
let in_shadow = false;
|
||||||
|
let m = Material::default();
|
||||||
|
let position = point(0., 0., 0.);
|
||||||
|
let object = Shape::sphere();
|
||||||
|
let eyev = vector(0., (2. as Float).sqrt() / 2., -(2. as Float).sqrt() / 2.);
|
||||||
|
let normalv = vector(0., 0., -1.);
|
||||||
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
||||||
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
||||||
|
assert_eq!(result, WHITE);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn eye_opposite_surface_light_offset_45() {
|
||||||
|
// Lighting with the eye opposite surface, light offset 45°.
|
||||||
|
let in_shadow = false;
|
||||||
|
let m = Material::default();
|
||||||
|
let position = point(0., 0., 0.);
|
||||||
|
let object = Shape::sphere();
|
||||||
|
let eyev = vector(0., 0., -1.);
|
||||||
|
let normalv = vector(0., 0., -1.);
|
||||||
|
let light = PointLight::new(point(0., 10., -10.), WHITE);
|
||||||
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
||||||
|
assert_eq!(result, Color::new(0.7364, 0.7364, 0.7364));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn eye_in_path_of_reflection_vector() {
|
||||||
|
// Lighting with the eye in the path of the reflection vector.
|
||||||
|
let in_shadow = false;
|
||||||
|
let m = Material::default();
|
||||||
|
let position = point(0., 0., 0.);
|
||||||
|
let object = Shape::sphere();
|
||||||
|
let eyev = vector(0., -(2.0 as Float).sqrt() / 2., -(2.0 as Float).sqrt() / 2.);
|
||||||
|
let normalv = vector(0., 0., -1.);
|
||||||
|
let light = PointLight::new(point(0., 10., -10.), WHITE);
|
||||||
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
||||||
|
assert_eq!(result, Color::new(1.63639, 1.63639, 1.63639));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn light_behind_surface() {
|
||||||
|
// Lighting with the light behind the surface.
|
||||||
|
let in_shadow = false;
|
||||||
|
let m = Material::default();
|
||||||
|
let position = point(0., 0., 0.);
|
||||||
|
let object = Shape::sphere();
|
||||||
|
let eyev = vector(0., 0., -1.);
|
||||||
|
let normalv = vector(0., 0., -1.);
|
||||||
|
let light = PointLight::new(point(0., 0., 10.), WHITE);
|
||||||
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
||||||
|
assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn surface_in_shadow() {
|
||||||
|
// Lighting with the surface in shadow.
|
||||||
|
let m = Material::default();
|
||||||
|
let position = point(0., 0., 0.);
|
||||||
|
let object = Shape::sphere();
|
||||||
|
let in_shadow = true;
|
||||||
|
let eyev = vector(0., 0., -1.);
|
||||||
|
let normalv = vector(0., 0., -1.);
|
||||||
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
||||||
|
let result = lighting(&m, &object, &light, position, eyev, normalv, in_shadow);
|
||||||
|
assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn pattern_applied() {
|
||||||
|
// Lighting with a pattern applied.
|
||||||
|
let object = Shape::sphere();
|
||||||
|
let m = Material {
|
||||||
|
color: Pattern::stripe(WHITE, BLACK),
|
||||||
|
ambient: 1.,
|
||||||
|
diffuse: 0.,
|
||||||
|
specular: 0.,
|
||||||
|
..Material::default()
|
||||||
|
};
|
||||||
|
let eyev = vector(0., 0., -1.);
|
||||||
|
let normalv = vector(0., 0., -1.);
|
||||||
|
let light = PointLight::new(point(0., 0., -10.), WHITE);
|
||||||
|
let c1 = lighting(
|
||||||
|
&m,
|
||||||
|
&object,
|
||||||
|
&light,
|
||||||
|
point(0.9, 0., 0.),
|
||||||
|
eyev,
|
||||||
|
normalv,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
let c2 = lighting(
|
||||||
|
&m,
|
||||||
|
&object,
|
||||||
|
&light,
|
||||||
|
point(1.1, 0., 0.),
|
||||||
|
eyev,
|
||||||
|
normalv,
|
||||||
|
false,
|
||||||
|
);
|
||||||
|
assert_eq!(c1, WHITE);
|
||||||
|
assert_eq!(c2, BLACK);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -4,82 +4,30 @@ use std::ops::{Index, IndexMut, Mul, Sub};
|
|||||||
use crate::{tuples::Tuple, Float, EPSILON};
|
use crate::{tuples::Tuple, Float, EPSILON};
|
||||||
|
|
||||||
/// Short hand for creating a Matrix4x4 set to the identity matrix.
|
/// Short hand for creating a Matrix4x4 set to the identity matrix.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{identity, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(identity(), Matrix4x4::identity());
|
|
||||||
/// ```
|
|
||||||
pub fn identity() -> Matrix4x4 {
|
pub fn identity() -> Matrix4x4 {
|
||||||
Matrix4x4::identity()
|
Matrix4x4::identity()
|
||||||
}
|
}
|
||||||
/// Short hand for creating a Matrix4x4 for rotating around the X-axis.
|
/// Short hand for creating a Matrix4x4 for rotating around the X-axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{rotation_x, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(rotation_x(10.), Matrix4x4::rotation_x(10.));
|
|
||||||
/// ```
|
|
||||||
pub fn rotation_x(radians: Float) -> Matrix4x4 {
|
pub fn rotation_x(radians: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::rotation_x(radians)
|
Matrix4x4::rotation_x(radians)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a Matrix4x4 for rotating around the Y-axis.
|
/// Short hand for creating a Matrix4x4 for rotating around the Y-axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{rotation_y, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(rotation_y(10.), Matrix4x4::rotation_y(10.));
|
|
||||||
/// ```
|
|
||||||
pub fn rotation_y(radians: Float) -> Matrix4x4 {
|
pub fn rotation_y(radians: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::rotation_y(radians)
|
Matrix4x4::rotation_y(radians)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a Matrix4x4 for rotating around the Z-axis.
|
/// Short hand for creating a Matrix4x4 for rotating around the Z-axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{rotation_z, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(rotation_z(10.), Matrix4x4::rotation_z(10.));
|
|
||||||
/// ```
|
|
||||||
pub fn rotation_z(radians: Float) -> Matrix4x4 {
|
pub fn rotation_z(radians: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::rotation_z(radians)
|
Matrix4x4::rotation_z(radians)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a Matrix4x4 that scales in the given x,y,z axis.
|
/// Short hand for creating a Matrix4x4 that scales in the given x,y,z axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{scaling, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(scaling(1., 2., 3.), Matrix4x4::scaling(1., 2., 3.));
|
|
||||||
/// ```
|
|
||||||
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::scaling(x, y, z)
|
Matrix4x4::scaling(x, y, z)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a Matrix4x4 that shears across the given axis pairs.
|
/// Short hand for creating a Matrix4x4 that shears across the given axis pairs.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{shearing, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// shearing(1., 2., 3., 4., 5., 6.),
|
|
||||||
/// Matrix4x4::shearing(1., 2., 3., 4., 5., 6.)
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
|
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::shearing(xy, xz, yx, yz, zx, zy)
|
Matrix4x4::shearing(xy, xz, yx, yz, zx, zy)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a Matrix4x4 that translations along the given x,y,z axis.
|
/// Short hand for creating a Matrix4x4 that translations along the given x,y,z axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{translation, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(translation(1., 2., 3.), Matrix4x4::translation(1., 2., 3.));
|
|
||||||
/// ```
|
|
||||||
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::translation(x, y, z)
|
Matrix4x4::translation(x, y, z)
|
||||||
}
|
}
|
||||||
@ -95,16 +43,6 @@ impl Matrix2x2 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Calculate the determinant of a 2x2.
|
/// Calculate the determinant of a 2x2.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix2x2;
|
|
||||||
///
|
|
||||||
/// let a = Matrix2x2::new([1., 5.], [-3., 2.]);
|
|
||||||
///
|
|
||||||
/// assert_eq!(a.determinant(), 17.);
|
|
||||||
/// ```
|
|
||||||
pub fn determinant(&self) -> Float {
|
pub fn determinant(&self) -> Float {
|
||||||
let m = self;
|
let m = self;
|
||||||
m[(0, 0)] * m[(1, 1)] - m[(0, 1)] * m[(1, 0)]
|
m[(0, 0)] * m[(1, 1)] - m[(0, 1)] * m[(1, 0)]
|
||||||
@ -142,16 +80,6 @@ impl Matrix3x3 {
|
|||||||
Matrix3x3 { m: [r0, r1, r2] }
|
Matrix3x3 { m: [r0, r1, r2] }
|
||||||
}
|
}
|
||||||
/// submatrix extracts a 2x2 matrix ignoring the 0-based `row` and `col` given.
|
/// submatrix extracts a 2x2 matrix ignoring the 0-based `row` and `col` given.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{Matrix2x2, Matrix3x3};
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// Matrix3x3::new([1., 5., 0.], [-3., 2., 7.], [0., 6., -3.],).submatrix(0, 2),
|
|
||||||
/// Matrix2x2::new([-3., 2.], [0., 6.])
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub fn submatrix(&self, row: usize, col: usize) -> Matrix2x2 {
|
pub fn submatrix(&self, row: usize, col: usize) -> Matrix2x2 {
|
||||||
assert!(row < 3);
|
assert!(row < 3);
|
||||||
assert!(col < 3);
|
assert!(col < 3);
|
||||||
@ -172,49 +100,17 @@ impl Matrix3x3 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute minor of a 3x3 matrix.
|
/// Compute minor of a 3x3 matrix.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix3x3;
|
|
||||||
///
|
|
||||||
/// let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
|
|
||||||
/// let b = a.submatrix(1, 0);
|
|
||||||
/// assert_eq!(b.determinant(), 25.0);
|
|
||||||
/// assert_eq!(b.determinant(), a.minor(1, 0));
|
|
||||||
/// ```
|
|
||||||
pub fn minor(&self, row: usize, col: usize) -> Float {
|
pub fn minor(&self, row: usize, col: usize) -> Float {
|
||||||
self.submatrix(row, col).determinant()
|
self.submatrix(row, col).determinant()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute cofactor of a 3x3 matrix.
|
/// Compute cofactor of a 3x3 matrix.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix3x3;
|
|
||||||
///
|
|
||||||
/// let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
|
|
||||||
/// assert_eq!(a.minor(0, 0), -12.);
|
|
||||||
/// assert_eq!(a.cofactor(0, 0), -12.);
|
|
||||||
/// assert_eq!(a.minor(1, 0), 25.);
|
|
||||||
/// assert_eq!(a.cofactor(1, 0), -25.);
|
|
||||||
/// ```
|
|
||||||
pub fn cofactor(&self, row: usize, col: usize) -> Float {
|
pub fn cofactor(&self, row: usize, col: usize) -> Float {
|
||||||
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
|
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
|
||||||
self.submatrix(row, col).determinant() * negate
|
self.submatrix(row, col).determinant() * negate
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute determinant of a 3x3 matrix.
|
/// Compute determinant of a 3x3 matrix.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix3x3;
|
|
||||||
///
|
|
||||||
/// let a = Matrix3x3::new([1., 2., 6.], [-5., 8., -4.], [2., 6., 4.]);
|
|
||||||
/// assert_eq!(a.cofactor(0, 0), 56.);
|
|
||||||
/// assert_eq!(a.cofactor(0, 1), 12.);
|
|
||||||
/// assert_eq!(a.cofactor(0, 2), -46.);
|
|
||||||
/// assert_eq!(a.determinant(), -196.);
|
|
||||||
/// ```
|
|
||||||
pub fn determinant(&self) -> Float {
|
pub fn determinant(&self) -> Float {
|
||||||
(0..3).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
|
(0..3).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
|
||||||
}
|
}
|
||||||
@ -244,34 +140,6 @@ impl PartialEq for Matrix3x3 {
|
|||||||
|
|
||||||
/// Matrix4x4 represents a 4x4 matrix in row-major form. So, element `m[i][j]` corresponds to m<sub>i,j</sub>
|
/// Matrix4x4 represents a 4x4 matrix in row-major form. So, element `m[i][j]` corresponds to m<sub>i,j</sub>
|
||||||
/// where `i` is the row number and `j` is the column number.
|
/// where `i` is the row number and `j` is the column number.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// // Individual transformations are applied in sequence.
|
|
||||||
/// let p = Tuple::point(1., 0., 1.);
|
|
||||||
/// let a = Matrix4x4::rotation_x(PI / 2.);
|
|
||||||
/// let b = Matrix4x4::scaling(5., 5., 5.);
|
|
||||||
/// let c = Matrix4x4::translation(10., 5., 7.);
|
|
||||||
/// // Apply rotation first.
|
|
||||||
/// let p2 = a * p;
|
|
||||||
/// assert_eq!(p2, Tuple::point(1., -1., 0.));
|
|
||||||
/// // Then apply scaling.
|
|
||||||
/// let p3 = b * p2;
|
|
||||||
/// assert_eq!(p3, Tuple::point(5., -5., 0.));
|
|
||||||
/// // Then apply translation.
|
|
||||||
/// let p4 = c * p3;
|
|
||||||
/// assert_eq!(p4, Tuple::point(15., 0., 7.));
|
|
||||||
///
|
|
||||||
/// // Chained transformations must be applied in reverse order.
|
|
||||||
/// let p = Tuple::point(1., 0., 1.);
|
|
||||||
/// let a = Matrix4x4::rotation_x(PI / 2.);
|
|
||||||
/// let b = Matrix4x4::scaling(5., 5., 5.);
|
|
||||||
/// let c = Matrix4x4::translation(10., 5., 7.);
|
|
||||||
/// let t = c * b * a;
|
|
||||||
/// assert_eq!(t * p, Tuple::point(15., 0., 7.));
|
|
||||||
/// ```
|
|
||||||
#[derive(Copy, Clone, Default)]
|
#[derive(Copy, Clone, Default)]
|
||||||
pub struct Matrix4x4 {
|
pub struct Matrix4x4 {
|
||||||
m: [[Float; 4]; 4],
|
m: [[Float; 4]; 4],
|
||||||
@ -292,21 +160,6 @@ impl From<[Float; 16]> for Matrix4x4 {
|
|||||||
|
|
||||||
impl Matrix4x4 {
|
impl Matrix4x4 {
|
||||||
/// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal.
|
/// Create a `Matrix4x4` containing the identity, all zeros with ones along the diagonal.
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
///
|
|
||||||
/// let a = Matrix4x4::new(
|
|
||||||
/// [0., 1., 2., 3.],
|
|
||||||
/// [1., 2., 4., 8.],
|
|
||||||
/// [2., 4., 8., 16.],
|
|
||||||
/// [4., 8., 16., 32.],
|
|
||||||
/// );
|
|
||||||
/// let i = Matrix4x4::identity();
|
|
||||||
///
|
|
||||||
/// assert_eq!(a * i, a);
|
|
||||||
/// ```
|
|
||||||
pub fn identity() -> Matrix4x4 {
|
pub fn identity() -> Matrix4x4 {
|
||||||
Matrix4x4::new(
|
Matrix4x4::new(
|
||||||
[1., 0., 0., 0.],
|
[1., 0., 0., 0.],
|
||||||
@ -324,22 +177,6 @@ impl Matrix4x4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a 4x4 matrix representing a translation of x,y,z.
|
/// Creates a 4x4 matrix representing a translation of x,y,z.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// let transform = Matrix4x4::translation(5., -3., 2.);
|
|
||||||
/// let p = Tuple::point(-3., 4., 5.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(2., 1., 7.));
|
|
||||||
///
|
|
||||||
/// let inv = transform.inverse();
|
|
||||||
/// assert_eq!(inv * p, Tuple::point(-8., 7., 3.));
|
|
||||||
///
|
|
||||||
/// let v = Tuple::vector(-3., 4., 5.);
|
|
||||||
/// assert_eq!(transform * v, v);
|
|
||||||
/// ```
|
|
||||||
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::new(
|
Matrix4x4::new(
|
||||||
[1., 0., 0., x],
|
[1., 0., 0., x],
|
||||||
@ -350,30 +187,6 @@ impl Matrix4x4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a 4x4 matrix representing a scaling of x,y,z.
|
/// Creates a 4x4 matrix representing a scaling of x,y,z.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// // A scaling matrix applied to a point.
|
|
||||||
/// let transform = Matrix4x4::scaling(2., 3., 4.);
|
|
||||||
/// let p = Tuple::point(-4., 6., 8.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(-8., 18., 32.));
|
|
||||||
///
|
|
||||||
/// // A scaling matrix applied to a vector.
|
|
||||||
/// let v = Tuple::vector(-4., 6., 8.);
|
|
||||||
/// assert_eq!(transform * v, Tuple::vector(-8., 18., 32.));
|
|
||||||
///
|
|
||||||
/// // Multiplying by the inverse of a scaling matrix.
|
|
||||||
/// let inv = transform.inverse();
|
|
||||||
/// assert_eq!(inv * v, Tuple::vector(-2., 2., 2.));
|
|
||||||
///
|
|
||||||
/// // Reflection is scaling by a negative value.
|
|
||||||
/// let transform = Matrix4x4::scaling(-1., 1., 1.);
|
|
||||||
/// let p = Tuple::point(2., 3., 4.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(-2., 3., 4.));
|
|
||||||
/// ```
|
|
||||||
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::new(
|
Matrix4x4::new(
|
||||||
[x, 0., 0., 0.],
|
[x, 0., 0., 0.],
|
||||||
@ -384,23 +197,6 @@ impl Matrix4x4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a 4x4 matrix representing a rotation around the x-axis.
|
/// Creates a 4x4 matrix representing a rotation around the x-axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float};
|
|
||||||
///
|
|
||||||
/// // A scaling matrix applied to a point.
|
|
||||||
/// let p = Tuple::point(0., 1., 0.);
|
|
||||||
/// let half_quarter = Matrix4x4::rotation_x(PI / 4.);
|
|
||||||
/// let full_quarter = Matrix4x4::rotation_x(PI / 2.);
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// half_quarter * p,
|
|
||||||
/// Tuple::point(0., (2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2.)
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(full_quarter * p, Tuple::point(0., 0., 1.),);
|
|
||||||
/// ```
|
|
||||||
pub fn rotation_x(radians: Float) -> Matrix4x4 {
|
pub fn rotation_x(radians: Float) -> Matrix4x4 {
|
||||||
let r = radians;
|
let r = radians;
|
||||||
Matrix4x4::new(
|
Matrix4x4::new(
|
||||||
@ -412,23 +208,6 @@ impl Matrix4x4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a 4x4 matrix representing a rotation around the y-axis.
|
/// Creates a 4x4 matrix representing a rotation around the y-axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float};
|
|
||||||
///
|
|
||||||
/// // A scaling matrix applied to a point.
|
|
||||||
/// let p = Tuple::point(0., 0., 1.);
|
|
||||||
/// let half_quarter = Matrix4x4::rotation_y(PI / 4.);
|
|
||||||
/// let full_quarter = Matrix4x4::rotation_y(PI / 2.);
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// half_quarter * p,
|
|
||||||
/// Tuple::point((2.0 as Float).sqrt() / 2., 0., (2.0 as Float).sqrt() / 2.)
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(full_quarter * p, Tuple::point(1., 0., 0.,),);
|
|
||||||
/// ```
|
|
||||||
pub fn rotation_y(radians: Float) -> Matrix4x4 {
|
pub fn rotation_y(radians: Float) -> Matrix4x4 {
|
||||||
let r = radians;
|
let r = radians;
|
||||||
Matrix4x4::new(
|
Matrix4x4::new(
|
||||||
@ -440,23 +219,6 @@ impl Matrix4x4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a 4x4 matrix representing a rotation around the z-axis.
|
/// Creates a 4x4 matrix representing a rotation around the z-axis.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float};
|
|
||||||
///
|
|
||||||
/// // A scaling matrix applied to a point.
|
|
||||||
/// let p = Tuple::point(0., 1., 0.);
|
|
||||||
/// let half_quarter = Matrix4x4::rotation_z(PI / 4.);
|
|
||||||
/// let full_quarter = Matrix4x4::rotation_z(PI / 2.);
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// half_quarter * p,
|
|
||||||
/// Tuple::point(-(2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2., 0.)
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(full_quarter * p, Tuple::point(-1., 0., 0.,),);
|
|
||||||
/// ```
|
|
||||||
pub fn rotation_z(radians: Float) -> Matrix4x4 {
|
pub fn rotation_z(radians: Float) -> Matrix4x4 {
|
||||||
let r = radians;
|
let r = radians;
|
||||||
Matrix4x4::new(
|
Matrix4x4::new(
|
||||||
@ -468,26 +230,6 @@ impl Matrix4x4 {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Transpose self, returning a new matrix that has been reflected across the diagonal.
|
/// Transpose self, returning a new matrix that has been reflected across the diagonal.
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
///
|
|
||||||
/// let m = Matrix4x4::new(
|
|
||||||
/// [2., 0., 0., 0.],
|
|
||||||
/// [3., 1., 0., 0.],
|
|
||||||
/// [4., 0., 1., 0.],
|
|
||||||
/// [5., 6., 7., 1.],
|
|
||||||
/// );
|
|
||||||
/// let m_t = Matrix4x4::new(
|
|
||||||
/// [2., 3., 4., 5.],
|
|
||||||
/// [0., 1., 0., 6.],
|
|
||||||
/// [0., 0., 1., 7.],
|
|
||||||
/// [0., 0., 0., 1.],
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(m.transpose(), m_t);
|
|
||||||
///
|
|
||||||
/// assert_eq!(Matrix4x4::identity(), Matrix4x4::identity().transpose());
|
|
||||||
pub fn transpose(&self) -> Matrix4x4 {
|
pub fn transpose(&self) -> Matrix4x4 {
|
||||||
let m = self.m;
|
let m = self.m;
|
||||||
Matrix4x4 {
|
Matrix4x4 {
|
||||||
@ -501,40 +243,6 @@ impl Matrix4x4 {
|
|||||||
}
|
}
|
||||||
/// Create a transform matrix that will shear (skew) points.
|
/// Create a transform matrix that will shear (skew) points.
|
||||||
/// # Examples
|
/// # Examples
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// // A shearing transform moves x in proportion to y.
|
|
||||||
/// let transform = Matrix4x4::shearing(1.,0.,0.,0.,0.,0.);
|
|
||||||
/// let p = Tuple::point(2.,3.,4.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(5.,3.,4.));
|
|
||||||
///
|
|
||||||
/// // A shearing transform moves x in proportion to z.
|
|
||||||
/// let transform = Matrix4x4::shearing(0.,1.,0.,0.,0.,0.);
|
|
||||||
/// let p = Tuple::point(2.,3.,4.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(6.,3.,4.));
|
|
||||||
///
|
|
||||||
/// // A shearing transform moves y in proportion to x.
|
|
||||||
/// let transform = Matrix4x4::shearing(0.,0.,1.,0.,0.,0.);
|
|
||||||
/// let p = Tuple::point(2.,3.,4.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(2.,5.,4.));
|
|
||||||
///
|
|
||||||
/// // A shearing transform moves y in proportion to z.
|
|
||||||
/// let transform = Matrix4x4::shearing(0.,0.,0.,1.,0.,0.);
|
|
||||||
/// let p = Tuple::point(2.,3.,4.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(2.,7.,4.));
|
|
||||||
///
|
|
||||||
/// // A shearing transform moves z in proportion to x.
|
|
||||||
/// let transform = Matrix4x4::shearing(0.,0.,0.,0.,1.,0.);
|
|
||||||
/// let p = Tuple::point(2.,3.,4.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(2.,3.,6.));
|
|
||||||
///
|
|
||||||
/// // A shearing transform moves z in proportion to y.
|
|
||||||
/// let transform = Matrix4x4::shearing(0.,0.,0.,0.,0.,1.);
|
|
||||||
/// let p = Tuple::point(2.,3.,4.);
|
|
||||||
/// assert_eq!(transform * p, Tuple::point(2.,3.,7.));
|
|
||||||
|
|
||||||
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
|
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
|
||||||
Matrix4x4::new(
|
Matrix4x4::new(
|
||||||
[1., xy, xz, 0.],
|
[1., xy, xz, 0.],
|
||||||
@ -547,24 +255,6 @@ impl Matrix4x4 {
|
|||||||
/// Returns a new matrix that is the inverse of self. If self is A, inverse returns A<sup>-1</sup>, where
|
/// Returns a new matrix that is the inverse of self. If self is A, inverse returns A<sup>-1</sup>, where
|
||||||
/// AA<sup>-1</sup> = I.
|
/// AA<sup>-1</sup> = I.
|
||||||
/// This implementation uses a numerically stable Gauss–Jordan elimination routine to compute the inverse.
|
/// This implementation uses a numerically stable Gauss–Jordan elimination routine to compute the inverse.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
///
|
|
||||||
/// let i = Matrix4x4::identity();
|
|
||||||
/// assert_eq!(i.inverse_rtiow() * i, i);
|
|
||||||
///
|
|
||||||
/// let m = Matrix4x4::new(
|
|
||||||
/// [2., 0., 0., 0.],
|
|
||||||
/// [0., 3., 0., 0.],
|
|
||||||
/// [0., 0., 4., 0.],
|
|
||||||
/// [0., 0., 0., 1.],
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(m.inverse_rtiow() * m, i);
|
|
||||||
/// assert_eq!(m * m.inverse_rtiow(), i);
|
|
||||||
/// ```
|
|
||||||
pub fn inverse_rtiow(&self) -> Matrix4x4 {
|
pub fn inverse_rtiow(&self) -> Matrix4x4 {
|
||||||
// TODO(wathiede): how come the C++ version doesn't need to deal with non-invertable
|
// TODO(wathiede): how come the C++ version doesn't need to deal with non-invertable
|
||||||
// matrix.
|
// matrix.
|
||||||
@ -639,22 +329,6 @@ impl Matrix4x4 {
|
|||||||
Matrix4x4 { m: minv }
|
Matrix4x4 { m: minv }
|
||||||
}
|
}
|
||||||
/// submatrix extracts a 3x3 matrix ignoring the 0-based `row` and `col` given.
|
/// submatrix extracts a 3x3 matrix ignoring the 0-based `row` and `col` given.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::{Matrix3x3, Matrix4x4};
|
|
||||||
///
|
|
||||||
/// assert_eq!(
|
|
||||||
/// Matrix4x4::new(
|
|
||||||
/// [-6., 1., 1., 6.],
|
|
||||||
/// [-8., 5., 8., 6.],
|
|
||||||
/// [-1., 0., 8., 2.],
|
|
||||||
/// [-7., 1., -1., 1.],
|
|
||||||
/// )
|
|
||||||
/// .submatrix(2, 1),
|
|
||||||
/// Matrix3x3::new([-6., 1., 6.], [-8., 8., 6.], [-7., -1., 1.],)
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub fn submatrix(&self, row: usize, col: usize) -> Matrix3x3 {
|
pub fn submatrix(&self, row: usize, col: usize) -> Matrix3x3 {
|
||||||
assert!(row < 4);
|
assert!(row < 4);
|
||||||
assert!(col < 4);
|
assert!(col < 4);
|
||||||
@ -688,133 +362,16 @@ impl Matrix4x4 {
|
|||||||
self.submatrix(row, col).determinant() * negate
|
self.submatrix(row, col).determinant() * negate
|
||||||
}
|
}
|
||||||
/// Compute determinant of a 4x4 matrix.
|
/// Compute determinant of a 4x4 matrix.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
///
|
|
||||||
/// let a = Matrix4x4::new(
|
|
||||||
/// [-2., -8., 3., 5.],
|
|
||||||
/// [-3., 1., 7., 3.],
|
|
||||||
/// [1., 2., -9., 6.],
|
|
||||||
/// [-6., 7., 7., -9.],
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(a.cofactor(0, 0), 690.);
|
|
||||||
/// assert_eq!(a.cofactor(0, 1), 447.);
|
|
||||||
/// assert_eq!(a.cofactor(0, 2), 210.);
|
|
||||||
/// assert_eq!(a.cofactor(0, 3), 51.);
|
|
||||||
/// assert_eq!(a.determinant(), -4071.);
|
|
||||||
/// ```
|
|
||||||
pub fn determinant(&self) -> Float {
|
pub fn determinant(&self) -> Float {
|
||||||
(0..4).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
|
(0..4).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute invertibility of matrix (i.e. non-zero determinant.
|
/// Compute invertibility of matrix (i.e. non-zero determinant.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
///
|
|
||||||
/// let a = Matrix4x4::new(
|
|
||||||
/// [6., 4., 4., 4.],
|
|
||||||
/// [5., 5., 7., 6.],
|
|
||||||
/// [4., -9., 3., -7.],
|
|
||||||
/// [9., 1., 7., -6.],
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(a.determinant(), -2120.);
|
|
||||||
/// assert_eq!(a.invertable(), true);
|
|
||||||
///
|
|
||||||
/// let a = Matrix4x4::new(
|
|
||||||
/// [-4., 2., -2., -3.],
|
|
||||||
/// [9., 6., 2., 6.],
|
|
||||||
/// [0., -5., 1., -5.],
|
|
||||||
/// [0., 0., 0., 0.],
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(a.determinant(), 0.);
|
|
||||||
/// assert_eq!(a.invertable(), false);
|
|
||||||
/// ```
|
|
||||||
pub fn invertable(&self) -> bool {
|
pub fn invertable(&self) -> bool {
|
||||||
self.determinant() != 0.
|
self.determinant() != 0.
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Compute the inverse of a 4x4 matrix.
|
/// Compute the inverse of a 4x4 matrix.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
///
|
|
||||||
/// let a = Matrix4x4::new(
|
|
||||||
/// [-5., 2., 6., -8.],
|
|
||||||
/// [1., -5., 1., 8.],
|
|
||||||
/// [7., 7., -6., -7.],
|
|
||||||
/// [1., -3., 7., 4.],
|
|
||||||
/// );
|
|
||||||
/// let b = a.inverse();
|
|
||||||
///
|
|
||||||
/// assert_eq!(a.determinant(), 532.);
|
|
||||||
/// assert_eq!(a.cofactor(2, 3), -160.);
|
|
||||||
/// assert_eq!(b[(3, 2)], -160. / 532.);
|
|
||||||
/// assert_eq!(a.cofactor(3, 2), 105.);
|
|
||||||
/// assert_eq!(b[(2, 3)], 105. / 532.);
|
|
||||||
/// assert_eq!(
|
|
||||||
/// b,
|
|
||||||
/// Matrix4x4::new(
|
|
||||||
/// [0.21804512, 0.45112783, 0.24060151, -0.04511278],
|
|
||||||
/// [-0.8082707, -1.456767, -0.44360903, 0.5206767],
|
|
||||||
/// [-0.078947365, -0.2236842, -0.05263158, 0.19736843],
|
|
||||||
/// [-0.52255636, -0.81390977, -0.30075186, 0.30639097]
|
|
||||||
/// )
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// // Second test case
|
|
||||||
/// assert_eq!(
|
|
||||||
/// Matrix4x4::new(
|
|
||||||
/// [8., -5., 9., 2.],
|
|
||||||
/// [7., 5., 6., 1.],
|
|
||||||
/// [-6., 0., 9., 6.],
|
|
||||||
/// [-3., 0., -9., -4.],
|
|
||||||
/// )
|
|
||||||
/// .inverse(),
|
|
||||||
/// Matrix4x4::new(
|
|
||||||
/// [-0.15384616, -0.15384616, -0.2820513, -0.53846157],
|
|
||||||
/// [-0.07692308, 0.12307692, 0.025641026, 0.03076923],
|
|
||||||
/// [0.35897437, 0.35897437, 0.43589744, 0.9230769],
|
|
||||||
/// [-0.6923077, -0.6923077, -0.7692308, -1.9230769]
|
|
||||||
/// ),
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// // Third test case
|
|
||||||
/// assert_eq!(
|
|
||||||
/// Matrix4x4::new(
|
|
||||||
/// [9., 3., 0., 9.],
|
|
||||||
/// [-5., -2., -6., -3.],
|
|
||||||
/// [-4., 9., 6., 4.],
|
|
||||||
/// [-7., 6., 6., 2.],
|
|
||||||
/// )
|
|
||||||
/// .inverse(),
|
|
||||||
/// Matrix4x4::new(
|
|
||||||
/// [-0.04074074, -0.07777778, 0.14444445, -0.22222222],
|
|
||||||
/// [-0.07777778, 0.033333335, 0.36666667, -0.33333334],
|
|
||||||
/// [-0.029012345, -0.14629629, -0.10925926, 0.12962963],
|
|
||||||
/// [0.17777778, 0.06666667, -0.26666668, 0.33333334]
|
|
||||||
/// ),
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// let a = Matrix4x4::new(
|
|
||||||
/// [3., -9., 7., 3.],
|
|
||||||
/// [3., -8., 2., -9.],
|
|
||||||
/// [-4., 4., 4., 1.],
|
|
||||||
/// [-6., 5., -1., 1.],
|
|
||||||
/// );
|
|
||||||
/// let b = Matrix4x4::new(
|
|
||||||
/// [8., 2., 2., 2.],
|
|
||||||
/// [3., -1., 7., 0.],
|
|
||||||
/// [7., 0., 5., 4.],
|
|
||||||
/// [6., -2., 0., 5.],
|
|
||||||
/// );
|
|
||||||
/// let c = a * b;
|
|
||||||
/// assert_eq!(c * b.inverse(), a);
|
|
||||||
/// ```
|
|
||||||
pub fn inverse(&self) -> Matrix4x4 {
|
pub fn inverse(&self) -> Matrix4x4 {
|
||||||
self.inverse_rtc()
|
self.inverse_rtc()
|
||||||
}
|
}
|
||||||
@ -857,17 +414,6 @@ impl Mul<Matrix4x4> for Matrix4x4 {
|
|||||||
type Output = Matrix4x4;
|
type Output = Matrix4x4;
|
||||||
|
|
||||||
/// Implement matrix multiplication for `Matrix4x4`.
|
/// Implement matrix multiplication for `Matrix4x4`.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
///
|
|
||||||
/// let i = Matrix4x4::identity();
|
|
||||||
/// let m1 = Matrix4x4::identity();
|
|
||||||
/// let m2 = Matrix4x4::identity();
|
|
||||||
///
|
|
||||||
/// assert_eq!(m1 * m2, i);
|
|
||||||
/// ```
|
|
||||||
fn mul(self, m2: Matrix4x4) -> Matrix4x4 {
|
fn mul(self, m2: Matrix4x4) -> Matrix4x4 {
|
||||||
let m1 = self;
|
let m1 = self;
|
||||||
let mut r: Matrix4x4 = Default::default();
|
let mut r: Matrix4x4 = Default::default();
|
||||||
@ -887,22 +433,6 @@ impl Mul<Tuple> for Matrix4x4 {
|
|||||||
type Output = Tuple;
|
type Output = Tuple;
|
||||||
|
|
||||||
/// Implement matrix multiplication for `Matrix4x4` * `Tuple`.
|
/// Implement matrix multiplication for `Matrix4x4` * `Tuple`.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::matrices::Matrix4x4;
|
|
||||||
/// use rtchallenge::tuples::Tuple;
|
|
||||||
///
|
|
||||||
/// let a = Matrix4x4::new(
|
|
||||||
/// [1., 2., 3., 4.],
|
|
||||||
/// [2., 4., 4., 2.],
|
|
||||||
/// [8., 6., 4., 1.],
|
|
||||||
/// [0., 0., 0., 1.],
|
|
||||||
/// );
|
|
||||||
/// let b = Tuple::new(1., 2., 3., 1.);
|
|
||||||
///
|
|
||||||
/// assert_eq!(a * b, Tuple::new(18., 24., 33., 1.));
|
|
||||||
/// ```
|
|
||||||
fn mul(self, t: Tuple) -> Tuple {
|
fn mul(self, t: Tuple) -> Tuple {
|
||||||
let m = self;
|
let m = self;
|
||||||
Tuple {
|
Tuple {
|
||||||
@ -962,6 +492,235 @@ impl IndexMut<(usize, usize)> for Matrix4x4 {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
float::consts::PI,
|
||||||
|
tuples::{point, vector},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example4x4() {
|
||||||
|
// Individual transformations are applied in sequence.
|
||||||
|
let p = point(1., 0., 1.);
|
||||||
|
let a = Matrix4x4::rotation_x(PI / 2.);
|
||||||
|
let b = Matrix4x4::scaling(5., 5., 5.);
|
||||||
|
let c = Matrix4x4::translation(10., 5., 7.);
|
||||||
|
// Apply rotation first.
|
||||||
|
let p2 = a * p;
|
||||||
|
assert_eq!(p2, point(1., -1., 0.));
|
||||||
|
// Then apply scaling.
|
||||||
|
let p3 = b * p2;
|
||||||
|
assert_eq!(p3, point(5., -5., 0.));
|
||||||
|
// Then apply translation.
|
||||||
|
let p4 = c * p3;
|
||||||
|
assert_eq!(p4, point(15., 0., 7.));
|
||||||
|
|
||||||
|
// Chained transformations must be applied in reverse order.
|
||||||
|
let p = point(1., 0., 1.);
|
||||||
|
let a = Matrix4x4::rotation_x(PI / 2.);
|
||||||
|
let b = Matrix4x4::scaling(5., 5., 5.);
|
||||||
|
let c = Matrix4x4::translation(10., 5., 7.);
|
||||||
|
let t = c * b * a;
|
||||||
|
assert_eq!(t * p, point(15., 0., 7.));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn translation() {
|
||||||
|
let transform = Matrix4x4::translation(5., -3., 2.);
|
||||||
|
let p = point(-3., 4., 5.);
|
||||||
|
assert_eq!(transform * p, point(2., 1., 7.));
|
||||||
|
|
||||||
|
let inv = transform.inverse();
|
||||||
|
assert_eq!(inv * p, point(-8., 7., 3.));
|
||||||
|
|
||||||
|
let v = vector(-3., 4., 5.);
|
||||||
|
assert_eq!(transform * v, v);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn scaling() {
|
||||||
|
// A scaling matrix applied to a point.
|
||||||
|
let transform = Matrix4x4::scaling(2., 3., 4.);
|
||||||
|
let p = point(-4., 6., 8.);
|
||||||
|
assert_eq!(transform * p, point(-8., 18., 32.));
|
||||||
|
|
||||||
|
// A scaling matrix applied to a vector.
|
||||||
|
let v = vector(-4., 6., 8.);
|
||||||
|
assert_eq!(transform * v, vector(-8., 18., 32.));
|
||||||
|
|
||||||
|
// Multiplying by the inverse of a scaling matrix.
|
||||||
|
let inv = transform.inverse();
|
||||||
|
assert_eq!(inv * v, vector(-2., 2., 2.));
|
||||||
|
|
||||||
|
// Reflection is scaling by a negative value.
|
||||||
|
let transform = Matrix4x4::scaling(-1., 1., 1.);
|
||||||
|
let p = point(2., 3., 4.);
|
||||||
|
assert_eq!(transform * p, point(-2., 3., 4.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn rotation_x() {
|
||||||
|
// A scaling matrix applied to a point.
|
||||||
|
let p = point(0., 1., 0.);
|
||||||
|
let half_quarter = Matrix4x4::rotation_x(PI / 4.);
|
||||||
|
let full_quarter = Matrix4x4::rotation_x(PI / 2.);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
half_quarter * p,
|
||||||
|
point(0., (2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2.)
|
||||||
|
);
|
||||||
|
assert_eq!(full_quarter * p, point(0., 0., 1.),);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn rotation_y() {
|
||||||
|
// A scaling matrix applied to a point.
|
||||||
|
let p = point(0., 0., 1.);
|
||||||
|
let half_quarter = Matrix4x4::rotation_y(PI / 4.);
|
||||||
|
let full_quarter = Matrix4x4::rotation_y(PI / 2.);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
half_quarter * p,
|
||||||
|
point((2.0 as Float).sqrt() / 2., 0., (2.0 as Float).sqrt() / 2.)
|
||||||
|
);
|
||||||
|
assert_eq!(full_quarter * p, point(1., 0., 0.,),);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn rotation_z() {
|
||||||
|
// A scaling matrix applied to a point.
|
||||||
|
let p = point(0., 1., 0.);
|
||||||
|
let half_quarter = Matrix4x4::rotation_z(PI / 4.);
|
||||||
|
let full_quarter = Matrix4x4::rotation_z(PI / 2.);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
half_quarter * p,
|
||||||
|
point(-(2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2., 0.)
|
||||||
|
);
|
||||||
|
assert_eq!(full_quarter * p, point(-1., 0., 0.,),);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn transpose() {
|
||||||
|
let m = Matrix4x4::new(
|
||||||
|
[2., 0., 0., 0.],
|
||||||
|
[3., 1., 0., 0.],
|
||||||
|
[4., 0., 1., 0.],
|
||||||
|
[5., 6., 7., 1.],
|
||||||
|
);
|
||||||
|
let m_t = Matrix4x4::new(
|
||||||
|
[2., 3., 4., 5.],
|
||||||
|
[0., 1., 0., 6.],
|
||||||
|
[0., 0., 1., 7.],
|
||||||
|
[0., 0., 0., 1.],
|
||||||
|
);
|
||||||
|
assert_eq!(m.transpose(), m_t);
|
||||||
|
|
||||||
|
assert_eq!(Matrix4x4::identity(), Matrix4x4::identity().transpose());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn shearing() {
|
||||||
|
// A shearing transform moves x in proportion to y.
|
||||||
|
let transform = Matrix4x4::shearing(1., 0., 0., 0., 0., 0.);
|
||||||
|
let p = point(2., 3., 4.);
|
||||||
|
assert_eq!(transform * p, point(5., 3., 4.));
|
||||||
|
|
||||||
|
// A shearing transform moves x in proportion to z.
|
||||||
|
let transform = Matrix4x4::shearing(0., 1., 0., 0., 0., 0.);
|
||||||
|
let p = point(2., 3., 4.);
|
||||||
|
assert_eq!(transform * p, point(6., 3., 4.));
|
||||||
|
|
||||||
|
// A shearing transform moves y in proportion to x.
|
||||||
|
let transform = Matrix4x4::shearing(0., 0., 1., 0., 0., 0.);
|
||||||
|
let p = point(2., 3., 4.);
|
||||||
|
assert_eq!(transform * p, point(2., 5., 4.));
|
||||||
|
|
||||||
|
// A shearing transform moves y in proportion to z.
|
||||||
|
let transform = Matrix4x4::shearing(0., 0., 0., 1., 0., 0.);
|
||||||
|
let p = point(2., 3., 4.);
|
||||||
|
assert_eq!(transform * p, point(2., 7., 4.));
|
||||||
|
|
||||||
|
// A shearing transform moves z in proportion to x.
|
||||||
|
let transform = Matrix4x4::shearing(0., 0., 0., 0., 1., 0.);
|
||||||
|
let p = point(2., 3., 4.);
|
||||||
|
assert_eq!(transform * p, point(2., 3., 6.));
|
||||||
|
|
||||||
|
// A shearing transform moves z in proportion to y.
|
||||||
|
let transform = Matrix4x4::shearing(0., 0., 0., 0., 0., 1.);
|
||||||
|
let p = point(2., 3., 4.);
|
||||||
|
assert_eq!(transform * p, point(2., 3., 7.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn inverse_rtiow() {
|
||||||
|
let i = Matrix4x4::identity();
|
||||||
|
assert_eq!(i.inverse_rtiow() * i, i);
|
||||||
|
|
||||||
|
let m = Matrix4x4::new(
|
||||||
|
[2., 0., 0., 0.],
|
||||||
|
[0., 3., 0., 0.],
|
||||||
|
[0., 0., 4., 0.],
|
||||||
|
[0., 0., 0., 1.],
|
||||||
|
);
|
||||||
|
assert_eq!(m.inverse_rtiow() * m, i);
|
||||||
|
assert_eq!(m * m.inverse_rtiow(), i);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn determinant_2x2() {
|
||||||
|
let a = Matrix2x2::new([1., 5.], [-3., 2.]);
|
||||||
|
|
||||||
|
assert_eq!(a.determinant(), 17.);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn determinant_3x3() {
|
||||||
|
let a = Matrix3x3::new([1., 2., 6.], [-5., 8., -4.], [2., 6., 4.]);
|
||||||
|
assert_eq!(a.cofactor(0, 0), 56.);
|
||||||
|
assert_eq!(a.cofactor(0, 1), 12.);
|
||||||
|
assert_eq!(a.cofactor(0, 2), -46.);
|
||||||
|
assert_eq!(a.determinant(), -196.);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn determinant_4x4() {
|
||||||
|
let a = Matrix4x4::new(
|
||||||
|
[-2., -8., 3., 5.],
|
||||||
|
[-3., 1., 7., 3.],
|
||||||
|
[1., 2., -9., 6.],
|
||||||
|
[-6., 7., 7., -9.],
|
||||||
|
);
|
||||||
|
assert_eq!(a.cofactor(0, 0), 690.);
|
||||||
|
assert_eq!(a.cofactor(0, 1), 447.);
|
||||||
|
assert_eq!(a.cofactor(0, 2), 210.);
|
||||||
|
assert_eq!(a.cofactor(0, 3), 51.);
|
||||||
|
assert_eq!(a.determinant(), -4071.);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn submatrix_3x3() {
|
||||||
|
assert_eq!(
|
||||||
|
Matrix3x3::new([1., 5., 0.], [-3., 2., 7.], [0., 6., -3.],).submatrix(0, 2),
|
||||||
|
Matrix2x2::new([-3., 2.], [0., 6.])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn submatrix_4x4() {
|
||||||
|
assert_eq!(
|
||||||
|
Matrix4x4::new(
|
||||||
|
[-6., 1., 1., 6.],
|
||||||
|
[-8., 5., 8., 6.],
|
||||||
|
[-1., 0., 8., 2.],
|
||||||
|
[-7., 1., -1., 1.],
|
||||||
|
)
|
||||||
|
.submatrix(2, 1),
|
||||||
|
Matrix3x3::new([-6., 1., 6.], [-8., 8., 6.], [-7., -1., 1.],)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn minor_3x3() {
|
||||||
|
let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
|
||||||
|
let b = a.submatrix(1, 0);
|
||||||
|
assert_eq!(b.determinant(), 25.0);
|
||||||
|
assert_eq!(b.determinant(), a.minor(1, 0));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn cofactor_3x3() {
|
||||||
|
let a = Matrix3x3::new([3., 5., 0.], [2., -1., -7.], [6., -1., 5.]);
|
||||||
|
assert_eq!(a.minor(0, 0), -12.);
|
||||||
|
assert_eq!(a.cofactor(0, 0), -12.);
|
||||||
|
assert_eq!(a.minor(1, 0), 25.);
|
||||||
|
assert_eq!(a.cofactor(1, 0), -25.);
|
||||||
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn construct2x2() {
|
fn construct2x2() {
|
||||||
let m = Matrix2x2::new([-3., 5.], [1., -2.]);
|
let m = Matrix2x2::new([-3., 5.], [1., -2.]);
|
||||||
@ -980,6 +739,100 @@ mod tests {
|
|||||||
assert_eq!(m[(2, 2)], 1.);
|
assert_eq!(m[(2, 2)], 1.);
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
|
fn invertable() {
|
||||||
|
let a = Matrix4x4::new(
|
||||||
|
[6., 4., 4., 4.],
|
||||||
|
[5., 5., 7., 6.],
|
||||||
|
[4., -9., 3., -7.],
|
||||||
|
[9., 1., 7., -6.],
|
||||||
|
);
|
||||||
|
assert_eq!(a.determinant(), -2120.);
|
||||||
|
assert_eq!(a.invertable(), true);
|
||||||
|
|
||||||
|
let a = Matrix4x4::new(
|
||||||
|
[-4., 2., -2., -3.],
|
||||||
|
[9., 6., 2., 6.],
|
||||||
|
[0., -5., 1., -5.],
|
||||||
|
[0., 0., 0., 0.],
|
||||||
|
);
|
||||||
|
assert_eq!(a.determinant(), 0.);
|
||||||
|
assert_eq!(a.invertable(), false);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn inverse() {
|
||||||
|
let a = Matrix4x4::new(
|
||||||
|
[-5., 2., 6., -8.],
|
||||||
|
[1., -5., 1., 8.],
|
||||||
|
[7., 7., -6., -7.],
|
||||||
|
[1., -3., 7., 4.],
|
||||||
|
);
|
||||||
|
let b = a.inverse();
|
||||||
|
|
||||||
|
assert_eq!(a.determinant(), 532.);
|
||||||
|
assert_eq!(a.cofactor(2, 3), -160.);
|
||||||
|
assert_eq!(b[(3, 2)], -160. / 532.);
|
||||||
|
assert_eq!(a.cofactor(3, 2), 105.);
|
||||||
|
assert_eq!(b[(2, 3)], 105. / 532.);
|
||||||
|
assert_eq!(
|
||||||
|
b,
|
||||||
|
Matrix4x4::new(
|
||||||
|
[0.21804512, 0.45112783, 0.24060151, -0.04511278],
|
||||||
|
[-0.8082707, -1.456767, -0.44360903, 0.5206767],
|
||||||
|
[-0.078947365, -0.2236842, -0.05263158, 0.19736843],
|
||||||
|
[-0.52255636, -0.81390977, -0.30075186, 0.30639097]
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
// Second test case
|
||||||
|
assert_eq!(
|
||||||
|
Matrix4x4::new(
|
||||||
|
[8., -5., 9., 2.],
|
||||||
|
[7., 5., 6., 1.],
|
||||||
|
[-6., 0., 9., 6.],
|
||||||
|
[-3., 0., -9., -4.],
|
||||||
|
)
|
||||||
|
.inverse(),
|
||||||
|
Matrix4x4::new(
|
||||||
|
[-0.15384616, -0.15384616, -0.2820513, -0.53846157],
|
||||||
|
[-0.07692308, 0.12307692, 0.025641026, 0.03076923],
|
||||||
|
[0.35897437, 0.35897437, 0.43589744, 0.9230769],
|
||||||
|
[-0.6923077, -0.6923077, -0.7692308, -1.9230769]
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Third test case
|
||||||
|
assert_eq!(
|
||||||
|
Matrix4x4::new(
|
||||||
|
[9., 3., 0., 9.],
|
||||||
|
[-5., -2., -6., -3.],
|
||||||
|
[-4., 9., 6., 4.],
|
||||||
|
[-7., 6., 6., 2.],
|
||||||
|
)
|
||||||
|
.inverse(),
|
||||||
|
Matrix4x4::new(
|
||||||
|
[-0.04074074, -0.07777778, 0.14444445, -0.22222222],
|
||||||
|
[-0.07777778, 0.033333335, 0.36666667, -0.33333334],
|
||||||
|
[-0.029012345, -0.14629629, -0.10925926, 0.12962963],
|
||||||
|
[0.17777778, 0.06666667, -0.26666668, 0.33333334]
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
let a = Matrix4x4::new(
|
||||||
|
[3., -9., 7., 3.],
|
||||||
|
[3., -8., 2., -9.],
|
||||||
|
[-4., 4., 4., 1.],
|
||||||
|
[-6., 5., -1., 1.],
|
||||||
|
);
|
||||||
|
let b = Matrix4x4::new(
|
||||||
|
[8., 2., 2., 2.],
|
||||||
|
[3., -1., 7., 0.],
|
||||||
|
[7., 0., 5., 4.],
|
||||||
|
[6., -2., 0., 5.],
|
||||||
|
);
|
||||||
|
let c = a * b;
|
||||||
|
assert_eq!(c * b.inverse(), a);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
fn construct4x4() {
|
fn construct4x4() {
|
||||||
let m = Matrix4x4::new(
|
let m = Matrix4x4::new(
|
||||||
[1., 2., 3., 4.],
|
[1., 2., 3., 4.],
|
||||||
@ -1052,4 +905,16 @@ mod tests {
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn mul4x4_tuple() {
|
||||||
|
let a = Matrix4x4::new(
|
||||||
|
[1., 2., 3., 4.],
|
||||||
|
[2., 4., 4., 2.],
|
||||||
|
[8., 6., 4., 1.],
|
||||||
|
[0., 0., 0., 1.],
|
||||||
|
);
|
||||||
|
let b = Tuple::new(1., 2., 3., 1.);
|
||||||
|
|
||||||
|
assert_eq!(a * b, Tuple::new(18., 24., 33., 1.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
361
rtchallenge/src/patterns.rs
Normal file
361
rtchallenge/src/patterns.rs
Normal file
@ -0,0 +1,361 @@
|
|||||||
|
use derive_builder::Builder;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
matrices::Matrix4x4,
|
||||||
|
shapes::Shape,
|
||||||
|
tuples::{Color, Tuple},
|
||||||
|
WHITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Copy, 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 },
|
||||||
|
/// Linear blend between `a` and `b` along the X-axis.
|
||||||
|
Gradient { a: Color, b: Color },
|
||||||
|
/// Bullseye pattern in the XZ plane.
|
||||||
|
Ring { a: Color, b: Color },
|
||||||
|
/// Traditional ray tracer tile floor pattern.
|
||||||
|
Checkers { a: Color, b: Color },
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Builder, Debug, PartialEq, Clone)]
|
||||||
|
#[builder(default, pattern = "owned")]
|
||||||
|
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: 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.
|
||||||
|
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.
|
||||||
|
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.
|
||||||
|
pub fn checkers_pattern(a: Color, b: Color) -> PatternBuilder {
|
||||||
|
PatternBuilder::default().color(ColorMapper::Checkers { a, b })
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 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 colors 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 {
|
||||||
|
Pattern {
|
||||||
|
color: ColorMapper::Stripe { a, b },
|
||||||
|
..Pattern::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Create a pattern that gradually blends between the given colors along the X-axis.
|
||||||
|
pub fn gradient(a: Color, b: Color) -> Pattern {
|
||||||
|
Pattern {
|
||||||
|
color: ColorMapper::Gradient { a, b },
|
||||||
|
..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 {
|
||||||
|
Pattern {
|
||||||
|
color: ColorMapper::Ring { a, b },
|
||||||
|
..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 {
|
||||||
|
Pattern {
|
||||||
|
color: ColorMapper::Checkers { a, b },
|
||||||
|
..Pattern::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample the color at the given point in untranslated object space.
|
||||||
|
pub fn pattern_at(&self, point: Tuple) -> Color {
|
||||||
|
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
|
||||||
|
} else {
|
||||||
|
b
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ColorMapper::Gradient { a, b } => {
|
||||||
|
let distance = b - a;
|
||||||
|
let fraction = point.x - point.x.floor();
|
||||||
|
a + distance * fraction
|
||||||
|
}
|
||||||
|
ColorMapper::Ring { a, b } => {
|
||||||
|
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 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;
|
||||||
|
let pattern_point = self.inverse_transform * object_point;
|
||||||
|
self.pattern_at(pattern_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, 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(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,17 +10,6 @@ pub struct Ray {
|
|||||||
impl Ray {
|
impl Ray {
|
||||||
/// Create a ray with the given origin point and direction vector.
|
/// Create a ray with the given origin point and direction vector.
|
||||||
/// Will panic if origin not a point or direction not a vector.
|
/// Will panic if origin not a point or direction not a vector.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{rays::Ray, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// let origin = Tuple::point(1., 2., 3.);
|
|
||||||
/// let direction = Tuple::vector(4., 5., 6.);
|
|
||||||
/// let r = Ray::new(origin, direction);
|
|
||||||
/// assert_eq!(r.origin, origin);
|
|
||||||
/// assert_eq!(r.direction, direction);
|
|
||||||
/// ```
|
|
||||||
pub fn new(origin: Tuple, direction: Tuple) -> Ray {
|
pub fn new(origin: Tuple, direction: Tuple) -> Ray {
|
||||||
assert!(origin.is_point(), "Ray origin must be a point");
|
assert!(origin.is_point(), "Ray origin must be a point");
|
||||||
assert!(direction.is_vector(), "Ray direction must be a vector");
|
assert!(direction.is_vector(), "Ray direction must be a vector");
|
||||||
@ -28,41 +17,11 @@ impl Ray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute a point from the given distance along the `Ray`.
|
/// Compute a point from the given distance along the `Ray`.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{rays::Ray, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// let r = Ray::new(Tuple::point(2., 3., 4.), Tuple::vector(1., 0., 0.));
|
|
||||||
/// assert_eq!(r.position(0.), Tuple::point(2., 3., 4.));
|
|
||||||
/// assert_eq!(r.position(1.), Tuple::point(3., 3., 4.));
|
|
||||||
/// assert_eq!(r.position(-1.), Tuple::point(1., 3., 4.));
|
|
||||||
/// assert_eq!(r.position(2.5), Tuple::point(4.5, 3., 4.));
|
|
||||||
/// ```
|
|
||||||
pub fn position(&self, t: Float) -> Tuple {
|
pub fn position(&self, t: Float) -> Tuple {
|
||||||
self.origin + self.direction * t
|
self.origin + self.direction * t
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Apply Matrix4x4 transforms to Ray.
|
/// Apply Matrix4x4 transforms to Ray.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{matrices::Matrix4x4, rays::Ray, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// // Translating a ray
|
|
||||||
/// let r = Ray::new(Tuple::point(1., 2., 3.), Tuple::vector(0., 1., 0.));
|
|
||||||
/// let m = Matrix4x4::translation(3., 4., 5.);
|
|
||||||
/// let r2 = r.transform(m);
|
|
||||||
/// assert_eq!(r2.origin, Tuple::point(4., 6., 8.));
|
|
||||||
/// assert_eq!(r2.direction, Tuple::vector(0., 1., 0.));
|
|
||||||
///
|
|
||||||
/// // Scaling a ray
|
|
||||||
/// let r = Ray::new(Tuple::point(1., 2., 3.), Tuple::vector(0., 1., 0.));
|
|
||||||
/// let m = Matrix4x4::scaling(2., 3., 4.);
|
|
||||||
/// let r2 = r.transform(m);
|
|
||||||
/// assert_eq!(r2.origin, Tuple::point(2., 6., 12.));
|
|
||||||
/// assert_eq!(r2.direction, Tuple::vector(0., 3., 0.));
|
|
||||||
/// ```
|
|
||||||
pub fn transform(&self, m: Matrix4x4) -> Ray {
|
pub fn transform(&self, m: Matrix4x4) -> Ray {
|
||||||
Ray {
|
Ray {
|
||||||
origin: m * self.origin,
|
origin: m * self.origin,
|
||||||
@ -70,3 +29,46 @@ impl Ray {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
matrices::Matrix4x4,
|
||||||
|
rays::Ray,
|
||||||
|
tuples::{point, vector},
|
||||||
|
};
|
||||||
|
#[test]
|
||||||
|
fn create() {
|
||||||
|
let origin = point(1., 2., 3.);
|
||||||
|
let direction = vector(4., 5., 6.);
|
||||||
|
let r = Ray::new(origin, direction);
|
||||||
|
assert_eq!(r.origin, origin);
|
||||||
|
assert_eq!(r.direction, direction);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn position() {
|
||||||
|
let r = Ray::new(point(2., 3., 4.), vector(1., 0., 0.));
|
||||||
|
assert_eq!(r.position(0.), point(2., 3., 4.));
|
||||||
|
assert_eq!(r.position(1.), point(3., 3., 4.));
|
||||||
|
assert_eq!(r.position(-1.), point(1., 3., 4.));
|
||||||
|
assert_eq!(r.position(2.5), point(4.5, 3., 4.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn transform_translating_ray() {
|
||||||
|
// Translating a ray
|
||||||
|
let r = Ray::new(point(1., 2., 3.), vector(0., 1., 0.));
|
||||||
|
let m = Matrix4x4::translation(3., 4., 5.);
|
||||||
|
let r2 = r.transform(m);
|
||||||
|
assert_eq!(r2.origin, point(4., 6., 8.));
|
||||||
|
assert_eq!(r2.direction, vector(0., 1., 0.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn transform_scaling_ray() {
|
||||||
|
// Scaling a ray
|
||||||
|
let r = Ray::new(point(1., 2., 3.), vector(0., 1., 0.));
|
||||||
|
let m = Matrix4x4::scaling(2., 3., 4.);
|
||||||
|
let r2 = r.transform(m);
|
||||||
|
assert_eq!(r2.origin, point(2., 6., 12.));
|
||||||
|
assert_eq!(r2.direction, vector(0., 3., 0.));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,7 +3,10 @@ use std::sync::{Arc, Mutex};
|
|||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
intersections::Intersections, materials::Material, matrices::Matrix4x4, rays::Ray,
|
intersections::Intersections,
|
||||||
|
materials::{Material, MaterialBuilder},
|
||||||
|
matrices::Matrix4x4,
|
||||||
|
rays::Ray,
|
||||||
tuples::Tuple,
|
tuples::Tuple,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -54,87 +57,39 @@ pub struct Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{plane, Shape};
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(plane().build()?, Shape::plane());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn plane() -> ShapeBuilder {
|
pub fn plane() -> ShapeBuilder {
|
||||||
ShapeBuilder::plane()
|
ShapeBuilder::plane()
|
||||||
}
|
}
|
||||||
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{sphere, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(sphere().build()?, Shape::sphere());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn sphere() -> ShapeBuilder {
|
pub fn sphere() -> ShapeBuilder {
|
||||||
ShapeBuilder::sphere()
|
ShapeBuilder::sphere()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{test_shape, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(test_shape().build()?, Shape::test_shape());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn test_shape() -> ShapeBuilder {
|
pub fn test_shape() -> ShapeBuilder {
|
||||||
ShapeBuilder::test_shape()
|
ShapeBuilder::test_shape()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Helper for producing a sphere with a glassy material.
|
||||||
|
pub fn glass_sphere() -> ShapeBuilder {
|
||||||
|
ShapeBuilder::sphere().material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.transparency(1.)
|
||||||
|
.refractive_index(1.5)
|
||||||
|
.build()
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}
|
||||||
impl ShapeBuilder {
|
impl ShapeBuilder {
|
||||||
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
/// Short hand for creating a ShapeBuilder with a plane geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{plane, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(plane().build()?, Shape::plane());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn plane() -> ShapeBuilder {
|
pub fn plane() -> ShapeBuilder {
|
||||||
ShapeBuilder::default().geometry(Geometry::Plane)
|
ShapeBuilder::default().geometry(Geometry::Plane)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
/// Short hand for creating a ShapeBuilder with a sphere geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{sphere, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(sphere().build()?, Shape::sphere());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn sphere() -> ShapeBuilder {
|
pub fn sphere() -> ShapeBuilder {
|
||||||
ShapeBuilder::default().geometry(Geometry::Sphere)
|
ShapeBuilder::default().geometry(Geometry::Sphere)
|
||||||
}
|
}
|
||||||
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
/// Short hand for creating a ShapeBuilder with a test shape geometry.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::shapes::{test_shape, Shape};
|
|
||||||
///
|
|
||||||
/// # fn main() -> Result<(), Box<std::error::Error>> {
|
|
||||||
/// assert_eq!(test_shape().build()?, Shape::test_shape());
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
pub fn test_shape() -> ShapeBuilder {
|
pub fn test_shape() -> ShapeBuilder {
|
||||||
ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new(
|
ShapeBuilder::default().geometry(Geometry::TestShape(Arc::new(Mutex::new(
|
||||||
TestData::default(),
|
TestData::default(),
|
||||||
@ -158,24 +113,6 @@ impl Default for Shape {
|
|||||||
|
|
||||||
impl Shape {
|
impl Shape {
|
||||||
/// Create a test shape useful for debugging.
|
/// Create a test shape useful for debugging.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape};
|
|
||||||
///
|
|
||||||
/// let mut s = Shape::test_shape();
|
|
||||||
/// // The default transform.
|
|
||||||
/// assert_eq!(s.transform(), Matrix4x4::identity());
|
|
||||||
/// // The default material.
|
|
||||||
/// assert_eq!(s.material, Material::default());
|
|
||||||
/// // Assigning a material.
|
|
||||||
/// let mut m = Material {
|
|
||||||
/// ambient: 1.,
|
|
||||||
/// ..Material::default()
|
|
||||||
/// };
|
|
||||||
/// s.material = m.clone();
|
|
||||||
/// assert_eq!(s.material, m);
|
|
||||||
/// ```
|
|
||||||
pub fn test_shape() -> Shape {
|
pub fn test_shape() -> Shape {
|
||||||
Shape {
|
Shape {
|
||||||
transform: Matrix4x4::identity(),
|
transform: Matrix4x4::identity(),
|
||||||
@ -185,28 +122,6 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// # Examples
|
/// # Examples
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{materials::Material, matrices::Matrix4x4, shapes::Shape};
|
|
||||||
///
|
|
||||||
/// // A sphere's default transform is the identity matrix.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// assert_eq!(s.transform(), Matrix4x4::identity());
|
|
||||||
///
|
|
||||||
/// // It can be changed by directly setting the transform member.
|
|
||||||
/// let mut s = Shape::sphere();
|
|
||||||
/// let t = Matrix4x4::translation(2., 3., 4.);
|
|
||||||
/// s.set_transform(t.clone());
|
|
||||||
/// assert_eq!(s.transform(), t);
|
|
||||||
///
|
|
||||||
/// // Default Sphere has the default material.
|
|
||||||
/// assert_eq!(s.material, Material::default());
|
|
||||||
/// // It can be overridden.
|
|
||||||
/// let mut s = Shape::sphere();
|
|
||||||
/// let mut m = Material::default();
|
|
||||||
/// m.ambient = 1.;
|
|
||||||
/// s.material = m.clone();
|
|
||||||
/// assert_eq!(s.material, m);
|
|
||||||
/// ```
|
|
||||||
pub fn sphere() -> Shape {
|
pub fn sphere() -> Shape {
|
||||||
Shape {
|
Shape {
|
||||||
transform: Matrix4x4::identity(),
|
transform: Matrix4x4::identity(),
|
||||||
@ -224,101 +139,6 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
/// Find the normal at the point on the sphere.
|
/// Find the normal at the point on the sphere.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// float::consts::PI, materials::Material, matrices::Matrix4x4, shapes::Shape, tuples::Tuple,
|
|
||||||
/// Float,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Computing the normal on a translated shape.
|
|
||||||
/// let mut s = Shape::test_shape();
|
|
||||||
/// s.set_transform(Matrix4x4::translation(0., 1., 0.));
|
|
||||||
/// let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711));
|
|
||||||
/// assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
|
|
||||||
///
|
|
||||||
/// // Computing the normal on a transform shape.
|
|
||||||
/// let mut s = Shape::test_shape();
|
|
||||||
/// s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.));
|
|
||||||
/// let n = s.normal_at(Tuple::point(
|
|
||||||
/// 0.,
|
|
||||||
/// (2. as Float).sqrt() / 2.,
|
|
||||||
/// -(2. as Float).sqrt() / 2.,
|
|
||||||
/// ));
|
|
||||||
/// assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
|
|
||||||
///
|
|
||||||
/// // Normal on X-axis
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let n = s.normal_at(Tuple::point(1., 0., 0.));
|
|
||||||
/// assert_eq!(n, Tuple::vector(1., 0., 0.));
|
|
||||||
///
|
|
||||||
/// // Normal on Y-axis
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let n = s.normal_at(Tuple::point(0., 1., 0.));
|
|
||||||
/// assert_eq!(n, Tuple::vector(0., 1., 0.));
|
|
||||||
///
|
|
||||||
/// // Normal on Z-axis
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let n = s.normal_at(Tuple::point(0., 0., 1.));
|
|
||||||
/// assert_eq!(n, Tuple::vector(0., 0., 1.));
|
|
||||||
///
|
|
||||||
/// // Normal on a sphere at a nonaxial point.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let n = s.normal_at(Tuple::point(
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// ));
|
|
||||||
/// assert_eq!(
|
|
||||||
/// n,
|
|
||||||
/// Tuple::vector(
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// )
|
|
||||||
/// );
|
|
||||||
/// // Normals returned are normalized.
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let n = s.normal_at(Tuple::point(
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// (3. as Float).sqrt() / 3.,
|
|
||||||
/// ));
|
|
||||||
/// assert_eq!(n, n.normalize());
|
|
||||||
///
|
|
||||||
/// // Compute the normal on a translated sphere.
|
|
||||||
/// let mut s = Shape::sphere();
|
|
||||||
/// s.set_transform(Matrix4x4::translation(0., 1., 0.));
|
|
||||||
/// let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711));
|
|
||||||
/// assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
|
|
||||||
///
|
|
||||||
/// // Compute the normal on a transformed sphere.
|
|
||||||
/// let mut s = Shape::sphere();
|
|
||||||
/// s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.));
|
|
||||||
/// let n = s.normal_at(Tuple::point(
|
|
||||||
/// 0.,
|
|
||||||
/// (2. as Float).sqrt() / 2.,
|
|
||||||
/// -(2. as Float).sqrt() / 2.,
|
|
||||||
/// ));
|
|
||||||
/// assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
|
|
||||||
///
|
|
||||||
/// // Normal of a plane is constant everywhere.
|
|
||||||
/// let p = Shape::plane();
|
|
||||||
/// assert_eq!(
|
|
||||||
/// p.normal_at(Tuple::point(0., 0., 0.)),
|
|
||||||
/// Tuple::vector(0., 1., 0.)
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(
|
|
||||||
/// p.normal_at(Tuple::point(10., 0., -10.)),
|
|
||||||
/// Tuple::vector(0., 1., 0.)
|
|
||||||
/// );
|
|
||||||
/// assert_eq!(
|
|
||||||
/// p.normal_at(Tuple::point(-5., 0., 150.)),
|
|
||||||
/// Tuple::vector(0., 1., 0.)
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
#[cfg(not(feature = "disable-inverse-cache"))]
|
|
||||||
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
||||||
let object_point = self.inverse_transform * world_point;
|
let object_point = self.inverse_transform * world_point;
|
||||||
let object_normal = match self.geometry {
|
let object_normal = match self.geometry {
|
||||||
@ -330,23 +150,15 @@ impl Shape {
|
|||||||
world_normal.w = 0.;
|
world_normal.w = 0.;
|
||||||
world_normal.normalize()
|
world_normal.normalize()
|
||||||
}
|
}
|
||||||
#[cfg(feature = "disable-inverse-cache")]
|
|
||||||
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
|
||||||
let object_point = self.transform.inverse() * world_point;
|
|
||||||
let object_normal = match self.geometry {
|
|
||||||
Geometry::Sphere => object_point - Tuple::point(0., 0., 0.),
|
|
||||||
Geometry::Plane => Tuple::vector(0., 1., 0.),
|
|
||||||
Geometry::TestShape(_) => todo!("test shape normal"),
|
|
||||||
};
|
|
||||||
let mut world_normal = self.transform.inverse().transpose() * object_normal;
|
|
||||||
world_normal.w = 0.;
|
|
||||||
world_normal.normalize()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn transform(&self) -> Matrix4x4 {
|
pub fn transform(&self) -> Matrix4x4 {
|
||||||
self.transform
|
self.transform
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn inverse_transform(&self) -> Matrix4x4 {
|
||||||
|
self.inverse_transform
|
||||||
|
}
|
||||||
|
|
||||||
pub fn set_transform(&mut self, t: Matrix4x4) {
|
pub fn set_transform(&mut self, t: Matrix4x4) {
|
||||||
self.transform = t;
|
self.transform = t;
|
||||||
self.inverse_transform = t.inverse();
|
self.inverse_transform = t.inverse();
|
||||||
@ -357,123 +169,6 @@ impl Shape {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Intersect a ray with a shapes.
|
/// Intersect a ray with a shapes.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// intersections::{Intersection, Intersections},
|
|
||||||
/// matrices::Matrix4x4,
|
|
||||||
/// rays::Ray,
|
|
||||||
/// shapes::{intersect, Geometry, Shape},
|
|
||||||
/// tuples::Tuple,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Intersecting a scaled shape with a ray.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let mut s = Shape::test_shape();
|
|
||||||
/// s.set_transform(Matrix4x4::scaling(2., 2., 2.));
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// if let Geometry::TestShape(data) = s.geometry() {
|
|
||||||
/// if let Some(ray) = &data.lock().unwrap().saved_ray {
|
|
||||||
/// assert_eq!(ray.origin, Tuple::point(0., 0., -2.5));
|
|
||||||
/// assert_eq!(ray.direction, Tuple::vector(0., 0., 0.5));
|
|
||||||
/// } else {
|
|
||||||
/// panic!("ray wasn't set");
|
|
||||||
/// };
|
|
||||||
/// } else {
|
|
||||||
/// panic!("test_shape returned a non-TestShape geometry")
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Intersecting a translated shape with a ray.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let mut s = Shape::test_shape();
|
|
||||||
/// s.set_transform(Matrix4x4::translation(5., 0., 0.));
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// if let Geometry::TestShape(data) = s.geometry() {
|
|
||||||
/// if let Some(ray) = &data.lock().unwrap().saved_ray {
|
|
||||||
/// assert_eq!(ray.origin, Tuple::point(-5., 0., -5.));
|
|
||||||
/// assert_eq!(ray.direction, Tuple::vector(0., 0., 1.));
|
|
||||||
/// } else {
|
|
||||||
/// panic!("ray wasn't set");
|
|
||||||
/// };
|
|
||||||
/// } else {
|
|
||||||
/// panic!("test_shape returned a non-TestShape geometry")
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // A ray intersects a sphere in two points.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// assert_eq!(
|
|
||||||
/// xs,
|
|
||||||
/// Intersections::new(vec![Intersection::new(4., &s), Intersection::new(6., &s)])
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// // A ray intersects a sphere at a tangent.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 2., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// assert_eq!(xs, Intersections::default());
|
|
||||||
///
|
|
||||||
/// // A ray originates inside a sphere.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// assert_eq!(
|
|
||||||
/// xs,
|
|
||||||
/// Intersections::new(vec![Intersection::new(-1., &s), Intersection::new(1., &s)])
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// // A sphere is behind a ray.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let s = Shape::sphere();
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// assert_eq!(
|
|
||||||
/// xs,
|
|
||||||
/// Intersections::new(vec![Intersection::new(-6., &s), Intersection::new(-4., &s)])
|
|
||||||
/// );
|
|
||||||
///
|
|
||||||
/// // Intersect a scaled sphere with a ray.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let mut s = Shape::sphere();
|
|
||||||
/// s.set_transform(Matrix4x4::scaling(2., 2., 2.));
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// assert_eq!(xs.len(), 2, "xs {:?}", xs);
|
|
||||||
/// assert_eq!(xs[0].t, 3., "xs {:?}", xs);
|
|
||||||
/// assert_eq!(xs[1].t, 7., "xs {:?}", xs);
|
|
||||||
///
|
|
||||||
/// // Intersect a translated sphere with a ray.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let mut s = Shape::sphere();
|
|
||||||
/// s.set_transform(Matrix4x4::translation(5., 0., 0.));
|
|
||||||
/// let xs = intersect(&s, &r);
|
|
||||||
/// assert_eq!(xs.len(), 0);
|
|
||||||
///
|
|
||||||
/// // Intersect with a ray parallel to the plane.
|
|
||||||
/// let p = Shape::plane();
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 10., 0.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let xs = intersect(&p, &r);
|
|
||||||
/// assert_eq!(xs.len(), 0);
|
|
||||||
///
|
|
||||||
/// // Intersect with a coplanar.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let xs = intersect(&p, &r);
|
|
||||||
/// assert_eq!(xs.len(), 0);
|
|
||||||
///
|
|
||||||
/// // A ray intersecting a plane from above.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 1., 0.), Tuple::vector(0., -1., 0.));
|
|
||||||
/// let xs = intersect(&p, &r);
|
|
||||||
/// assert_eq!(xs.len(), 1);
|
|
||||||
/// assert_eq!(xs[0].t, 1.);
|
|
||||||
/// assert_eq!(xs[0].object, &p);
|
|
||||||
///
|
|
||||||
/// // A ray intersecting a plane from below.
|
|
||||||
/// let r = Ray::new(Tuple::point(0., -1., 0.), Tuple::vector(0., 1., 0.));
|
|
||||||
/// let xs = intersect(&p, &r);
|
|
||||||
/// assert_eq!(xs.len(), 1);
|
|
||||||
/// assert_eq!(xs[0].t, 1.);
|
|
||||||
/// assert_eq!(xs[0].object, &p);
|
|
||||||
/// ```
|
|
||||||
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
pub fn intersect<'s>(shape: &'s Shape, ray: &Ray) -> Intersections<'s> {
|
||||||
let local_ray = ray.transform(shape.inverse_transform);
|
let local_ray = ray.transform(shape.inverse_transform);
|
||||||
match shape.geometry {
|
match shape.geometry {
|
||||||
@ -539,3 +234,325 @@ mod plane {
|
|||||||
)])
|
)])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
mod shape_builder {
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use crate::shapes::{plane, sphere, test_shape, Shape};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn plane_builder() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_eq!(plane().build()?, Shape::plane());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere_builder() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_eq!(sphere().build()?, Shape::sphere());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_shape_builder() -> Result<(), Box<dyn Error>> {
|
||||||
|
assert_eq!(test_shape().build()?, Shape::test_shape());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod shape {
|
||||||
|
use crate::{
|
||||||
|
materials::Material,
|
||||||
|
matrices::{identity, translation},
|
||||||
|
shapes::Shape,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_shape() {
|
||||||
|
let mut s = Shape::test_shape();
|
||||||
|
// The default transform.
|
||||||
|
assert_eq!(s.transform(), identity());
|
||||||
|
// The default material.
|
||||||
|
assert_eq!(s.material, Material::default());
|
||||||
|
// Assigning a material.
|
||||||
|
let m = Material {
|
||||||
|
ambient: 1.,
|
||||||
|
..Material::default()
|
||||||
|
};
|
||||||
|
s.material = m.clone();
|
||||||
|
assert_eq!(s.material, m);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere() {
|
||||||
|
// A sphere's default transform is the identity matrix.
|
||||||
|
let s = Shape::sphere();
|
||||||
|
assert_eq!(s.transform(), identity());
|
||||||
|
|
||||||
|
// It can be changed by directly setting the transform member.
|
||||||
|
let mut s = Shape::sphere();
|
||||||
|
let t = translation(2., 3., 4.);
|
||||||
|
s.set_transform(t.clone());
|
||||||
|
assert_eq!(s.transform(), t);
|
||||||
|
|
||||||
|
// Default Sphere has the default material.
|
||||||
|
assert_eq!(s.material, Material::default());
|
||||||
|
// It can be overridden.
|
||||||
|
let mut s = Shape::sphere();
|
||||||
|
let mut m = Material::default();
|
||||||
|
m.ambient = 1.;
|
||||||
|
s.material = m.clone();
|
||||||
|
assert_eq!(s.material, m);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod normal_at {
|
||||||
|
use crate::{float::consts::PI, matrices::Matrix4x4, shapes::Shape, tuples::Tuple, Float};
|
||||||
|
#[test]
|
||||||
|
fn compute_normal_on_translated_shape() {
|
||||||
|
// Computing the normal on a translated shape.
|
||||||
|
let mut s = Shape::test_shape();
|
||||||
|
s.set_transform(Matrix4x4::translation(0., 1., 0.));
|
||||||
|
let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711));
|
||||||
|
assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn compute_normal_on_scaled_shape() {
|
||||||
|
// Computing the normal on a scaled shape.
|
||||||
|
let mut s = Shape::test_shape();
|
||||||
|
s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.));
|
||||||
|
let n = s.normal_at(Tuple::point(
|
||||||
|
0.,
|
||||||
|
(2. as Float).sqrt() / 2.,
|
||||||
|
-(2. as Float).sqrt() / 2.,
|
||||||
|
));
|
||||||
|
assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere_normal_on_x_axis() {
|
||||||
|
// Normal on X-axis
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let n = s.normal_at(Tuple::point(1., 0., 0.));
|
||||||
|
assert_eq!(n, Tuple::vector(1., 0., 0.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere_normal_on_y_axis() {
|
||||||
|
// Normal on Y-axis
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let n = s.normal_at(Tuple::point(0., 1., 0.));
|
||||||
|
assert_eq!(n, Tuple::vector(0., 1., 0.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere_normal_on_z_axis() {
|
||||||
|
// Normal on Z-axis
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let n = s.normal_at(Tuple::point(0., 0., 1.));
|
||||||
|
assert_eq!(n, Tuple::vector(0., 0., 1.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere_normal_non_axial() {
|
||||||
|
// Normal on a sphere at a nonaxial point.
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let n = s.normal_at(Tuple::point(
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
));
|
||||||
|
assert_eq!(
|
||||||
|
n,
|
||||||
|
Tuple::vector(
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn normals_are_normalized() {
|
||||||
|
// Normals returned are normalized.
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let n = s.normal_at(Tuple::point(
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
(3. as Float).sqrt() / 3.,
|
||||||
|
));
|
||||||
|
assert_eq!(n, n.normalize());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn compute_normal_on_translated_sphere() {
|
||||||
|
// Compute the normal on a translated sphere.
|
||||||
|
let mut s = Shape::sphere();
|
||||||
|
s.set_transform(Matrix4x4::translation(0., 1., 0.));
|
||||||
|
let n = s.normal_at(Tuple::point(0., 1.70711, -0.70711));
|
||||||
|
assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn compute_normal_on_scaled_sphere() {
|
||||||
|
// Compute the normal on a transformed sphere.
|
||||||
|
let mut s = Shape::sphere();
|
||||||
|
s.set_transform(Matrix4x4::scaling(1., 0.5, 1.) * Matrix4x4::rotation_z(PI / 5.));
|
||||||
|
let n = s.normal_at(Tuple::point(
|
||||||
|
0.,
|
||||||
|
(2. as Float).sqrt() / 2.,
|
||||||
|
-(2. as Float).sqrt() / 2.,
|
||||||
|
));
|
||||||
|
assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn nomal_of_plane_constant() {
|
||||||
|
// Normal of a plane is constant everywhere.
|
||||||
|
let p = Shape::plane();
|
||||||
|
assert_eq!(
|
||||||
|
p.normal_at(Tuple::point(0., 0., 0.)),
|
||||||
|
Tuple::vector(0., 1., 0.)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
p.normal_at(Tuple::point(10., 0., -10.)),
|
||||||
|
Tuple::vector(0., 1., 0.)
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
p.normal_at(Tuple::point(-5., 0., 150.)),
|
||||||
|
Tuple::vector(0., 1., 0.)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod intersect {
|
||||||
|
use crate::{
|
||||||
|
intersections::{Intersection, Intersections},
|
||||||
|
matrices::Matrix4x4,
|
||||||
|
rays::Ray,
|
||||||
|
shapes::{intersect, Geometry, Shape},
|
||||||
|
tuples::Tuple,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn scaled_shape_with_ray() {
|
||||||
|
// Intersecting a scaled shape with a ray.
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
let mut s = Shape::test_shape();
|
||||||
|
s.set_transform(Matrix4x4::scaling(2., 2., 2.));
|
||||||
|
let _xs = intersect(&s, &r);
|
||||||
|
if let Geometry::TestShape(data) = s.geometry() {
|
||||||
|
if let Some(ray) = &data.lock().unwrap().saved_ray {
|
||||||
|
assert_eq!(ray.origin, Tuple::point(0., 0., -2.5));
|
||||||
|
assert_eq!(ray.direction, Tuple::vector(0., 0., 0.5));
|
||||||
|
} else {
|
||||||
|
panic!("ray wasn't set");
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
panic!("test_shape returned a non-TestShape geometry")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn translated_shape_with_ray() {
|
||||||
|
// Intersecting a translated shape with a ray.
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
let mut s = Shape::test_shape();
|
||||||
|
s.set_transform(Matrix4x4::translation(5., 0., 0.));
|
||||||
|
let _xs = intersect(&s, &r);
|
||||||
|
if let Geometry::TestShape(data) = s.geometry() {
|
||||||
|
if let Some(ray) = &data.lock().unwrap().saved_ray {
|
||||||
|
assert_eq!(ray.origin, Tuple::point(-5., 0., -5.));
|
||||||
|
assert_eq!(ray.direction, Tuple::vector(0., 0., 1.));
|
||||||
|
} else {
|
||||||
|
panic!("ray wasn't set");
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
panic!("test_shape returned a non-TestShape geometry")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_intersects_sphere_two_points() {
|
||||||
|
// A ray intersects a sphere in two points.
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let xs = intersect(&s, &r);
|
||||||
|
assert_eq!(
|
||||||
|
xs,
|
||||||
|
Intersections::new(vec![Intersection::new(4., &s), Intersection::new(6., &s)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_intersects_at_tangent() {
|
||||||
|
// A ray intersects a sphere at a tangent.
|
||||||
|
let r = Ray::new(Tuple::point(0., 2., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let xs = intersect(&s, &r);
|
||||||
|
assert_eq!(xs, Intersections::default());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_originates_inside_sphere() {
|
||||||
|
// A ray originates inside a sphere.
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let xs = intersect(&s, &r);
|
||||||
|
assert_eq!(
|
||||||
|
xs,
|
||||||
|
Intersections::new(vec![Intersection::new(-1., &s), Intersection::new(1., &s)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn sphere_behind_ray() {
|
||||||
|
// A sphere is behind a ray.
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
|
||||||
|
let s = Shape::sphere();
|
||||||
|
let xs = intersect(&s, &r);
|
||||||
|
assert_eq!(
|
||||||
|
xs,
|
||||||
|
Intersections::new(vec![Intersection::new(-6., &s), Intersection::new(-4., &s)])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_intersects_scaled_sphere() {
|
||||||
|
// Intersect a scaled sphere with a ray.
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
let mut s = Shape::sphere();
|
||||||
|
s.set_transform(Matrix4x4::scaling(2., 2., 2.));
|
||||||
|
let xs = intersect(&s, &r);
|
||||||
|
assert_eq!(xs.len(), 2, "xs {:?}", xs);
|
||||||
|
assert_eq!(xs[0].t, 3., "xs {:?}", xs);
|
||||||
|
assert_eq!(xs[1].t, 7., "xs {:?}", xs);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_intersects_translated_sphere() {
|
||||||
|
// Intersect a translated sphere with a ray.
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||||
|
let mut s = Shape::sphere();
|
||||||
|
s.set_transform(Matrix4x4::translation(5., 0., 0.));
|
||||||
|
let xs = intersect(&s, &r);
|
||||||
|
assert_eq!(xs.len(), 0);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_parallel_to_plane() {
|
||||||
|
// Intersect with a ray parallel to the plane.
|
||||||
|
let p = Shape::plane();
|
||||||
|
let r = Ray::new(Tuple::point(0., 10., 0.), Tuple::vector(0., 0., 1.));
|
||||||
|
let xs = intersect(&p, &r);
|
||||||
|
assert_eq!(xs.len(), 0);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_coplanar_to_plane() {
|
||||||
|
// Intersect with a coplanar.
|
||||||
|
let p = Shape::plane();
|
||||||
|
let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
||||||
|
let xs = intersect(&p, &r);
|
||||||
|
assert_eq!(xs.len(), 0);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_intersects_plane_from_above() {
|
||||||
|
// A ray intersecting a plane from above.
|
||||||
|
let p = Shape::plane();
|
||||||
|
let r = Ray::new(Tuple::point(0., 1., 0.), Tuple::vector(0., -1., 0.));
|
||||||
|
let xs = intersect(&p, &r);
|
||||||
|
assert_eq!(xs.len(), 1);
|
||||||
|
assert_eq!(xs[0].t, 1.);
|
||||||
|
assert_eq!(xs[0].object, &p);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ray_intersects_plane_from_below() {
|
||||||
|
// A ray intersecting a plane from below.
|
||||||
|
let p = Shape::plane();
|
||||||
|
let r = Ray::new(Tuple::point(0., -1., 0.), Tuple::vector(0., 1., 0.));
|
||||||
|
let xs = intersect(&p, &r);
|
||||||
|
assert_eq!(xs.len(), 1);
|
||||||
|
assert_eq!(xs[0].t, 1.);
|
||||||
|
assert_eq!(xs[0].object, &p);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -5,47 +5,6 @@ use crate::{
|
|||||||
|
|
||||||
/// Create a matrix representing a eye at `from` looking at `to`, with an `up`
|
/// Create a matrix representing a eye at `from` looking at `to`, with an `up`
|
||||||
/// as the up vector.
|
/// as the up vector.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{matrices::Matrix4x4, transformations::view_transform, tuples::Tuple};
|
|
||||||
///
|
|
||||||
/// // The transofrmation matrix for the default orientation.
|
|
||||||
/// let from = Tuple::point(0., 0., 0.);
|
|
||||||
/// let to = Tuple::point(0., 0., -1.);
|
|
||||||
/// let up = Tuple::vector(0., 1., 0.);
|
|
||||||
/// let t = view_transform(from, to, up);
|
|
||||||
/// assert_eq!(t, Matrix4x4::identity());
|
|
||||||
///
|
|
||||||
/// // A view transformation matrix looking in positive z direction.
|
|
||||||
/// let from = Tuple::point(0., 0., 0.);
|
|
||||||
/// let to = Tuple::point(0., 0., 1.);
|
|
||||||
/// let up = Tuple::vector(0., 1., 0.);
|
|
||||||
/// let t = view_transform(from, to, up);
|
|
||||||
/// assert_eq!(t, Matrix4x4::scaling(-1., 1., -1.));
|
|
||||||
///
|
|
||||||
/// // The view tranformation moves the world.
|
|
||||||
/// let from = Tuple::point(0., 0., 8.);
|
|
||||||
/// let to = Tuple::point(0., 0., 0.);
|
|
||||||
/// let up = Tuple::vector(0., 1., 0.);
|
|
||||||
/// let t = view_transform(from, to, up);
|
|
||||||
/// assert_eq!(t, Matrix4x4::translation(0., 0., -8.));
|
|
||||||
///
|
|
||||||
/// // An arbitrary view transformation.
|
|
||||||
/// let from = Tuple::point(1., 3., 2.);
|
|
||||||
/// let to = Tuple::point(4., -2., 8.);
|
|
||||||
/// let up = Tuple::vector(1., 1., 0.);
|
|
||||||
/// let t = view_transform(from, to, up);
|
|
||||||
/// assert_eq!(
|
|
||||||
/// t,
|
|
||||||
/// Matrix4x4::new(
|
|
||||||
/// [-0.50709, 0.50709, 0.67612, -2.36643],
|
|
||||||
/// [0.76772, 0.60609, 0.12122, -2.82843],
|
|
||||||
/// [-0.35857, 0.59761, -0.71714, 0.],
|
|
||||||
/// [0., 0., 0., 1.],
|
|
||||||
/// )
|
|
||||||
/// );
|
|
||||||
/// ```
|
|
||||||
pub fn view_transform(from: Tuple, to: Tuple, up: Tuple) -> Matrix4x4 {
|
pub fn view_transform(from: Tuple, to: Tuple, up: Tuple) -> Matrix4x4 {
|
||||||
let forward = (to - from).normalize();
|
let forward = (to - from).normalize();
|
||||||
let left = cross(forward, up.normalize());
|
let left = cross(forward, up.normalize());
|
||||||
@ -57,3 +16,56 @@ pub fn view_transform(from: Tuple, to: Tuple, up: Tuple) -> Matrix4x4 {
|
|||||||
[0., 0., 0., 1.],
|
[0., 0., 0., 1.],
|
||||||
) * Matrix4x4::translation(-from.x, -from.y, -from.z)
|
) * Matrix4x4::translation(-from.x, -from.y, -from.z)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
matrices::{identity, scaling, translation, Matrix4x4},
|
||||||
|
transformations::view_transform,
|
||||||
|
tuples::{point, vector},
|
||||||
|
};
|
||||||
|
#[test]
|
||||||
|
fn default_orientation() {
|
||||||
|
// The transformation matrix for the default orientation.
|
||||||
|
let from = point(0., 0., 0.);
|
||||||
|
let to = point(0., 0., -1.);
|
||||||
|
let up = vector(0., 1., 0.);
|
||||||
|
let t = view_transform(from, to, up);
|
||||||
|
assert_eq!(t, identity());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn looking_positive_z() {
|
||||||
|
// A view transformation matrix looking in positive z direction.
|
||||||
|
let from = point(0., 0., 0.);
|
||||||
|
let to = point(0., 0., 1.);
|
||||||
|
let up = vector(0., 1., 0.);
|
||||||
|
let t = view_transform(from, to, up);
|
||||||
|
assert_eq!(t, scaling(-1., 1., -1.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn transformation_moves_world() {
|
||||||
|
// The view transformation moves the world.
|
||||||
|
let from = point(0., 0., 8.);
|
||||||
|
let to = point(0., 0., 0.);
|
||||||
|
let up = vector(0., 1., 0.);
|
||||||
|
let t = view_transform(from, to, up);
|
||||||
|
assert_eq!(t, translation(0., 0., -8.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn arbitrary_view() {
|
||||||
|
// An arbitrary view transformation.
|
||||||
|
let from = point(1., 3., 2.);
|
||||||
|
let to = point(4., -2., 8.);
|
||||||
|
let up = vector(1., 1., 0.);
|
||||||
|
let t = view_transform(from, to, up);
|
||||||
|
assert_eq!(
|
||||||
|
t,
|
||||||
|
Matrix4x4::new(
|
||||||
|
[-0.50709, 0.50709, 0.67612, -2.36643],
|
||||||
|
[0.76772, 0.60609, 0.12122, -2.82843],
|
||||||
|
[-0.35857, 0.59761, -0.71714, 0.],
|
||||||
|
[0., 0., 0., 1.],
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,27 +3,11 @@ use std::ops::{Add, Div, Mul, Neg, Sub};
|
|||||||
use crate::{Float, EPSILON};
|
use crate::{Float, EPSILON};
|
||||||
|
|
||||||
/// Short hand for creating a Tuple that represents a point, w=1.
|
/// Short hand for creating a Tuple that represents a point, w=1.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::tuples::{point, Tuple};
|
|
||||||
///
|
|
||||||
/// assert_eq!(point(1., 2., 3.), Tuple::point(1., 2., 3.));
|
|
||||||
/// assert_eq!(point(1., 2., 3.), Tuple::new(1., 2., 3., 1.));
|
|
||||||
/// ```
|
|
||||||
pub fn point(x: Float, y: Float, z: Float) -> Tuple {
|
pub fn point(x: Float, y: Float, z: Float) -> Tuple {
|
||||||
Tuple::point(x, y, z)
|
Tuple::point(x, y, z)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Short hand for creating a Tuple that represents a vector, w=0.
|
/// Short hand for creating a Tuple that represents a vector, w=0.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::tuples::{vector, Tuple};
|
|
||||||
///
|
|
||||||
/// assert_eq!(vector(1., 2., 3.), Tuple::vector(1., 2., 3.));
|
|
||||||
/// assert_eq!(vector(1., 2., 3.), Tuple::new(1., 2., 3., 0.));
|
|
||||||
/// ```
|
|
||||||
pub fn vector(x: Float, y: Float, z: Float) -> Tuple {
|
pub fn vector(x: Float, y: Float, z: Float) -> Tuple {
|
||||||
Tuple::vector(x, y, z)
|
Tuple::vector(x, y, z)
|
||||||
}
|
}
|
||||||
@ -73,26 +57,6 @@ impl Tuple {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Reflects vector v across normal n.
|
/// Reflects vector v across normal n.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// tuples::{reflect, Tuple},
|
|
||||||
/// Float,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Reflecting a vector approaching at 45°
|
|
||||||
/// let v = Tuple::vector(1., -1., 0.);
|
|
||||||
/// let n = Tuple::vector(0., 1., 0.);
|
|
||||||
/// let r = reflect(v, n);
|
|
||||||
/// assert_eq!(r, Tuple::vector(1., 1., 0.));
|
|
||||||
///
|
|
||||||
/// // Reflecting off a slanted surface.
|
|
||||||
/// let v = Tuple::vector(0., -1., 0.);
|
|
||||||
/// let n = Tuple::vector((2. as Float).sqrt() / 2., (2. as Float).sqrt() / 2., 0.);
|
|
||||||
/// let r = reflect(v, n);
|
|
||||||
/// assert_eq!(r, Tuple::vector(1., 0., 0.));
|
|
||||||
/// ```
|
|
||||||
pub fn reflect(v: Tuple, n: Tuple) -> Tuple {
|
pub fn reflect(v: Tuple, n: Tuple) -> Tuple {
|
||||||
v - n * 2. * dot(v, n)
|
v - n * 2. * dot(v, n)
|
||||||
}
|
}
|
||||||
@ -297,7 +261,7 @@ impl Sub for Color {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{cross, dot, Color, Float, Tuple, EPSILON};
|
use super::{cross, dot, reflect, Color, Float, Tuple, EPSILON};
|
||||||
#[test]
|
#[test]
|
||||||
fn is_point() {
|
fn is_point() {
|
||||||
// A tuple with w = 1 is a point
|
// A tuple with w = 1 is a point
|
||||||
@ -456,4 +420,20 @@ mod tests {
|
|||||||
let c2 = Color::new(0.9, 1., 0.1);
|
let c2 = Color::new(0.9, 1., 0.1);
|
||||||
assert_eq!(c1 * c2, Color::new(1.0 * 0.9, 0.2 * 1., 0.4 * 0.1));
|
assert_eq!(c1 * c2, Color::new(1.0 * 0.9, 0.2 * 1., 0.4 * 0.1));
|
||||||
}
|
}
|
||||||
|
#[test]
|
||||||
|
fn reflect_approaching_at_45() {
|
||||||
|
// Reflecting a vector approaching at 45°
|
||||||
|
let v = Tuple::vector(1., -1., 0.);
|
||||||
|
let n = Tuple::vector(0., 1., 0.);
|
||||||
|
let r = reflect(v, n);
|
||||||
|
assert_eq!(r, Tuple::vector(1., 1., 0.));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn reflect_slanted_surface() {
|
||||||
|
// Reflecting off a slanted surface.
|
||||||
|
let v = Tuple::vector(0., -1., 0.);
|
||||||
|
let n = Tuple::vector((2. as Float).sqrt() / 2., (2. as Float).sqrt() / 2., 0.);
|
||||||
|
let r = reflect(v, n);
|
||||||
|
assert_eq!(r, Tuple::vector(1., 0., 0.));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,27 +1,17 @@
|
|||||||
use derive_builder::Builder;
|
use derive_builder::Builder;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
intersections::{prepare_computations, Intersections, PrecomputedData},
|
intersections::{prepare_computations, schlick, Intersections, PrecomputedData},
|
||||||
lights::PointLight,
|
lights::PointLight,
|
||||||
materials::{lighting, Material},
|
materials::{lighting, Material},
|
||||||
matrices::Matrix4x4,
|
matrices::Matrix4x4,
|
||||||
rays::Ray,
|
rays::Ray,
|
||||||
shapes::{intersect, Shape},
|
shapes::{intersect, Shape},
|
||||||
tuples::{Color, Tuple},
|
tuples::{dot, Color, Tuple},
|
||||||
BLACK, WHITE,
|
BLACK, WHITE,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// World holds all drawable objects and the light(s) that illuminate them.
|
/// World holds all drawable objects and the light(s) that illuminate them.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::world::World;
|
|
||||||
///
|
|
||||||
/// let w = World::default();
|
|
||||||
/// assert!(w.objects.is_empty());
|
|
||||||
/// assert_eq!(w.lights.len(), 0);
|
|
||||||
/// ```
|
|
||||||
|
|
||||||
#[derive(Builder, Clone, Debug, Default)]
|
#[derive(Builder, Clone, Debug, Default)]
|
||||||
#[builder(default)]
|
#[builder(default)]
|
||||||
pub struct World {
|
pub struct World {
|
||||||
@ -31,20 +21,11 @@ pub struct World {
|
|||||||
|
|
||||||
impl World {
|
impl World {
|
||||||
/// Creates a world suitable for use across multiple tests in from the book.
|
/// Creates a world suitable for use across multiple tests in from the book.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::world::World;
|
|
||||||
///
|
|
||||||
/// let w = World::test_world();
|
|
||||||
/// assert_eq!(w.objects.len(), 2);
|
|
||||||
/// assert!(!w.lights.is_empty());
|
|
||||||
/// ```
|
|
||||||
pub fn test_world() -> World {
|
pub fn test_world() -> World {
|
||||||
let light = PointLight::new(Tuple::point(-10., 10., -10.), WHITE);
|
let light = PointLight::new(Tuple::point(-10., 10., -10.), WHITE);
|
||||||
let mut s1 = Shape::sphere();
|
let mut s1 = Shape::sphere();
|
||||||
s1.material = Material {
|
s1.material = Material {
|
||||||
color: Color::new(0.8, 1., 0.6),
|
color: [0.8, 1., 0.6].into(),
|
||||||
diffuse: 0.7,
|
diffuse: 0.7,
|
||||||
specular: 0.2,
|
specular: 0.2,
|
||||||
..Material::default()
|
..Material::default()
|
||||||
@ -57,21 +38,7 @@ impl World {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Intesects the ray with this world.
|
/// Intersects the ray with this world.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{rays::Ray, tuples::Tuple, world::World};
|
|
||||||
///
|
|
||||||
/// let w = World::test_world();
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let xs = w.intersect(&r);
|
|
||||||
/// assert_eq!(xs.len(), 4);
|
|
||||||
/// assert_eq!(xs[0].t, 4.);
|
|
||||||
/// assert_eq!(xs[1].t, 4.5);
|
|
||||||
/// assert_eq!(xs[2].t, 5.5);
|
|
||||||
/// assert_eq!(xs[3].t, 6.);
|
|
||||||
/// ```
|
|
||||||
pub fn intersect(&self, r: &Ray) -> Intersections {
|
pub fn intersect(&self, r: &Ray) -> Intersections {
|
||||||
let mut xs: Vec<_> = self
|
let mut xs: Vec<_> = self
|
||||||
.objects
|
.objects
|
||||||
@ -87,147 +54,46 @@ impl World {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Compute shaded value for given precomputation.
|
/// Compute shaded value for given precomputation.
|
||||||
///
|
pub fn shade_hit(&self, comps: &PrecomputedData, remaining: usize) -> Color {
|
||||||
/// # Examples
|
let surface = self
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// intersections::{prepare_computations, Intersection},
|
|
||||||
/// lights::PointLight,
|
|
||||||
/// matrices::Matrix4x4,
|
|
||||||
/// rays::Ray,
|
|
||||||
/// shapes::Shape,
|
|
||||||
/// tuples::{Color, Tuple},
|
|
||||||
/// world::World,
|
|
||||||
/// WHITE,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // Shading an intersection.
|
|
||||||
/// let w = World::test_world();
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let s = &w.objects[0];
|
|
||||||
/// let i = Intersection::new(4., &s);
|
|
||||||
/// let comps = prepare_computations(&i, &r);
|
|
||||||
/// let c = w.shade_hit(&comps);
|
|
||||||
/// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855));
|
|
||||||
///
|
|
||||||
/// // Shading an intersection from the inside.
|
|
||||||
/// let mut w = World::test_world();
|
|
||||||
/// w.lights = vec![PointLight::new(Tuple::point(0., 0.25, 0.), WHITE)];
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let s = &w.objects[1];
|
|
||||||
/// let i = Intersection::new(0.5, &s);
|
|
||||||
/// let comps = prepare_computations(&i, &r);
|
|
||||||
/// let c = w.shade_hit(&comps);
|
|
||||||
/// assert_eq!(c, Color::new(0.90498, 0.90498, 0.90498));
|
|
||||||
///
|
|
||||||
/// // Shading with an intersection in shadow.
|
|
||||||
/// let mut w = World::default();
|
|
||||||
/// w.lights = vec![PointLight::new(Tuple::point(0., 0., -10.), WHITE)];
|
|
||||||
/// let s1 = Shape::sphere();
|
|
||||||
/// let mut s2 = Shape::sphere();
|
|
||||||
/// s2.set_transform(Matrix4x4::translation(0., 0., 10.));
|
|
||||||
/// w.objects = vec![s1, s2.clone()];
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let i = Intersection::new(4., &s2);
|
|
||||||
/// let comps = prepare_computations(&i, &r);
|
|
||||||
/// let c = w.shade_hit(&comps);
|
|
||||||
/// assert_eq!(c, Color::new(0.1, 0.1, 0.1));
|
|
||||||
/// ```
|
|
||||||
pub fn shade_hit(&self, comps: &PrecomputedData) -> Color {
|
|
||||||
let c = self
|
|
||||||
.lights
|
.lights
|
||||||
.iter()
|
.iter()
|
||||||
.fold(Color::new(0., 0., 0.), |acc, light| {
|
.fold(Color::new(0., 0., 0.), |acc, light| {
|
||||||
let shadowed = self.is_shadowed(comps.over_point, light);
|
let shadowed = self.is_shadowed(comps.over_point, light);
|
||||||
acc + lighting(
|
let surface = lighting(
|
||||||
&comps.object.material,
|
&comps.object.material,
|
||||||
|
&comps.object,
|
||||||
light,
|
light,
|
||||||
comps.over_point,
|
comps.over_point,
|
||||||
comps.eyev,
|
comps.eyev,
|
||||||
comps.normalv,
|
comps.normalv,
|
||||||
shadowed,
|
shadowed,
|
||||||
)
|
);
|
||||||
|
acc + surface
|
||||||
});
|
});
|
||||||
c
|
let reflected = self.reflected_color(comps, remaining);
|
||||||
|
let refracted = self.refracted_color(comps, remaining);
|
||||||
|
let material = &comps.object.material;
|
||||||
|
if material.reflective > 0. && material.transparency > 0. {
|
||||||
|
let reflectance = schlick(comps);
|
||||||
|
surface + reflected * reflectance + refracted * (1. - reflectance)
|
||||||
|
} else {
|
||||||
|
surface + reflected + refracted
|
||||||
|
}
|
||||||
}
|
}
|
||||||
/// Compute color for given ray fired at the world.
|
/// Compute color for given ray fired at the world.
|
||||||
///
|
pub fn color_at(&self, r: &Ray, remaining: usize) -> Color {
|
||||||
/// # Examples
|
let xs = self.intersect(r);
|
||||||
/// ```
|
match xs.hit() {
|
||||||
/// use rtchallenge::{
|
|
||||||
/// intersections::{prepare_computations, Intersection},
|
|
||||||
/// lights::PointLight,
|
|
||||||
/// rays::Ray,
|
|
||||||
/// shapes::Shape,
|
|
||||||
/// tuples::{Color, Tuple},
|
|
||||||
/// world::World,
|
|
||||||
/// BLACK,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// // The color when a ray misses.
|
|
||||||
/// let w = World::test_world();
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 1., 0.));
|
|
||||||
/// let c = w.color_at(&r);
|
|
||||||
/// assert_eq!(c, BLACK);
|
|
||||||
///
|
|
||||||
/// // The color when a ray hits.
|
|
||||||
/// let w = World::test_world();
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
|
||||||
/// let c = w.color_at(&r);
|
|
||||||
/// assert_eq!(c, Color::new(0.38066, 0.47583, 0.2855));
|
|
||||||
///
|
|
||||||
/// // The color with an intersection behind the ray.
|
|
||||||
/// let w = {
|
|
||||||
/// let mut w = World::test_world();
|
|
||||||
/// let mut outer = &mut w.objects[0];
|
|
||||||
/// let m = outer.material.ambient = 1.;
|
|
||||||
/// let inner = &mut w.objects[1];
|
|
||||||
/// inner.material.ambient = 1.;
|
|
||||||
/// w
|
|
||||||
/// };
|
|
||||||
/// let inner = &w.objects[1];
|
|
||||||
/// let r = Ray::new(Tuple::point(0., 0., 0.75), Tuple::vector(0., 0., -1.));
|
|
||||||
/// let c = w.color_at(&r);
|
|
||||||
/// assert_eq!(c, inner.material.color);
|
|
||||||
/// ```
|
|
||||||
pub fn color_at(&self, r: &Ray) -> Color {
|
|
||||||
match self.intersect(r).hit() {
|
|
||||||
Some(hit) => {
|
Some(hit) => {
|
||||||
let comps = prepare_computations(&hit, r);
|
let comps = prepare_computations(&hit, r, &xs);
|
||||||
self.shade_hit(&comps)
|
self.shade_hit(&comps, remaining)
|
||||||
}
|
}
|
||||||
None => BLACK,
|
None => BLACK,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine if point in world is in a shadow.
|
/// Determine if point in world is in a shadow.
|
||||||
///
|
|
||||||
/// # Examples
|
|
||||||
/// ```
|
|
||||||
/// use rtchallenge::{
|
|
||||||
/// tuples::{ Tuple},
|
|
||||||
/// shapes::Shape,
|
|
||||||
/// world::World,
|
|
||||||
/// };
|
|
||||||
///
|
|
||||||
/// let w = World::test_world();
|
|
||||||
/// let light = &w.lights[0];
|
|
||||||
///
|
|
||||||
/// // There is no shadow when nothing is collinear with point and light.
|
|
||||||
/// let p = Tuple::point(0.,10.,0.);
|
|
||||||
/// assert_eq!(w.is_shadowed(p,light), false);
|
|
||||||
///
|
|
||||||
/// // There shadow when an object is between the point and the light.
|
|
||||||
/// let p = Tuple::point(10.,-10.,10.);
|
|
||||||
/// assert_eq!(w.is_shadowed(p,light), true);
|
|
||||||
///
|
|
||||||
/// // There is no shadow when an object is behind the light.
|
|
||||||
/// let p = Tuple::point(-20.,20.,-20.);
|
|
||||||
/// assert_eq!(w.is_shadowed(p,light), false);
|
|
||||||
///
|
|
||||||
/// // There is no shadow when an object is behind the point.
|
|
||||||
/// let p = Tuple::point(-2.,2.,-2.);
|
|
||||||
/// assert_eq!(w.is_shadowed(p,light), false);
|
|
||||||
pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool {
|
pub fn is_shadowed(&self, point: Tuple, light: &PointLight) -> bool {
|
||||||
let v = light.position - point;
|
let v = light.position - point;
|
||||||
let distance = v.magnitude();
|
let distance = v.magnitude();
|
||||||
@ -240,4 +106,455 @@ impl World {
|
|||||||
}
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Compute reflected color for the given precomputation.
|
||||||
|
pub fn reflected_color(&self, comps: &PrecomputedData, remaining: usize) -> Color {
|
||||||
|
if remaining == 0 {
|
||||||
|
return BLACK;
|
||||||
|
}
|
||||||
|
if comps.object.material.reflective == 0. {
|
||||||
|
return BLACK;
|
||||||
|
}
|
||||||
|
// Fire reflected ray to figure out color.
|
||||||
|
let reflect_ray = Ray::new(comps.over_point, comps.reflectv);
|
||||||
|
let color = self.color_at(&reflect_ray, remaining - 1);
|
||||||
|
color * comps.object.material.reflective
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compute refracted color for the given precomputation.
|
||||||
|
pub fn refracted_color(&self, comps: &PrecomputedData, remaining: usize) -> Color {
|
||||||
|
if remaining == 0 {
|
||||||
|
return BLACK;
|
||||||
|
}
|
||||||
|
if comps.object.material.transparency == 0. {
|
||||||
|
return BLACK;
|
||||||
|
}
|
||||||
|
// Find the ratio of the first index of refraction to the secon
|
||||||
|
// (This is inverted from the definition of Snell's Law.)
|
||||||
|
let n_ratio = comps.n1 / comps.n2;
|
||||||
|
|
||||||
|
// cos(theta_i) is the same as the dot product of the two vectors
|
||||||
|
// TODO(wathiede): is cosine faster than doc productings?
|
||||||
|
let cos_i = dot(comps.eyev, comps.normalv);
|
||||||
|
|
||||||
|
// Find the sin(theta_t)^2 via trigonometric identity.
|
||||||
|
let sin2_t = n_ratio * n_ratio * (1. - cos_i * cos_i);
|
||||||
|
|
||||||
|
if sin2_t > 1. {
|
||||||
|
// Total internal reflection.
|
||||||
|
return BLACK;
|
||||||
|
}
|
||||||
|
// Find cos(theta_t) via trigonometric identity.
|
||||||
|
let cos_t = (1. - sin2_t).sqrt();
|
||||||
|
// Compute the direction of the refracted ray.
|
||||||
|
let direction = comps.normalv * (n_ratio * cos_i - cos_t) - comps.eyev * n_ratio;
|
||||||
|
// Create the refracted ray.
|
||||||
|
let refract_ray = Ray::new(comps.under_point, direction);
|
||||||
|
// Find he color of the refracted ray, making sure to multiply by the transparency value to
|
||||||
|
// account for any opacity.
|
||||||
|
self.color_at(&refract_ray, remaining - 1) * comps.object.material.transparency
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{
|
||||||
|
float::consts::SQRT_2,
|
||||||
|
intersections::{prepare_computations, Intersection, Intersections},
|
||||||
|
lights::PointLight,
|
||||||
|
materials::MaterialBuilder,
|
||||||
|
matrices::translation,
|
||||||
|
patterns::test_pattern,
|
||||||
|
rays::Ray,
|
||||||
|
shapes::{plane, sphere, Shape, ShapeBuilder},
|
||||||
|
tuples::{point, vector},
|
||||||
|
world::{World, WorldBuilder},
|
||||||
|
Float, BLACK, WHITE,
|
||||||
|
};
|
||||||
|
|
||||||
|
mod intersect {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn intersection_with_test_world() {
|
||||||
|
let w = World::test_world();
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let xs = w.intersect(&r);
|
||||||
|
assert_eq!(xs.len(), 4);
|
||||||
|
assert_eq!(xs[0].t, 4.);
|
||||||
|
assert_eq!(xs[1].t, 4.5);
|
||||||
|
assert_eq!(xs[2].t, 5.5);
|
||||||
|
assert_eq!(xs[3].t, 6.);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod world {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn default_world() {
|
||||||
|
let w = World::default();
|
||||||
|
assert!(w.objects.is_empty());
|
||||||
|
assert_eq!(w.lights.len(), 0);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_world() {
|
||||||
|
let w = World::test_world();
|
||||||
|
assert_eq!(w.objects.len(), 2);
|
||||||
|
assert!(!w.lights.is_empty());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod shade_hit {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn shading_an_intersection() {
|
||||||
|
// Shading an intersection.
|
||||||
|
let w = World::test_world();
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let s = &w.objects[0];
|
||||||
|
let xs = Intersections::from(Intersection::new(4., &s));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.shade_hit(&comps, 1);
|
||||||
|
assert_eq!(c, [0.38066, 0.47583, 0.2855].into());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn shading_an_intersection_from_inside() {
|
||||||
|
// Shading an intersection from the inside.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
w.lights = vec![PointLight::new(point(0., 0.25, 0.), WHITE)];
|
||||||
|
let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
|
||||||
|
let s = &w.objects[1];
|
||||||
|
let xs = Intersections::from(Intersection::new(0.5, &s));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.shade_hit(&comps, 1);
|
||||||
|
assert_eq!(c, [0.90498, 0.90498, 0.90498].into());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn shading_an_intersection_in_shadow() {
|
||||||
|
// Shading with an intersection in shadow.
|
||||||
|
let mut w = World::default();
|
||||||
|
w.lights = vec![PointLight::new(point(0., 0., -10.), WHITE)];
|
||||||
|
let s1 = Shape::sphere();
|
||||||
|
let mut s2 = Shape::sphere();
|
||||||
|
s2.set_transform(translation(0., 0., 10.));
|
||||||
|
w.objects = vec![s1, s2.clone()];
|
||||||
|
let r = Ray::new(point(0., 0., 5.), vector(0., 0., 1.));
|
||||||
|
let xs = Intersections::from(Intersection::new(4., &s2));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.shade_hit(&comps, 1);
|
||||||
|
assert_eq!(c, [0.1, 0.1, 0.1].into());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn shading_with_a_reflective_material() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// Shading with a reflective material.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
let shape = ShapeBuilder::plane()
|
||||||
|
.material(MaterialBuilder::default().reflective(0.5).build()?)
|
||||||
|
.transform(translation(0., -1., 0.))
|
||||||
|
.build()?;
|
||||||
|
w.objects.push(shape.clone());
|
||||||
|
let r = Ray::new(
|
||||||
|
point(0., 0., -3.),
|
||||||
|
vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||||
|
);
|
||||||
|
let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.shade_hit(&comps, 1);
|
||||||
|
assert_eq!(c, [0.87676, 0.92434, 0.82917].into());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn shading_with_a_transparent_material() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// shade_hit() with a transparent material.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
let floor = plane()
|
||||||
|
.transform(translation(0., -1., 0.))
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.transparency(0.5)
|
||||||
|
.refractive_index(1.5)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
w.objects.push(floor.clone());
|
||||||
|
let ball = sphere()
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.color([1., 0., 0.])
|
||||||
|
.ambient(0.5)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.transform(translation(0., -3.5, -0.5))
|
||||||
|
.build()?;
|
||||||
|
w.objects.push(ball);
|
||||||
|
let r = Ray::new(
|
||||||
|
point(0., 0., -3.),
|
||||||
|
vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||||
|
);
|
||||||
|
let xs = Intersections::new(vec![Intersection::new((2 as Float).sqrt(), &floor)]);
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.shade_hit(&comps, 5);
|
||||||
|
assert_eq!(c, [0.93642, 0.68642, 0.68643].into());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn reflective_transparent_material() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
let mut w = World::test_world();
|
||||||
|
let floor = plane()
|
||||||
|
.transform(translation(0., -1., 0.))
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.reflective(0.5)
|
||||||
|
.transparency(0.5)
|
||||||
|
.refractive_index(1.5)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
w.objects.push(floor.clone());
|
||||||
|
let ball = sphere()
|
||||||
|
.transform(translation(0., -3.5, -0.5))
|
||||||
|
.material(
|
||||||
|
MaterialBuilder::default()
|
||||||
|
.color([1., 0., 0.])
|
||||||
|
.ambient(0.5)
|
||||||
|
.build()?,
|
||||||
|
)
|
||||||
|
.build()?;
|
||||||
|
w.objects.push(ball);
|
||||||
|
let r = Ray::new(point(0., 0., -3.), vector(0., -SQRT_2 / 2., SQRT_2 / 2.));
|
||||||
|
let xs = Intersections::new(vec![Intersection::new(SQRT_2, &floor)]);
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let color = w.shade_hit(&comps, 5);
|
||||||
|
assert_eq!(color, [0.93391, 0.69643, 0.69243].into());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod color_at {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn color_when_ray_misses() {
|
||||||
|
// The color when a ray misses.
|
||||||
|
let w = World::test_world();
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 1., 0.));
|
||||||
|
let c = w.color_at(&r, 1);
|
||||||
|
assert_eq!(c, BLACK);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn color_when_ray_hits() {
|
||||||
|
// The color when a ray hits.
|
||||||
|
let w = World::test_world();
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let c = w.color_at(&r, 1);
|
||||||
|
assert_eq!(c, [0.38066, 0.47583, 0.2855].into());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn color_with_an_intersection_behind_ray() {
|
||||||
|
// The color with an intersection behind the ray.
|
||||||
|
let w = {
|
||||||
|
let mut w = World::test_world();
|
||||||
|
let mut outer = &mut w.objects[0];
|
||||||
|
outer.material.ambient = 1.;
|
||||||
|
let inner = &mut w.objects[1];
|
||||||
|
inner.material.ambient = 1.;
|
||||||
|
w
|
||||||
|
};
|
||||||
|
let r = Ray::new(point(0., 0., 0.75), vector(0., 0., -1.));
|
||||||
|
let c = w.color_at(&r, 1);
|
||||||
|
// inner.material.color is WHITE
|
||||||
|
assert_eq!(c, WHITE);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn ensure_mutually_reflective_surfaces_terminate() -> Result<(), Box<dyn std::error::Error>>
|
||||||
|
{
|
||||||
|
// Ensure mutually reflective surfaces don't infinitely recurse.
|
||||||
|
let lower = ShapeBuilder::plane()
|
||||||
|
.material(MaterialBuilder::default().reflective(1.).build()?)
|
||||||
|
.transform(translation(0., -1., 0.))
|
||||||
|
.build()?;
|
||||||
|
let upper = ShapeBuilder::plane()
|
||||||
|
.material(MaterialBuilder::default().reflective(1.).build()?)
|
||||||
|
.transform(translation(0., 1., 0.))
|
||||||
|
.build()?;
|
||||||
|
let w = WorldBuilder::default()
|
||||||
|
.lights(vec![PointLight::new(point(0., 0., 0.), WHITE)])
|
||||||
|
.objects(vec![lower, upper])
|
||||||
|
.build()?;
|
||||||
|
let r = Ray::new(point(0., 0., 0.), vector(0., 1., 0.));
|
||||||
|
// This should complete without stack overflow.
|
||||||
|
w.color_at(&r, 1);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod is_shadowed {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_shadow_when_nothing_collinear_with_point_and_light() {
|
||||||
|
// There is no shadow when nothing is collinear with point and light.
|
||||||
|
let w = World::test_world();
|
||||||
|
let light = &w.lights[0];
|
||||||
|
|
||||||
|
let p = point(0., 10., 0.);
|
||||||
|
assert_eq!(w.is_shadowed(p, light), false);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn shadow_when_object_between_point_and_light() {
|
||||||
|
// The shadow when an object is between the point and the light.
|
||||||
|
let w = World::test_world();
|
||||||
|
let light = &w.lights[0];
|
||||||
|
|
||||||
|
let p = point(10., -10., 10.);
|
||||||
|
assert_eq!(w.is_shadowed(p, light), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_shadow_when_object_behind_light() {
|
||||||
|
// There is no shadow when an object is behind the light.
|
||||||
|
let w = World::test_world();
|
||||||
|
let light = &w.lights[0];
|
||||||
|
|
||||||
|
let p = point(-20., 20., -20.);
|
||||||
|
assert_eq!(w.is_shadowed(p, light), false);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn no_shadow_when_object_behind_point() {
|
||||||
|
// There is no shadow when an object is behind the point.
|
||||||
|
let w = World::test_world();
|
||||||
|
let light = &w.lights[0];
|
||||||
|
|
||||||
|
let p = point(-2., 2., -2.);
|
||||||
|
assert_eq!(w.is_shadowed(p, light), false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod reflected_color {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reflected_color_for_nonreflective_material() {
|
||||||
|
// The reflected color for a nonreflective material.
|
||||||
|
let r = Ray::new(point(0., 0., 0.), vector(0., 0., 1.));
|
||||||
|
let mut w = World::test_world();
|
||||||
|
w.objects[1].material.ambient = 1.;
|
||||||
|
let xs = Intersections::from(Intersection::new(1., &w.objects[1]));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.reflected_color(&comps, 1);
|
||||||
|
assert_eq!(c, BLACK);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn reflected_color_for_reflective_material() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The reflected color for a reflective material.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
let shape = ShapeBuilder::plane()
|
||||||
|
.material(MaterialBuilder::default().reflective(0.5).build()?)
|
||||||
|
.transform(translation(0., -1., 0.))
|
||||||
|
.build()?;
|
||||||
|
w.objects.push(shape.clone());
|
||||||
|
let r = Ray::new(
|
||||||
|
point(0., 0., -3.),
|
||||||
|
vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||||
|
);
|
||||||
|
let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.reflected_color(&comps, 1);
|
||||||
|
assert_eq!(c, [0.19033, 0.23791, 0.14274].into());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn reflected_color_at_maximum_recursion_depth() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The reflected color at the maximum recursion depth.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
let shape = ShapeBuilder::plane()
|
||||||
|
.material(MaterialBuilder::default().reflective(0.5).build()?)
|
||||||
|
.transform(translation(0., -1., 0.))
|
||||||
|
.build()?;
|
||||||
|
w.objects.push(shape.clone());
|
||||||
|
let r = Ray::new(
|
||||||
|
point(0., 0., -3.),
|
||||||
|
vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
|
||||||
|
);
|
||||||
|
let xs = Intersections::from(Intersection::new((2 as Float).sqrt(), &shape));
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let _ = w.reflected_color(&comps, 0);
|
||||||
|
// Just needs to get here without infinite recursion.
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod refracted_color {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn refracted_color_with_opaque_surface() {
|
||||||
|
// The refracted color with an opaque surface.
|
||||||
|
let w = World::test_world();
|
||||||
|
let shape = &w.objects[0];
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let xs = Intersections::new(vec![
|
||||||
|
Intersection::new(4., &shape),
|
||||||
|
Intersection::new(6., &shape),
|
||||||
|
]);
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.refracted_color(&comps, 5);
|
||||||
|
assert_eq!(c, BLACK);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn refracted_color_at_maximum_recursion_depth() {
|
||||||
|
// The refracted color at the maximum recursive depth.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
w.objects[0].material.transparency = 1.0;
|
||||||
|
w.objects[0].material.refractive_index = 1.5;
|
||||||
|
let shape = &w.objects[0];
|
||||||
|
let r = Ray::new(point(0., 0., -5.), vector(0., 0., 1.));
|
||||||
|
let xs = Intersections::new(vec![
|
||||||
|
Intersection::new(4., &shape),
|
||||||
|
Intersection::new(6., &shape),
|
||||||
|
]);
|
||||||
|
let comps = prepare_computations(&xs[0], &r, &xs);
|
||||||
|
let c = w.refracted_color(&comps, 0);
|
||||||
|
assert_eq!(c, BLACK);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn refracted_color_under_total_internal_reflection() {
|
||||||
|
// The refracted color under total internal reflection.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
w.objects[0].material.transparency = 1.0;
|
||||||
|
w.objects[0].material.refractive_index = 1.5;
|
||||||
|
let shape = &w.objects[0];
|
||||||
|
let r = Ray::new(point(0., 0., (2 as Float).sqrt() / 2.), vector(0., 1., 0.));
|
||||||
|
let xs = Intersections::new(vec![
|
||||||
|
Intersection::new(-(2 as Float).sqrt() / 2., &shape),
|
||||||
|
Intersection::new((2 as Float).sqrt() / 2., &shape),
|
||||||
|
]);
|
||||||
|
let comps = prepare_computations(&xs[1], &r, &xs);
|
||||||
|
let c = w.refracted_color(&comps, 5);
|
||||||
|
assert_eq!(c, BLACK);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn refracted_color_with_a_refracted_ray() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
|
// The refracted color with a refracted ray.
|
||||||
|
let mut w = World::test_world();
|
||||||
|
w.objects[0].material.ambient = 1.;
|
||||||
|
w.objects[0].material.color = test_pattern().build()?;
|
||||||
|
|
||||||
|
w.objects[1].material.transparency = 1.;
|
||||||
|
w.objects[1].material.refractive_index = 1.5;
|
||||||
|
|
||||||
|
let r = Ray::new(point(0., 0., 0.1), vector(0., 1., 0.));
|
||||||
|
let a = &w.objects[0];
|
||||||
|
let b = &w.objects[1];
|
||||||
|
let xs = Intersections::new(vec![
|
||||||
|
Intersection::new(-0.9899, &a),
|
||||||
|
Intersection::new(-0.4899, &b),
|
||||||
|
Intersection::new(0.4899, &b),
|
||||||
|
Intersection::new(0.9899, &a),
|
||||||
|
]);
|
||||||
|
let comps = prepare_computations(&xs[2], &r, &xs);
|
||||||
|
let c = w.refracted_color(&comps, 5);
|
||||||
|
assert_eq!(c, [0., 0.99887, 0.04721].into());
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user