def test_precomputing_state_of_intersection(self): ray = Ray(point(0, 0, -5), vector(0, 0, 1)) shape = Sphere() xs = Intersection(4, shape) xs.prepare_hit(ray) self.assertEqual(point(0, 0, -1), xs.point) self.assertEqual(vector(0, 0, -1), xs.eyev) self.assertEqual(vector(0, 0, -1), xs.normalv)
def test_intersection_occurs_on_the_inside(self): ray = Ray(point(0, 0, 0), vector(0, 0, 1)) shape = Sphere() xs = Intersection(1, shape) xs.prepare_hit(ray) self.assertEqual(point(0, 0, 1), xs.point) self.assertEqual(vector(0, 0, -1), xs.eyev) self.assertEqual(True, xs.inside) self.assertEqual(vector(0, 0, -1), xs.normalv)
def test_the_color_when_ray_misses(self): world = default_world() ray = Ray(point(0, 0, -5), vector(0, 1, 0)) c = color_at(world, ray) self.assertEqual(color(0, 0, 0), c)
def test_the_color_when_ray_hits(self): world = default_world() ray = Ray(point(0, 0, -5), vector(0, 0, 1)) c = color_at(world, ray) self.assert_tuple_equals(color(0.38066, 0.47583, 0.2855), c, 0.001)
def test_computing_normal_on_scaled_sphere(self): s = Sphere() s.set_transform(scaling(1, 0.5, 1)) n = s.normal_at(point(0, math.sqrt(2) / 2, -math.sqrt(2) / 2)) self.assert_tuple_equals(vector(0, 0.97014, -0.24254), n, 0.001)
def test_computing_normal_on_translated_sphere(self): s = Sphere() s.set_transform(translation(0, 1, 0)) n = s.normal_at(point(0, 1.70711, -0.70711)) self.assert_tuple_equals(vector(0, 0.70711, -0.70711), n, 0.001)
def test_construct_ray_through_the_center_of_the_canvas(self): c = Camera(201, 101, pi / 2.0) r = ray_for_pixel(c, 100, 50) self.assert_tuple_equals(point(0, 0, 0), r.origin, 0.001) self.assert_tuple_equals(vector(0, 0, -1), r.direction, 0.001)
def test_construct_ray_through_corner_of_the_canvas(self): c = Camera(201, 101, pi / 2.0) r = ray_for_pixel(c, 0, 0) self.assert_tuple_equals(point(0, 0, 0), r.origin, 0.001) self.assert_tuple_equals(vector(0.66519, 0.33259, -0.66851), r.direction, 0.001)
def test_intersecting_translated_sphere_with_ray(self): r = Ray(point(0, 0, -5), vector(0, 0, 1)) s = Sphere() s.set_transform(translation(5, 0, 0)) xs = s.intersect(r) self.assertEqual(0, len(xs))
def test_sphere_behind_ray(self): r = Ray(point(0, 0, 5), vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(2, len(xs)) self.assertEqual(-6, xs[0].t) self.assertEqual(-4, xs[1].t)
def test_ray_originates_inside_sphere(self): r = Ray(point(0, 0, 0), vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(2, len(xs)) self.assertEqual(-1, xs[0].t) self.assertEqual(1, xs[1].t)
def test_ray_intersects_sphere_at_tangent(self): r = Ray(point(0, 1, -5), vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(2, len(xs)) self.assertEqual(5, xs[0].t) self.assertEqual(5, xs[1].t)
def test_intersection_occurs_on_the_outside(self): ray = Ray(point(0, 0, -5), vector(0, 0, 1)) shape = Sphere() xs = Intersection(4, shape) xs.prepare_hit(ray) self.assertEqual(False, xs.inside)
def test_transformation_matrix_for_the_default_orientation(self): from_where = point(0, 0, 0) to = point(0, 0, -1) up = vector(0, 1, 0) t = view_transform(from_where, to, up) self.assert_matrix_equals(identity_matrix, t)
def test_the_view_trasnformation_move_the_world(self): from_where = point(0, 0, 8) to = point(0, 0, 0) up = vector(0, 1, 0) t = view_transform(from_where, to, up) self.assert_matrix_equals(translation(0, 0, -8), t)
def test_intersect_sets_object_on_intersection(self): r = Ray(point(0, 0, -5), vector(0, 0, 1)) s = sphere.Sphere() xs = s.intersect(r) self.assertEqual(2, len(xs)) self.assertEqual(s, xs[0].shape) self.assertEqual(s, xs[1].shape)
def test_view_transformation_matrix_looking_in_positive_z_direction(self): from_where = point(0, 0, 0) to = point(0, 0, 1) up = vector(0, 1, 0) t = view_transform(from_where, to, up) self.assertEqual(scaling(-1, 1, -1), t)
def test_intersecting_scaled_sphere_with_ray(self): r = Ray(point(0, 0, -5), vector(0, 0, 1)) s = Sphere() s.set_transform(scaling(2, 2, 2)) xs = s.intersect(r) self.assertEqual(2, len(xs)) self.assertEqual(3, xs[0].t) self.assertEqual(7, xs[1].t)
def test_shading_and_intersection(self): world = default_world() ray = Ray(point(0, 0, -5), vector(0, 0, 1)) shape = world.objects[0] xs = Intersection(4, shape) xs.prepare_hit(ray) c = shade_hit(world, xs) self.assert_tuple_equals(color(0.38066, 0.47583, 0.2855), c, 0.001)
def test_construct_ray_when_camera_is_transformed(self): c = Camera(201, 101, pi / 2.0, transform=rotation_y(pi / 4.0) * translation(0, -2, 5)) r = ray_for_pixel(c, 100, 50) self.assert_tuple_equals(point(0, 2, -5), r.origin, 0.001) self.assert_tuple_equals(vector(sqrt(2) / 2, 0, -sqrt(2) / 2), r.direction, 0.001)
def test_the_color_with_an_intersection_behind_the_ray(self): world = default_world() outer = world.objects[0] outer.material.ambient = 1 inner = world.objects[1] inner.material.ambient = 1 ray = Ray(point(0, 0, 0.75), vector(0, 0, -1)) c = color_at(world, ray) self.assertEqual(inner.material.color, c)
def test_arbitrary_view_transformation(self): from_where = point(1, 3, 2) to = point(4, -2, 8) up = vector(1, 1, 0) t = view_transform(from_where, to, up) m = Matrix4(-0.50709, 0.50709, 0.67612, -2.36643, 0.76772, 0.60609, 0.12122, -2.82843, -0.35857, 0.59761, -0.71714, 0.00000, 0.00000, 0.00000, 0.00000, 1.00000) self.assert_matrix_equals(m, t)
def test_shading_an_intersection_form_the_inside(self): world = default_world() world.light = PointLight(point(0, 0.25, 0), color(1, 1, 1)) ray = Ray(point(0, 0, 0), vector(0, 0, 1)) shape = world.objects[1] xs = Intersection(0.5, shape) xs.prepare_hit(ray) c = shade_hit(world, xs) self.assert_tuple_equals(color(0.90498, 0.90498, 0.90498), c, 0.001)
def test_intersect_world_with_ray(self): world = default_world() ray = Ray(point(0, 0, -5), vector(0, 0, 1)) xs = intersect_world(world, ray) self.assertEqual(4, len(xs)) self.assertEqual(4, xs[0].t) self.assertEqual(4.5, xs[1].t) self.assertEqual(5.5, xs[2].t) self.assertEqual(6, xs[3].t)
def test_rendering_world_with_camera(self): w = default_world() c = Camera(11, 11, pi / 2.0) from_where = point(0, 0, -5) to = point(0, 0, 0) up = vector(0, 1, 0) c.transform = view_transform(from_where, to, up) image = render(c, w) self.assert_tuple_equals(color(0.38066, 0.47583, 0.2855), image.pixel_at(5, 5), 0.001)
def view_transform(from_where, to, up): forward_vector = (to - from_where) if forward_vector != vector(0, 0, 0): forward_vector = forward_vector.normalize() left_vector = forward_vector.cross(up.normalize()) true_up_vector = left_vector.cross(forward_vector) orientation = Matrix4(left_vector.x, left_vector.y, left_vector.z, 0, true_up_vector.x, true_up_vector.y, true_up_vector.z, 0, -forward_vector.x, -forward_vector.y, -forward_vector.z, 0, 0, 0, 0, 1) return orientation * translation(-from_where.x, -from_where.y, -from_where.z)
class World: def __init__(self, gravity, wind): self.gravity = gravity self.wind = wind def tick(world, projectile): position = projectile.position + projectile.velocity velocity = projectile.velocity + world.gravity + world.wind return Projectile(position, velocity) if __name__ == '__main__': w = World(vector(0, -0.1, 0), vector(-0.01, 0, 0)) p = Projectile(position=point(0, 1, 0), velocity=vector(1, 1.8, 0).normalize() * 11.25) c = Canvas(900, 550) t = 0 while p.position.y > 0: p = tick(w, p) t += 1 c.write_pixel(round(p.position.x), c.height - round(p.position.y), green) c.save_to_file('projectile.ppm')
def test_reflection_scaling_by_a_negative_value(self): self.assertEqual(vector(-2, 3, 4), scaling(-1, 1, 1) * vector(2, 3, 4))
def test_multiplying_by_inverse_of_scaling_matrix(self): self.assertEqual(vector(-2, 2, 2), scaling(2, 3, 4).inverse() * vector(-4, 6, 8))
def test_scaling_matrix_applied_to_vector(self): self.assertEqual(vector(-8, 18, 32), scaling(2, 3, 4) * vector(-4, 6, 8))