Skip to content

Commit

Permalink
feat: refractive index for entered and exited object
Browse files Browse the repository at this point in the history
  • Loading branch information
Flinner committed Aug 15, 2021
1 parent 3cb0ff1 commit 7865990
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 59 deletions.
81 changes: 45 additions & 36 deletions src/objects/intersections.rs
Original file line number Diff line number Diff line change
Expand Up @@ -106,45 +106,15 @@ impl Intersection {
let eyev = -ray.direction;
let mut normalv = self.object.normal_at(point)?;
let inside: bool;
let mut refractive_exited: f64 = 1.0;
let mut refractive_entered: f64 = 1.0;

// TODO: compare to unwrap_or
let xs = xs.unwrap_or_else(|| Intersections {
list: vec![self.to_owned()],
});

// record which objects have been encoutered (for refraction)
let mut containers: Vec<Shape> = Vec::with_capacity(xs.list.len());
for i in &xs.list {
let is_hit = xs.hit().unwrap().object.uid == i.object.uid;
if is_hit {
if let Some(last) = containers.last() {
refractive_exited = last.material.refractive_index;
} else {
refractive_exited = 1.0
}
}
if let Some(index) = containers.iter().position(|x| x.uid == i.object.uid) {
// intersection is exiting object
// since it already entered and is present in the container
containers.remove(index);
} else {
// intersection is entering object
// push it, next time it is encoutered, this branch won't run
containers.push(i.object);
}
if is_hit {
if let Some(last) = containers.last() {
refractive_entered = last.material.refractive_index;
} else {
refractive_entered = 1.0
}
break;
}
}
// only for tests!
// let xs = xs.unwrap_or_else(|| Intersections {
// list: vec![self.to_owned()],
// });

let (refractive_exited, refractive_entered) = refractive_index(self, xs.unwrap());

// when the intersection is inside the object, invert the normal
if normalv.dot_product(&eyev) < 0.0 {
inside = true;
normalv = -normalv
Expand All @@ -168,3 +138,42 @@ impl Intersection {
})
}
}

