Implement transparency, reflections and refraction.

This commit is contained in:
Bill Thiede 2021-07-26 21:46:04 -07:00
parent 1d61f59935
commit 42e8ebe3bd
3 changed files with 417 additions and 44 deletions

View File

@ -64,6 +64,13 @@ impl<'i> Intersection<'i> {
#[derive(Debug, Default, PartialEq)]
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> {
pub fn new(xs: Vec<Intersection<'i>>) -> Intersections {
Intersections(xs)
@ -145,11 +152,14 @@ pub struct PrecomputedData<'i> {
pub t: Float,
pub object: &'i Shape,
pub point: Tuple,
pub under_point: Tuple,
pub over_point: Tuple,
pub eyev: Tuple,
pub normalv: Tuple,
pub reflectv: Tuple,
pub inside: bool,
pub n1: Float,
pub n2: Float,
}
/// Precomputes data common to all intersections.
@ -159,19 +169,22 @@ pub struct PrecomputedData<'i> {
/// use rtchallenge::{
/// intersections::{prepare_computations, Intersection, Intersections},
/// rays::Ray,
/// matrices::Matrix4x4,
/// shapes::{intersect, Shape},
/// materials::MaterialBuilder,
/// matrices::{Matrix4x4,scaling,translation},
/// shapes::{intersect, Shape,glass_sphere},
/// tuples::{point,vector,Tuple},
/// EPSILON,Float
/// };
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // 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);
/// 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, Tuple::point(0., 0., -1.));
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
/// assert_eq!(comps.normalv, Tuple::vector(0., 0., -1.));
@ -179,15 +192,15 @@ pub struct PrecomputedData<'i> {
/// // 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);
/// let xs = Intersections::from(Intersection::new(4., &shape));
/// let comps = prepare_computations(&xs[0], &r,&xs);
/// 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);
/// let xs = Intersections::from(Intersection::new(1., &shape));
/// let comps = prepare_computations(&xs[0], &r,&xs);
/// assert_eq!(comps.point, Tuple::point(0., 0., 1.));
/// assert_eq!(comps.eyev, Tuple::vector(0., 0., -1.));
/// assert_eq!(comps.inside, true);
@ -198,21 +211,61 @@ pub struct PrecomputedData<'i> {
/// 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);
/// 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);
///
/// // 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 i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// 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.));
///
/// // 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);
/// }
///
/// // 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(())
/// # }
/// ```
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);
pub fn prepare_computations<'i>(
hit: &'i Intersection,
r: &Ray,
xs: &Intersections,
) -> PrecomputedData<'i> {
let point = r.position(hit.t);
let normalv = hit.object.normal_at(point);
let eyev = -r.direction;
let (inside, normalv) = if dot(normalv, eyev) < 0. {
(true, -normalv)
@ -221,15 +274,154 @@ pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData
};
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 under_point = point - normalv * EPSILON;
PrecomputedData {
t: i.t,
object: i.object,
t: hit.t,
object: hit.object,
point,
over_point,
under_point,
normalv,
reflectv,
inside,
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::{
float::consts::SQRT_2,
intersections::{prepare_computations, Intersection, Intersections},
materials::MaterialBuilder,
matrices::translation,
rays::Ray,
shapes::{glass_sphere, plane, sphere},
tuples::{point, vector},
world::World,
Float, EPSILON,
};
use super::schlick;
#[test]
fn schlick_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 schlick_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 schlick_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(())
}
#[test]
fn shade_hit_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(())
}
}

View File

