def local_intersect(self, ray: Ray) -> Iterable[Intersection]: sphere_to_ray = ray.origin - Point(0, 0, 0) a = Vector.dot(ray.direction, ray.direction) b = 2 * Vector.dot(ray.direction, sphere_to_ray) c = Vector.dot(sphere_to_ray, sphere_to_ray) - 1 discriminant = b**2 - 4 * a * c if discriminant < 0: return Intersection.intersections() t1 = (-b - math.sqrt(discriminant)) / (2 * a) t2 = (-b + math.sqrt(discriminant)) / (2 * a) return Intersection.intersections(Intersection(t1, self), Intersection(t2, self))
def test_hit_all_intersections_negative_t(self): s = Sphere() i1 = Intersection(-2, s) i2 = Intersection(-1, s) xs = Intersection.intersections(i2, i1) i = Intersection.hit(xs) self.assertIsNone(i)
def test_schlick_approximation_with_perpendicular_viewing_angle(self): shape = GlassSphere() r = Ray(Point(0, 0, 0), Vector(0, 1, 0)) xs = Intersection.intersections(Intersection(-1,shape), Intersection(1, shape)) comps = Computations.prepare_computations(xs[1], r, xs) reflectance = World.schlick(comps) self.assertAlmostEqual(reflectance, 0.04, delta = Constants.epsilon)
def test_hit_all_intersections_positive_t(self): s = Sphere() i1 = Intersection(1, s) i2 = Intersection(2, s) xs = Intersection.intersections(i2, i1) i = Intersection.hit(xs) self.assertEqual(i, i1)
def test_schlick_approximation_with_small_angle(self): shape = GlassSphere() r = Ray(Point(0, 0.99, -2), Vector(0, 0, 1)) xs = Intersection.intersections(Intersection(1.8589, shape)) comps = Computations.prepare_computations(xs[0], r, xs) reflectance = World.schlick(comps) self.assertAlmostEqual(reflectance, 0.48873, delta = Constants.epsilon)
def test_schlick_approximation_under_total_internal_reflection(self): shape = GlassSphere() r = Ray(Point(0, 0, math.sqrt(2) / 2), Vector(0, 1, 0)) xs = Intersection.intersections(Intersection(-math.sqrt(2) / 2, shape), Intersection(math.sqrt(2) / 2, shape)) comps = Computations.prepare_computations(xs[1], r, xs) reflectance = World.schlick(comps) self.assertEqual(reflectance, 1.0)
def test_findinf_n1_n2_at_various_intersections(self): a = GlassSphere() a.transform = Transformations.scaling(2, 2, 2) a.material.refractive_index = 1.5 b = GlassSphere() b.transform = Transformations.translation(0, 0, -0.25) b.material.refractive_index = 2.0 c = GlassSphere() c.transform = Transformations.translation(0, 0, 0.25) c.material.refractive_index = 2.5 r = Ray(Point(0, 0, -4), Vector(0, 0, 1)) xs = Intersection.intersections(Intersection(2, a), Intersection(2.75, b), Intersection(3.25, c), Intersection(4.75, b), Intersection(5.25, c), Intersection(6, a)) RefractiveIndices = namedtuple("RefractiveIndices", ["n1", "n2"]) refractive_indices_list = [ RefractiveIndices(1.0, 1.5), RefractiveIndices(1.5, 2.0), RefractiveIndices(2.0, 2.5), RefractiveIndices(2.5, 2.5), RefractiveIndices(2.5, 1.5), RefractiveIndices(1.5, 1.0) ] for index, refractive_index in enumerate(refractive_indices_list): comps = Computations.prepare_computations(xs[index], r, xs) print(comps.n1) print(comps.n2) self.assertEqual(comps.n1, refractive_index.n1) self.assertEqual(comps.n2, refractive_index.n2)
def test_hit_some_intersections_negative_t(self): s = Sphere() i1 = Intersection(-1, s) i2 = Intersection(1, s) xs = Intersection.intersections(i2, i1) i = Intersection.hit(xs) self.assertEqual(i, i2)
def test_aggregate_intersections(self): s = Sphere() i1 = Intersection(1, s) i2 = Intersection(2, s) xs = Intersection.intersections(i1, i2) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, 1) self.assertEqual(xs[1].t, 2)
def test_refracted_color_with_opaque_surface(self): w = World.default_world() shape = w.objects[0] r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) xs = Intersection.intersections(Intersection(4, shape), Intersection(6, shape)) comps = Computations.prepare_computations(xs[0], r, xs) c = World.refracted_color(w, comps, 5) self.assertEqual(c, Color(0, 0, 0))
def test_hit_lowest_nonnegative_intersection(self): s = Sphere() i1 = Intersection(5, s) i2 = Intersection(7, s) i3 = Intersection(-3, s) i4 = Intersection(2, s) xs = Intersection.intersections(i1, i2, i3, i4) i = Intersection.hit(xs) self.assertEqual(i, i4)
def test_under_point_offset_below_surface(self): r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) shape = GlassSphere() shape.transform = Transformations.translation(0, 0, 1) i = Intersection(5, shape) xs = Intersection.intersections(i) comps = Computations.prepare_computations(i, r, xs) self.assertGreater(comps.under_point.z, Constants.epsilon / 2) self.assertLess(comps.point.z, comps.under_point.z)
def test_refracted_color_at_max_recursive_depth(self): w = World.default_world() shape = w.objects[0] shape.material.transparency = 1.0 shape.material.refractive_index = 1.5 r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) xs = Intersection.intersections(Intersection(4, shape), Intersection(6, shape)) comps = Computations.prepare_computations(xs[0], r, xs) c = World.refracted_color(w, comps, 0) self.assertEqual(c, Color(0, 0, 0))
def test_refracted_color_under_total_internal_reflection(self): w = World.default_world() shape = w.objects[0] shape.material.transparency = 1.0 shape.material.refractive_index = 1.5 r = Ray(Point(0, 0, math.sqrt(2) / 2), Vector(0, 1, 0)) xs = Intersection.intersections(Intersection(-math.sqrt(2) / 2, shape), Intersection(math.sqrt(2) / 2, shape)) # NOTE: this time you're inside the sphere, so you need # to look at the second intersection, xs[1], not xs[0] comps = Computations.prepare_computations(xs[1], r, xs) c = World.refracted_color(w, comps, 5) self.assertEqual(c, Color(0, 0, 0))
def test_refracted_color_with_refracted_ray(self): w = World.default_world() a = w.objects[0] a.material.ambient = 1.0 a.material.pattern = Pattern.test_pattern() b = w.objects[1] b.material.transparency = 1.0 b.material.refractive_index = 1.5 r = Ray(Point(0, 0, 0.1), Vector(0, 1, 0)) xs = Intersection.intersections(Intersection(-0.9899, a), Intersection(-0.4899, b), Intersection(0.4899, b), Intersection(0.9899, a)) comps = Computations.prepare_computations(xs[2], r, xs) c = World.refracted_color(w, comps, 5) self.assertEqual(c, Color(0, 0.99888, 0.04725))
def test_filtering_list_intersections(self): s1 = Sphere() s2 = Cube() Operation = namedtuple("Operation", ["operation", "x0", "x1"]) operatios = [ Operation("union", 0, 3), Operation("intersection", 1, 2), Operation("difference", 0, 1) ] for operation in operatios: c = CSG(operation.operation, s1, s2) xs = Intersection.intersections(Intersection(1, s1), Intersection(2, s2), Intersection(3, s1), Intersection(4, s2)) result = c.filter_intersections(xs) self.assertEqual(len(result), 2) self.assertEqual(result[0], xs[operation.x0]) self.assertEqual(result[1], xs[operation.x1])
def test_shade_hit_with_transparent_material(self): w = World.default_world() floor = Plane() floor.transform = Transformations.translation(0, -1, 0) floor.material.transparency = 0.5 floor.material.refractive_index = 1.5 w.objects.append(floor) ball = Sphere() ball.material.color = Color(1, 0, 0) ball.material.ambient = 0.5 ball.transform = Transformations.translation(0, -3.5, -0.5) w.objects.append(ball) r = Ray(Point(0, 0, -3), Vector(0, -math.sqrt(2) / 2, math.sqrt(2) / 2)) xs = Intersection.intersections(Intersection(math.sqrt(2), floor)) comps = Computations.prepare_computations(xs[0], r, xs) color = World.shade_hit(w, comps, 5) self.assertEqual(color, Color(0.93642, 0.68642, 0.68642))
def test_preparing_normal_smooth_triangle(self): i = Intersection(1, self.tri, 0.45, 0.25) r = Ray(Point(-0.2, 0.3, -2), Vector(0, 0, 1)) xs = Intersection.intersections(i) comps = Computations.prepare_computations(i, r, xs) self.assertEqual(comps.normalv, Vector(-0.5547, 0.83205, 0))