fn refractive_index(hit: &Intersection, xs: Intersections) -> (f64, f64) {
let mut refractive_exited: f64 = 1.0;
let mut refractive_entered: f64 = 1.0;

// record which objects have been encoutered (for refraction)
let mut containers: Vec<Shape> = Vec::with_capacity(xs.list.len());

for i in &xs.list {
let is_hit = hit.object.uid == i.object.uid
&& (hit.intersects_at - i.intersects_at).abs() < constants::EPSILON;

if is_hit {
if let Some(last) = containers.last() {
refractive_exited = last.material.refractive_index;
} else {
refractive_exited = 1.0
}
}
if let Some(index) = containers.iter().position(|x| x.uid == i.object.uid) {
// intersection is exiting object
// since it already entered and is present in the container
containers.remove(index);
} else {
// intersection is entering object
// push it, next time it is encoutered, this branch won't run
containers.push(i.object);
}
if is_hit {
if let Some(last) = containers.last() {
refractive_entered = last.material.refractive_index;
} else {
refractive_entered = 1.0;
}
break;
}
}
(refractive_exited, refractive_entered)
}
2 changes: 1 addition & 1 deletion src/objects/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ impl World {
pub fn color_at(&self, ray: Ray, remaining: usize) -> Color {
let is = self.intersect(ray);
if let Some(hit) = is.hit() {
let comp = hit.prepare_computations(ray, Some(is.clone())).unwrap();
let comp = hit.prepare_computations(ray, Some(is.to_owned())).unwrap();
self.shade_hit(&comp, remaining - 1)
} else {
color::BLACK
Expand Down
40 changes: 27 additions & 13 deletions tests/intersections.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
use std::f64::EPSILON;

use raytracer::{
constants,
math::{point::Point, ray::Ray, transformations::Transformation, vector::Vector},
objects::{
intersections::{Intersection, Intersections},
Expand Down Expand Up @@ -88,7 +87,11 @@ fn hit_when_intersection_is_outside() {
let s: Shape = shape::sphere::default();
let i = Intersection::new(4.0, s);

let comps = i.prepare_computations(ray, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(ray, xs).unwrap();
assert!(!comps.inside);
}

Expand All @@ -101,7 +104,11 @@ fn hit_when_intersection_is_inside() {
let s: Shape = shape::sphere::default();
let i = Intersection::new(1.0, s);

let comps = i.prepare_computations(ray, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(ray, xs).unwrap();
assert!(comps.inside);
assert_eq!(comps.point, Point::new(0.0, 0.0, 1.0));
assert_eq!(comps.eyev, Vector::new(0.0, 0.0, -1.0));
Expand All @@ -117,14 +124,17 @@ fn hit_should_offset_the_point() {
s.transformation = Transformation::translation(0.0, 0.0, 1.0);
let i = Intersection::new(5.0, s);

let comps = i.prepare_computations(ray, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(ray, xs).unwrap();
println!("comps.over_point.z {}", comps.over_point.z);
assert!(comps.over_point.z < -constants::EPSILON / 2.0);
assert!(comps.point.z > comps.over_point.z)
}

#[test]
#[ignore = "doen't work :("]
fn finding_refractive_indices_of_inner_and_outer_surface() {
// n1 is the material being exited
// n2 is the material being entered
Expand Down Expand Up @@ -154,12 +164,16 @@ fn finding_refractive_indices_of_inner_and_outer_surface() {
let i4 = Intersection::new(4.75, b);
let i5 = Intersection::new(5.25, c);
let i6 = Intersection::new(6.0, a);
let xs = i1
.agregate(i2)
.agregate(i3)
.agregate(i4)
.agregate(i5)
.agregate(i6);
// let xs = i1
// .agregate(i2)
// .agregate(i3)
// .agregate(i4)
// .agregate(i5)
// .agregate(i6);

let xs: Intersections = Intersections {
list: vec![i1, i2, i3, i4, i5, i6],
};

// index, refractive_exited, refractive_entered
let test_cases: [(usize, f64, f64); 6] = [
Expand All @@ -171,7 +185,7 @@ fn finding_refractive_indices_of_inner_and_outer_surface() {
(5, 1.5, 1.0),
];
for (i, refractive_exited, refractive_entered) in test_cases {
println!("{}", i);
println!("======{}", i);
let comps = xs
.get(i)
.unwrap()
Expand Down
56 changes: 47 additions & 9 deletions tests/world.rs
Original file line number Diff line number Diff line change
Expand Up @@ -56,11 +56,15 @@ fn intersect_world_with_ray() {

#[test]
fn preparing_computations() {
let ray = Ray::new(Point::new(0.0, 0.0, -5.0), Vector::new(0.0, 0.0, 1.0));
let r = Ray::new(Point::new(0.0, 0.0, -5.0), Vector::new(0.0, 0.0, 1.0));
let shape: Shape = shape::sphere::default();
let i = Intersection::new(4.0, shape);

let comps = i.prepare_computations(ray, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();

assert_eq!(comps.intersects_at, i.intersects_at);
assert_eq!(comps.object, i.object);
Expand All @@ -76,7 +80,12 @@ fn shading_an_intersection() {
let shape = w.objects[0];
let i = Intersection::new(4.0, shape);

let comps = i.prepare_computations(r, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();

let c = w.shade_hit(&comps, MAX_REFLECTION_RECRUSTION);

Testing::assert_nearly_eq(c, Color::new(0.38066, 0.47583, 0.2855))
Expand All @@ -96,7 +105,11 @@ fn shading_an_intersection_from_inside() {
let shape = w.objects[1];
let i = Intersection::new(0.5, shape);

let comps = i.prepare_computations(r, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();
let c = w.shade_hit(&comps, MAX_REFLECTION_RECRUSTION);

Testing::assert_nearly_eq(c, Color::new(0.90498, 0.90498, 0.90498))
Expand Down Expand Up @@ -194,7 +207,12 @@ fn intersection_is_shadow() {
object: s2,
};

let comps = i.prepare_computations(r, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();

let c = w.shade_hit(&comps, MAX_REFLECTION_RECRUSTION);

Testing::assert_nearly_eq(c, Color::new(0.1, 0.1, 0.1))
Expand All @@ -209,7 +227,12 @@ fn precomputing_reflection_vector() {
Vector::new(0.0, -SQRT_2 / 2.0, SQRT_2 / 2.0),
);
let i = Intersection::new(SQRT_2, shape);
let comps = i.prepare_computations(r, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();

assert_eq!(comps.reflectv, Vector::new(0.0, SQRT_2 / 2.0, SQRT_2 / 2.0))
}

Expand All @@ -222,7 +245,12 @@ fn reflect_color_for_nonreflective_material() {
shape.material.ambient = 1.0;

let i = Intersection::new(1.0, *shape);
let comps = i.prepare_computations(r, None).unwrap();
let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();

let c = w.reflected_color(&comps, MAX_REFLECTION_RECRUSTION);

assert_eq!(c, color::BLACK)
Expand All @@ -243,7 +271,12 @@ fn reflect_color_for_reflective_material() {
);

let i = Intersection::new(SQRT_2, shape);
let comps = i.prepare_computations(r, None).unwrap();

let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();
let c = w.reflected_color(&comps, MAX_REFLECTION_RECRUSTION);

Color::assert_nearly_eq(c, Color::new(0.1903306125, 0.237913265737, 0.142747959442));
Expand All @@ -264,7 +297,12 @@ fn shade_hit_with_reflective_material() {
);

let i = Intersection::new(SQRT_2, shape);
let comps = i.prepare_computations(r, None).unwrap();

let xs = Some(Intersections {
list: vec![i.to_owned()],
});

let comps = i.prepare_computations(r, xs).unwrap();
let color = w.shade_hit(&comps, MAX_REFLECTION_RECRUSTION);

Color::assert_nearly_eq(
Expand Down

0 comments on commit 7865990

Please sign in to comment.