@ -3,7 +3,10 @@ use std::sync::{Arc, Mutex};
use derive_builder::Builder;
use crate::{
intersections::Intersections, materials::Material, matrices::Matrix4x4, rays::Ray,
intersections::Intersections,
materials::{Material, MaterialBuilder},
matrices::Matrix4x4,
rays::Ray,
tuples::Tuple,
};
@ -80,6 +83,7 @@ pub fn plane() -> ShapeBuilder {
pub fn sphere() -> ShapeBuilder {
ShapeBuilder::sphere()
}
/// Short hand for creating a ShapeBuilder with a test shape geometry.
///
/// # Examples
@ -95,6 +99,31 @@ pub fn test_shape() -> ShapeBuilder {
ShapeBuilder::test_shape()
}
/// Helper for producing a sphere with a glassy material.
///
/// # Examples
/// ```
/// use rtchallenge::{matrices::identity, shapes::glass_sphere};
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// let s = glass_sphere().build()?;
/// assert_eq!(s.transform(), identity());
/// assert_eq!(s.material.transparency, 1.);
/// assert_eq!(s.material.refractive_index, 1.5);
///
/// # Ok(())
/// # }
/// ```
pub fn glass_sphere() -> ShapeBuilder {
ShapeBuilder::sphere().material(
MaterialBuilder::default()
.transparency(1.)
.refractive_index(1.5)
.build()
.unwrap(),
)
}
impl ShapeBuilder {
/// Short hand for creating a ShapeBuilder with a plane geometry.
///

View File

@ -1,13 +1,13 @@
use derive_builder::Builder;
use crate::{
intersections::{prepare_computations, Intersections, PrecomputedData},
intersections::{prepare_computations, schlick, Intersections, PrecomputedData},
lights::PointLight,
materials::{lighting, Material},
matrices::Matrix4x4,
rays::Ray,
shapes::{intersect, Shape},
tuples::{Color, Tuple},
tuples::{dot, Color, Tuple},
BLACK, WHITE,
};
@ -91,13 +91,13 @@ impl World {
/// # Examples
/// ```
/// use rtchallenge::{
/// intersections::{prepare_computations, Intersection},
/// intersections::{prepare_computations, Intersection, Intersections},
/// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::Matrix4x4,
/// matrices::{translation, Matrix4x4},
/// rays::Ray,
/// shapes::{Shape, ShapeBuilder},
/// tuples::{Color, Tuple},
/// shapes::{plane, sphere, Shape, ShapeBuilder},
/// tuples::{point, vector, Color, Tuple},
/// world::World,
/// Float, WHITE,
/// };
@ -108,8 +108,8 @@ impl World {
/// 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 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, Color::new(0.38066, 0.47583, 0.2855));
///
@ -118,8 +118,8 @@ impl 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 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, Color::new(0.90498, 0.90498, 0.90498));
///
@ -131,8 +131,8 @@ impl World {
/// 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 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, Color::new(0.1, 0.1, 0.1));
///
@ -147,11 +147,41 @@ impl World {
/// Tuple::point(0., 0., -3.),
/// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
/// );
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// 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());
///
/// // 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(())
/// # }
/// ```
@ -173,7 +203,14 @@ impl World {
acc + surface
});
let reflected = self.reflected_color(comps, remaining);
surface + reflected
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.
///
@ -240,9 +277,10 @@ impl World {
/// # }
/// ```
pub fn color_at(&self, r: &Ray, remaining: usize) -> Color {
match self.intersect(r).hit() {
let xs = self.intersect(r);
match xs.hit() {
Some(hit) => {
let comps = prepare_computations(&hit, r);
let comps = prepare_computations(&hit, r, &xs);
self.shade_hit(&comps, remaining)
}
None => BLACK,
@ -292,7 +330,7 @@ impl World {
/// # Examples
/// ```
/// use rtchallenge::{
/// intersections::{prepare_computations, Intersection},
/// intersections::{prepare_computations, Intersection, Intersections},
/// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::{translation, Matrix4x4},
@ -309,8 +347,8 @@ impl World {
/// let r = Ray::new(Tuple::point(0., 0., 0.), Tuple::vector(0., 0., 1.));
/// let mut w = World::test_world();
/// w.objects[1].material.ambient = 1.;
/// let i = Intersection::new(1., &w.objects[1]);
/// let comps = prepare_computations(&i, &r);
/// 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);
///
@ -325,8 +363,8 @@ impl World {
/// Tuple::point(0., 0., -3.),
/// Tuple::vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
/// );
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// 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());
///
@ -341,8 +379,8 @@ impl World {
/// point(0., 0., -3.),
/// vector(0., -(2 as Float).sqrt() / 2., (2 as Float).sqrt() / 2.),
/// );
/// let i = Intersection::new((2 as Float).sqrt(), &shape);
/// let comps = prepare_computations(&i, &r);
/// 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, 0);
///
/// # Ok(())
@ -360,4 +398,118 @@ impl World {
let color = self.color_at(&reflect_ray, remaining - 1);
color * comps.object.material.reflective
}
/// Compute refracted color for the given precomputation.
///
/// # Examples
/// ```
/// use rtchallenge::{
/// intersections::{prepare_computations, Intersection, Intersections},
/// lights::PointLight,
/// materials::MaterialBuilder,
/// matrices::{translation, Matrix4x4},
/// patterns::test_pattern,
/// rays::Ray,
/// shapes::{plane, sphere, Shape, ShapeBuilder},
/// tuples::{point, vector, Color, Tuple},
/// world::World,
/// Float, BLACK,
/// };
///
/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
///
/// // The refracted color with an opaque surface.
/// let mut w = World::test_world();
/// let shape = &w.objects[0];
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::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);
///
/// // 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 r = Ray::new(Tuple::point(0., 0., -5.), Tuple::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);
///
/// // 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 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);
///
/// // 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(())
/// # }
/// ```
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
}
}