def test_subdivide_csg_subdivide_children(self): s1 = Sphere() s1.transform = Transformations.translation(-1.5, 0, 0) s2 = Sphere() s2.transform = Transformations.translation(1.5, 0, 0) left = Group() left.add_child(s1) left.add_child(s2) s3 = Sphere() s3.transform = Transformations.translation(0, 0, -1.5) s4 = Sphere() s4.transform = Transformations.translation(0, 0, 1.5) right = Group() right.add_child(s3) right.add_child(s4) shape = CSG("difference", left, right) shape.divide(1) self.assertIsInstance(left.members[0], Group) self.assertEqual(left.members[0].members, [s1]) self.assertIsInstance(left.members[1], Group) self.assertEqual(left.members[1].members, [s2]) self.assertIsInstance(right.members[0], Group) self.assertEqual(right.members[0].members, [s3]) self.assertIsInstance(right.members[1], Group) self.assertEqual(right.members[1].members, [s4])
def test_normal_with_transform(self): s = Sphere() s.transform = Matrix.translate(0, 5, 0) n = s.normal(Point(1, 5, 0)) self.assertEqual(n, Vector(1, 0, 0)) s = Sphere() s.transform = Matrix.scale(1, 0.5, 1) r = math.sqrt(2) / 2 n = s.normal(Point(0, r, -r)) self.assertTupleEqual(n, Vector(0, 0.97014, -0.24254))
def test_partitioning_group_children(self): s1 = Sphere() s1.transform = Transformations.translation(-2, 0, 0) s2 = Sphere() s2.transform = Transformations.translation(2, 0, 0) s3 = Sphere() g = Group() g.add_child(s1) g.add_child(s2) g.add_child(s3) (left, right) = g.partition_children() self.assertEqual(g.members, [s3]) self.assertEqual(left, [s1]) self.assertEqual(right, [s2])
def test_pattern_object_and_pattern_transformation(self): shape = Sphere() shape.transform = Transformations.scaling(2, 2, 2) pattern = Pattern.test_pattern() pattern.transform = Transformations.translation(0.5, 1, 1.5) c = pattern.pattern_at_shape(shape, Point(2.5, 3, 3.5)) self.assertEqual(c, Color(0.75, 0.5, 0.25))
def test_transformation(self): s = Sphere() self.assertEqual(s.transform, Matrix.identity()) t = Matrix.translate(2, 3, 4) s.transform = t self.assertEqual(s.transform, t)
def test_intersect_with_transform(self): # intersect a scaled sphere r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() s.transform = Matrix.scale(2, 2, 2) xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, 3) self.assertEqual(xs[1].t, 7) # intersect a translated sphere r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() s.transform = Matrix.translate(5, 0, 0) xs = s.intersect(r) self.assertEqual(len(xs), 0)
def test_hit_offset_point(self): r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) shape = Sphere() shape.transform = Transformations.translation(0, 0, 1) i = Intersection(5, shape) comps = Computations.prepare_computations(i, r) self.assertLess(comps.over_point.z, -Constants.epsilon / 2) self.assertGreater(comps.point.z, comps.over_point.z)
def test_offset1(self): r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) shape = Sphere() shape.transform = translate(0, 0, 1) i = Intersection(5, shape) comps = i.prepare_computations(r) self.assertTrue(comps.over_point.z < -EPSILON / 2) self.assertTrue(comps.point.z > comps.over_point.z)
def test_csg_bounding_box(self): left = Sphere() right = Sphere() right.transform = Transformations.translation(2, 3, 4) shape = CSG("difference", left, right) box = shape.bounds_of() self.assertEqual(box.min, Point(-1, -1, -1)) self.assertEqual(box.max, Point(3, 4, 5))
def test_intersecting_ray_with_nonempty_group(self): g = Group() s1 = Sphere() s2 = Sphere() s2.transform = Transformations.translation(0, 0, -3) s3 = Sphere() s3.transform = Transformations.translation(5, 0, 0) g.add_child(s1) g.add_child(s2) g.add_child(s3) r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) xs = g.local_intersect(r) self.assertEqual(len(xs), 4) self.assertEqual(xs[0].object, s2) self.assertEqual(xs[1].object, s2) self.assertEqual(xs[2].object, s1) self.assertEqual(xs[3].object, s1)
def test_intersect_scaled_sphere_with_ray(self): r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() s.transform = Transformations.scaling(2, 2, 2) xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, 3) self.assertEqual(xs[1].t, 7)
def test_intersecting_transformed_group(self): g = Group() g.transform = Transformations.scaling(2, 2, 2) s = Sphere() s.transform = Transformations.translation(5, 0, 0) g.add_child(s) r = Ray(Point(10, 0, -10), Vector(0, 0, 1)) xs = g.intersect(r) self.assertEqual(len(xs), 2)
def test_group_partitions_children(self): s1 = Sphere() s1.transform = Transformations.translation(-2, -2, 0) s2 = Sphere() s2.transform = Transformations.translation(-2, 2, 0) s3 = Sphere() s3.transform = Transformations.scaling(4, 4, 4) g = Group() g.add_child(s1) g.add_child(s2) g.add_child(s3) g.divide(1) self.assertEqual(g.members[0], s3) subgroup = g.members[1] self.assertIsInstance(subgroup, Group) self.assertEqual(len(subgroup.members), 2) self.assertEqual(subgroup.members[0].members, [s1]) self.assertEqual(subgroup.members[1].members, [s2])
def test_finding_normal_on_child_object(self): g1 = Group() g1.transform = Transformations.rotation_y(math.pi / 2) g2 = Group() g2.transform = Transformations.scaling(1, 2, 3) g1.add_child(g2) s = Sphere() s.transform = Transformations.translation(5, 0, 0) g2.add_child(s) n = s.normal_at(Point(1.7321, 1.1547, -5.5774)) self.assertEqual(n, Vector(0.2857, 0.4286, -0.8571))
def test_converting_point_from_world_to_object_space(self): g1 = Group() g1.transform = Transformations.rotation_y(math.pi / 2) g2 = Group() g2.transform = Transformations.scaling(2, 2, 2) g1.add_child(g2) s = Sphere() s.transform = Transformations.translation(5, 0, 0) g2.add_child(s) p = s.world_to_object(Point(-2, 0, -10)) self.assertEqual(p, Point(0, 0, -1))
def test_converting_normal_from_object_to_world_space(self): g1 = Group() g1.transform = Transformations.rotation_y(math.pi / 2) g2 = Group() g2.transform = Transformations.scaling(1, 2, 3) g1.add_child(g2) s = Sphere() s.transform = Transformations.translation(5, 0, 0) g2.add_child(s) n = s.normal_to_world(Vector(math.sqrt(3) /3, math.sqrt(3) / 3, math.sqrt(3) / 3)) self.assertEqual(n, Vector(0.2857, 0.4286, -0.8571))
def test_ray_hits_csg_object(self): s1 = Sphere() s2 = Sphere() s2.transform = Transformations.translation(0, 0, 0.5) c = CSG("union", s1, s2) r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) xs = c.local_intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, 4) self.assertEqual(xs[0].object, s1) self.assertEqual(xs[1].t, 6.5) self.assertEqual(xs[1].object, s2)
def test_subdividing_group_too_few_children(self): s1 = Sphere() s1.transform = Transformations.translation(-2, 0, 0) s2 = Sphere() s2.transform = Transformations.translation(2, 1, 0) s3 = Sphere() s3.transform = Transformations.translation(2, -1, 0) subgroup = Group() subgroup.add_child(s1) subgroup.add_child(s2) subgroup.add_child(s3) s4 = Sphere() g = Group() g.add_child(subgroup) g.add_child(s4) g.divide(3) self.assertEqual(g.members[0], subgroup) self.assertEqual(g.members[1], s4) self.assertEqual(len(subgroup.members), 2) self.assertEqual(subgroup.members[0].members, [s1]) self.assertEqual(subgroup.members[1].members, [s2, s3])
def test_shadow5(self): w = World() light = Light(Point(0, 0, -10), Color(1, 1, 1)) w.light = light s1 = Sphere() s2 = Sphere() s2.transform = translate(0, 0, 10) w.objs = [s1, s2] r = Ray(Point(0, 0, 5), Vector(0, 0, 1)) i = Intersection(4, s2) comps = i.prepare_computations(r) c = w.shade_hit(comps) self.assertTrue(Color(0.1, 0.1, 0.1).equals(c))
def test_shade_hit_intersection_in_shadow(self): w = World() w.light = PointLight(Point(0, 0, -10), Color(1, 1, 1)) s1 = Sphere() w.objects.append(s1) s2 = Sphere() s2.transform = Transformations.translation(0, 0, 10) w.objects.append(s2) r = Ray(Point(0, 0, 5), Vector(0, 0, 1)) i = Intersection(4, s2) comps = Computations.prepare_computations(i, r) c = World.shade_hit(w, comps) self.assertEqual(c, Color(0.1, 0.1, 0.1))
def test_default_world(self): light = PointLight(Point(-10, 10, -10), Color(1, 1, 1)) s1 = Sphere() s1.material.color = Color(0.8, 1.0, 0.6) s1.material.diffuse = 0.7 s1.material.specular = 0.2 s2 = Sphere() s2.transform = Transformations.scaling(0.5, 0.5, 0.5) w = World.default_world() self.assertEqual(w.light, light) self.assertTrue(s1 in w.objects) self.assertTrue(s2 in w.objects)
def test_group_bounding_box_contains_children(self): s = Sphere() s.transform = Transformations.translation(2, 5, -3).dot( Transformations.scaling(2, 2, 2)) c = Cylinder() c.minimum = -2 c.maximum = 2 c.transform = Transformations.translation(-4, -1, 4).dot( Transformations.scaling(0.5, 1, 0.5)) shape = Group() shape.add_child(s) shape.add_child(c) box = shape.bounds_of() self.assertEqual(box.min, Point(-4.5, -3, -5)) self.assertEqual(box.max, Point(4, 7, 4.5))
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_normal_translated_sphere(self): s = Sphere() s.transform = Transformations.translation(0, 1, 0) n = s.normal_at(Point(0, 1.70711, -0.70711)) self.assertEqual(n, Vector(0, 0.70711, -0.70711))
W = 200 H = 200 D = 200 im = Image.new('RGB', (W, H)) pix = im.load() sphere = Sphere() sphere.material.color = Color(1, 0.2, 1) light_color = Color(1, 1, 1) light_position = Point(-10, 10, -10) light = PointLight(light_position, light_color) ## this is done in object coordinates eye = Point(0, 0, -5) tt = Matrix.translate(0, 0, -2) sphere.transform = tt wall = (-3, 3, -3, 3) # LRBT # the -1 flips the y-coordinate so it's up my = Matrix.scale(1.0, -1.0, 1.0) ms = Matrix.scale( float(wall[1] - wall[0]) / W, float(wall[3] - wall[2]) / H, 1.0) mt = Matrix.translate(wall[0], wall[2], 0) m = my * mt * ms _debug = False _pause = _debug def dprint(s):
from canvas import Canvas from point import Point from color import Color from ray import Ray from sphere import Sphere from material import Material from light import Light from time import time from world import World from camera import Camera if __name__ == '__main__': t1 = time() floor = Sphere() floor.transform = scale(10, 0.01, 10) floor.material = Material() floor.material.color = Color(1, 0.9, 0.9) floor.material.specular = 0 left_wall = Sphere() left_wall.transform = translate(0, 0, 5) * \ rotate_y(-pi/4) * \ rotate_x(pi/2) * \ scale(10, 0.01, 10) left_wall.material = floor.material right_wall = Sphere() right_wall.transform = translate(0, 0, 5) * \ rotate_y(pi/4) * \ rotate_x(pi/2) * \
def test_normal_transformed_sphere(self): s = Sphere() m = Transformations.scaling(1, 0.5, 1).dot(Transformations.rotation_z(math.pi / 5)) s.transform = m n = s.normal_at(Point(0, math.sqrt(2) / 2, -(math.sqrt(2) / 2))) self.assertEqual(n, Vector(0, 0.97014, -0.24254))
def test_intersect_translated_sphere(self): r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() s.transform = Transformations.translation(5, 0, 0) xs = s.intersect(r) self.assertEqual(len(xs), 0)
import math from camera import Camera from color import Color from light import PointLight from material import Material from sphere import Sphere from transformations import Transformations from tuple import Point, Vector from world import World if __name__ == '__main__': # The floor is an extremely flattened sphere with a matte texture floor = Sphere() floor.transform = Transformations.scaling(10, 0.1, 10) floor.material = Material() floor.material.color = Color(1, 0.9, 0.9) floor.material.specular = 0 # The wall on the left has the same scale and color as the floor, but is also rotated and translated into place left_wall = Sphere() left_wall.transform = Transformations.translation(0, 0, 5) left_wall.transform = left_wall.transform.dot( Transformations.rotation_y(-math.pi / 4)) left_wall.transform = left_wall.transform.dot( Transformations.rotation_x(math.pi / 2)) left_wall.transform = left_wall.transform.dot( Transformations.scaling(10, 0.1, 10)) left_wall.material = floor.material # The wall on the right is identical to the left wall, but is rotated the opposite direction in y
from sphere import Sphere from ray import Ray W = 200 H = 200 D = 200 im = Image.new('RGB', (W, H)) pix = im.load() if False: ## this is done in image coordinates eye = Point(W / 2, H / 2, D) sphere = Sphere() ts = Matrix.scale(50, 50, 50) tt = Matrix.translate(W / 2, H / 2, D / 4) sphere.transform = tt * ts for x in range(W): for y in range(H): ray = Ray(eye, Point(x, y, 0) - eye) xs = sphere.intersect(ray) if xs: pix[x, y] = (255, 0, 0) print(x) else: ## this is done in object coordinates eye = Point(0, 0, -5) sphere = Sphere() tt = Matrix.translate(0, 0, -2) sphere.transform = tt