def local_intersect(self, local_ray: rays.Ray): """Return the t-value where the ray intersects with the plane, in local units """ # If the y-direction of the ray is small it is approximately parallel to #the plane, no intersections if abs(local_ray.direction.y) < MIN_Y_FOR_PLANE_INTERSECT: return intersections.Intersections() t = -local_ray.origin.y / local_ray.direction.y return intersections.Intersections(intersections.Intersection(self, t))
def test_reflective_transparent_material(self): """Test shade_hit with a reflective, transparent material""" world = copy.deepcopy(self.default_scene) floor = shapes.Plane(material=materials.Material( reflective=0.5, transparency=0.5, refractive_index=1.5)) floor.set_transform(transforms.Translate(0, -1, 0)) ball = shapes.Sphere(material=materials.Material( color=colors.Color(1, 0, 0), ambient=0.5)) ball.set_transform(transforms.Translate(0, -3.5, -0.5)) r = rays.Ray(points.Point(0, 0, -3), vectors.Vector(0, -math.sqrt(2) / 2, math.sqrt(2) / 2)) xs = intersections.Intersections( intersections.Intersection(floor, math.sqrt(2))) world.add_object(floor) world.add_object(ball) comps = xs.intersections[0].precompute(r, all_intersections=xs) color, _ = world.shade_hit(comps, remaining=5) self.assertEqual(color, colors.Color(0.93391, 0.69643, 0.69243))
def test_refraction__standard(self): """Test that we return the correct color under normal refraction""" world = copy.deepcopy(self.default_scene) # s1 = A s1 = world.objects[1] s1.material.ambient = 1.0 s1.material.pattern = patterns.TestPattern() # s2 = B s2 = world.objects[0] s2.material.transparency = 1.0 s2.material.refractive_index = 1.5 r = rays.Ray(points.Point(0, 0, 0.1), vectors.Vector(0, 1, 0)) xs = intersections.Intersections( intersections.Intersection(s1, -0.9899), intersections.Intersection(s2, -0.4899), intersections.Intersection(s2, 0.4899), intersections.Intersection(s1, 0.9899)) # Intersections[2] because the first two are behind the camera comps = xs.intersections[2].precompute(r, all_intersections=xs) result, _ = world.refracted_color(comps, 5) # Remember the test pattern returns the x, y, z coordinates of the # input point as the color so we can inspect positions self.assertEqual(result, colors.Color(0, 0.99888, 0.0475))
def test_refraction__total_internal_reflection(self): """Test that we return black if we hit total internal reflection""" world = copy.deepcopy(self.default_scene) s = world.objects[1] s.material.transparency = 1.0 s.material.refractive_index = 1.5 r = rays.Ray(points.Point(0, 0, math.sqrt(2) / 2), vectors.Vector(0, 1, 0)) xs = intersections.Intersections( intersections.Intersection(s, -math.sqrt(2) / 2), intersections.Intersection(s, math.sqrt(2) / 2)) # n.b. the camera is inside the inner-sphere so use the second # intersection comps = xs.intersections[1].precompute(r, all_intersections=xs) result, _ = world.refracted_color(comps, 5) self.assertEqual(result, colors.Color(0, 0, 0))
def test_refractive_index_intersections(self): """Test we can calculate the refractive indices between intersections""" # Set up a scene with three glass spheres. One at the origin with size # 2 then inside of that 2 that are offset along z by different amounts A = shapes.Sphere(material=materials.Material(refractive_index=1.5, transparency=1.0)) B = shapes.Sphere(material=materials.Material(refractive_index=2.0, transparency=1.0)) C = shapes.Sphere(material=materials.Material(refractive_index=2.5, transparency=1.0)) A.set_transform(transforms.Scale(2, 2, 2)) B.set_transform(transforms.Translate(0, 0, -0.25)) C.set_transform(transforms.Translate(0, 0, 0.25)) r = rays.Ray(points.Point(0, 0, -4), vectors.Vector(0, 0, 1)) xs = intersections.Intersections(intersections.Intersection(A, 2), intersections.Intersection(B, 2.75), intersections.Intersection(C, 3.25), intersections.Intersection(B, 4.75), intersections.Intersection(C, 5.25), intersections.Intersection(A, 6)) expected_results = [ { "n1": 1.0, "n2": 1.5 }, { "n1": 1.5, "n2": 2.0 }, { "n1": 2.0, "n2": 2.5 }, { "n1": 2.5, "n2": 2.5 }, { "n1": 2.5, "n2": 1.5 }, { "n1": 1.5, "n2": 1.0 }, ] for index, expected in enumerate(expected_results): comps = xs.intersections[index].precompute(r, all_intersections=xs) self.assertDictEqual(expected, {"n1": comps.n1, "n2": comps.n2})
def test_hits__all_negative(self): """Test we get no hits for all negative intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, -1) i2 = intersections.Intersection(shape, -2) isections = intersections.Intersections(i1, i2) self.assertIsNone(isections.hit())
def test_hits__some_negative(self): """Test we get the correct hit for some negative intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, -1) i2 = intersections.Intersection(shape, 1) isections = intersections.Intersections(i1, i2) self.assertEqual(isections.hit(), i2)
def test_hits__positive(self): """Test we get the correct hit for multiple positive intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, 2) i2 = intersections.Intersection(shape, 1) isections = intersections.Intersections(i1, i2) self.assertEqual(isections.hit(), i2)
def local_intersect(self, local_ray: rays.Ray): """Return to t values for where the ray intersects the shape. All in local coordinates for the shape """ sphere_to_ray = local_ray.origin - points.Point(0, 0, 0) a = local_ray.direction.dot(local_ray.direction) b = 2 * local_ray.direction.dot(sphere_to_ray) c = sphere_to_ray.dot(sphere_to_ray) - 1 discriminant = b**2 - 4 * a * c if discriminant < 0: return intersections.Intersections() else: t1 = (-b - math.sqrt(discriminant)) / (2 * a) t2 = (-b + math.sqrt(discriminant)) / (2 * a) return intersections.Intersections( intersections.Intersection(self, t1), intersections.Intersection(self, t2))
def test_schlick__small_angle(self): """Test that we calculate the reflectance at a small angle """ r = rays.Ray(points.Point(0, 0.99, -2), vectors.Vector(0, 0, 1)) xs = intersections.Intersections( intersections.Intersection(self.glass_sphere, 1.8589), ) comps = xs.intersections[0].precompute(r, all_intersections=xs) self.assertAlmostEqual(comps.schlick, 0.48873081)
def test_hits__multiple(self): """Test we get the correct hit for some negative intersections""" shape = shapes.Sphere() i1 = intersections.Intersection(shape, -1) i2 = intersections.Intersection(shape, 7) i3 = intersections.Intersection(shape, -5) i4 = intersections.Intersection(shape, 2) i5 = intersections.Intersection(shape, 3) isections = intersections.Intersections(i1, i2, i3, i4, i5) self.assertEqual(isections.hit(), i4)
def test_schlick__perpendicular(self): """Test that we calculate the reflectance for a perpendicular ray """ r = rays.Ray(points.Point(0, 0, 0), vectors.Vector(0, 1, 0)) xs = intersections.Intersections( intersections.Intersection(self.glass_sphere, -1), intersections.Intersection(self.glass_sphere, 1)) comps = xs.intersections[1].precompute(r, all_intersections=xs) self.assertAlmostEqual(comps.schlick, 0.04)
def test_initialization(self): shape = shapes.Sphere() t1 = 3.5 t2 = -3.5 i1 = intersections.Intersection(shape, t1) self.assertEqual(i1.t, 3.5) self.assertEqual(i1.shape, shape) i2 = intersections.Intersection(shape, t2) self.assertEqual(i2.t, -3.5) i = intersections.Intersections(i1, i2) self.assertEqual(i.intersections, [i2, i1])
def test_schlick__total_internal_reflection(self): """Test that we calculate the reflectance under total internal reflection """ r = rays.Ray(points.Point(0, 0, math.sqrt(2) / 2), vectors.Vector(0, 1, 0)) xs = intersections.Intersections( intersections.Intersection(self.glass_sphere, -math.sqrt(2) / 2), intersections.Intersection(self.glass_sphere, math.sqrt(2) / 2)) comps = xs.intersections[1].precompute(r, all_intersections=xs) self.assertEqual(comps.schlick, 1)
def test_refraction__opaque(self): """Test the refraction color of an opaque material is black""" world = copy.deepcopy(self.default_scene) s = world.objects[1] r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) xs = intersections.Intersections(intersections.Intersection(s, 4), intersections.Intersection(s, 6)) comps = xs.intersections[0].precompute(r, all_intersections=xs) result, _ = world.refracted_color(comps, 5) self.assertEqual(result, colors.Color(0, 0, 0))
def test_refraction__recursion_limit(self): """Test that we return black if we hit the refraction limit""" world = copy.deepcopy(self.default_scene) s = world.objects[1] s.material.transparency = 1.0 s.material.refractive_index = 1.5 r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) xs = intersections.Intersections(intersections.Intersection(s, 4), intersections.Intersection(s, 6)) comps = xs.intersections[0].precompute(r, all_intersections=xs) result, _ = world.refracted_color(comps, 0) self.assertEqual(result, colors.Color(0, 0, 0))