def test_light_directly_behind_surface_and_eye(self): m = Material() position = Point(0, 0, 0) eyev = Vector(0, 0, -1) normalv = Vector(0, 0, -1) light = PointLight(Point(0, 0, 10), Color(1, 1, 1)) result = lighting(m, light, position, eyev, normalv) self.assertEqual(result, Color(0.1, 0.1, 0.1))
def test_light_and_eye_perpendicular(self): m = Material() position = Point(0, 0, 0) eyev = Vector(0, 0, -1) normalv = Vector(0, 0, -1) light = PointLight(Point(0, 0, -10), Color(1, 1, 1)) result = lighting(m, light, position, eyev, normalv) self.assertEqual(result, Color(1.9, 1.9, 1.9))
def test_light_perpendicular_and_eye_at_45(self): r = math.sqrt(2) / 2 m = Material() position = Point(0, 0, 0) eyev = Vector(0, r, -r) normalv = Vector(0, 0, -1) light = PointLight(Point(0, 0, -10), Color(1, 1, 1)) result = lighting(m, light, position, eyev, normalv) self.assertEqual(result, Color(1.0, 1.0, 1.0))
def test_scaling(self): s = Matrix.scale(2, 3, 4) p = Point(-4, 6, 8) self.assertEqual(s * p, Point(-8, 18, 32)) v = Vector(-4, 6, 8) self.assertEqual(s * v, Vector(-8, 18, 32)) self.assertEqual(s.inverse() * v, Vector(-2, 2, 2)) s = Matrix.scale(-1, 1, 1) p = Point(2, 3, 4) self.assertEqual(s * p, Point(-2, 3, 4))
def test_light_at_45_and_eye_at_neg_45(self): r = math.sqrt(2) / 2 m = Material() position = Point(0, 0, 0) eyev = Vector(0, -r, -r) normalv = Vector(0, 0, -1) light = PointLight(Point(0, 10, -10), Color(1, 1, 1)) result = lighting(m, light, position, eyev, normalv) x = 1.6363961 self.assertColorEqual(result, Color(x, x, x))
def test_point_subtraction(self): # subtracting two points results in a vector p1 = Point(3, 2, 1) p2 = Point(5, 6, 7) self.assertEqual(p1 - p2, Vector(-2, -4, -6)) # subtracting a vector from a point results in a new point p = Point(3, 2, 1) v = Vector(5, 6, 7) self.assertEqual(p - v, Point(-2, -4, -6))
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_reflection(self): v = Vector(1, -1, 0) n = Vector(0, 1, 0) r = v.reflect(n) self.assertEqual(r, Vector(1, 1, 0)) x = math.sqrt(2) / 2 v = Vector(0, -1, 0) n = Vector(x, x, 0) r = v.reflect(n) self.assertTupleEqual(r, Vector(1, 0, 0))
def test_normal(self): s = Sphere() n = s.normal(Point(1, 0, 0)) self.assertEqual(n, Vector(1, 0, 0)) n = s.normal(Point(0, 1, 0)) self.assertEqual(n, Vector(0, 1, 0)) n = s.normal(Point(0, 0, 1)) self.assertEqual(n, Vector(0, 0, 1)) r = math.sqrt(3) / 3 n = s.normal(Point(r, r, r)) self.assertEqual(n, Vector(r, r, r)) self.assertEqual(n, n.norm())
def test_intersect(self): r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].object, s) self.assertEqual(xs[1].object, s)
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_translation(self): t = Matrix.translate(5, -3, 2) p = Point(-3, 4, 5) self.assertEqual(t * p, Point(2, 1, 7)) self.assertEqual(t.inverse() * p, Point(-8, 7, 3)) v = Vector(-3, 4, 5) self.assertEqual(t * v, v) self.assertEqual(t.inverse() * v, v)
def normal(self, point): inv = self.transform.inverse() # transform world point back to object space obj_point = inv * point obj_normal = obj_point - self.center # transform the normal back to world space and normalize it normal = (inv.transpose() * obj_normal).norm() d = normal.x * normal.x + normal.y * normal.y + normal.z * normal.z assert abs(d - 1.0) < 0.01 # the inverse transform can mess up the w-component so we ensure it is clear return Vector(normal.x, normal.y, normal.z)
def test_intersect_sphere(self): # through the center r = Ray(Point(0, 0, -5), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, 4) self.assertEqual(xs[1].t, 6) # at a tangent r = Ray(Point(0, 1, -5), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, 5) self.assertEqual(xs[1].t, 5) # why two points? # no intersection r = Ray(Point(0, 2, -5), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 0) # starting inside the sphere r = Ray(Point(0, 0, 0), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, -1) self.assertEqual(xs[1].t, 1) # starting past the sphere r = Ray(Point(0, 0, 5), Vector(0, 0, 1)) s = Sphere() xs = s.intersect(r) self.assertEqual(len(xs), 2) self.assertEqual(xs[0].t, -6) self.assertEqual(xs[1].t, -4)
def test_translation(self): r = Ray(Point(1, 2, 3), Vector(0, 1, 0)) m = Matrix.translate(3, 4, 5) r2 = m.transform(r) self.assertEqual(r2.origin, Point(4, 6, 8)) self.assertEqual(r2.direction, Vector(0, 1, 0))
def test_dot_product(self): a = Vector(1, 2, 3) b = Vector(2, 3, 4) self.assertEqual(20, a.dot(b))
def test_normalization(self): self.assertEqual(Vector(1, 0, 0), Vector(4, 0, 0).norm()) x = math.sqrt(14) self.assertEqual(Vector(1 / x, 2 / x, 3 / x), Vector(1, 2, 3).norm()) # the magnitude of a normalized vector should always be 1.0 self.assertEqual(1, Vector(1, 2, 3).norm().mag())
def test_vector(self): v = Vector(4.3, -4.2, 3.1) self.assertEqual(v.x, 4.3) self.assertEqual(v.y, -4.2) self.assertEqual(v.z, 3.1) self.assertEqual(v.w, 0.0)
def __str__(self): return "P={}, V={}".format(self.position, self.velocity) class World(object): def __init__(self, gravity, wind): self.gravity = gravity self.wind = wind def tick(world, projectile): p = projectile.position + projectile.velocity v = projectile.velocity + world.gravity + world.wind return Projectile(p, v) W, H = 900, 550 im = Image.new('RGB', (W, H)) pix = im.load() p = Projectile(Point(0, 1, 0), Vector(1, 1.8, 0).norm() * 11.25) w = World(Vector(0, -0.1, 0), Vector(-0.01, 0, 0)) while p.position.y > 0: pix[int(p.position.x), H - int(p.position.y)] = (255, 0, 0) print(p) p = tick(w, p) im.show()
def test_cross_product(self): a = Vector(1, 2, 3) b = Vector(2, 3, 4) self.assertEqual(Vector(-1, 2, -1), a.cross(b)) self.assertEqual(Vector(1, -2, 1), b.cross(a))
def test_scaling(self): r = Ray(Point(1, 2, 3), Vector(0, 1, 0)) m = Matrix.scale(2, 3, 4) r2 = m.transform(r) self.assertEqual(r2.origin, Point(2, 6, 12)) self.assertEqual(r2.direction, Vector(0, 3, 0))
def test_magnitude(self): self.assertEqual(1, Vector(1, 0, 0).mag()) self.assertEqual(1, Vector(0, 1, 0).mag()) self.assertEqual(1, Vector(0, 0, 1).mag()) self.assertEqual(math.sqrt(14), Vector(1, 2, 3).mag()) self.assertEqual(math.sqrt(14), Vector(-1, -2, -3).mag())
def test_vector_tuple_equality(self): a = Vector(4, -4, 3) t = Tuple(4, -4, 3, 0) self.assertEqual(a, t)
def test_ray_position(self): r = Ray(Point(2, 3, 4), Vector(1, 0, 0)) self.assertEqual(r.position(0.0), Point(2, 3, 4)) self.assertEqual(r.position(1.0), Point(3, 3, 4)) self.assertEqual(r.position(-1.0), Point(1, 3, 4)) self.assertEqual(r.position(2.5), Point(4.5, 3, 4))
def test_create_ray(self): origin = Point(1, 2, 3) direction = Vector(4, 5, 6) r = Ray(origin, direction) self.assertEqual(r.origin, origin) self.assertEqual(r.direction, direction)
def test_vector_subtraction(self): # subtracting two vectors results in a vector v1 = Vector(3, 2, 1) v2 = Vector(5, 6, 7) self.assertEqual(v1 - v2, Vector(-2, -4, -6))