def render(self, d, view, t): if view is None: view = visible() push() try: style = d['style'] if style == 'solid': color(d['color'], d['_alpha']) rectangle(view) elif style == 'horz': alpha = d['_alpha'] top = colors.Color(d['color'], alpha) bottom = colors.Color(d['color2'], alpha) rectangle(view, sw=bottom, se=bottom, nw=top, ne=top) elif style == 'vert': alpha = d['_alpha'] left = colors.Color(d['color'], alpha) right = colors.Color(d['color2'], alpha) rectangle(view, sw=left, se=right, nw=left, ne=right) else: raise ValueError, "Fill() element doesn't understand style '%s'" % ( style, ) finally: pop()
def test_shadows__full_scene(self): """Test that we identify a shadow in a full scene""" # First sphere is at z=10 s1 = shapes.Sphere() s1.set_transform(transforms.Translate(0, 0, 10)) # Second sphere is at the origin s2 = shapes.Sphere() s2.material = materials.Material() # Light is at z=-10 l1 = lights.Light(position=points.Point(0, 0, -10), intensity=colors.Color(1, 1, 1)) scene = scenes.Scene(objects=[s1, s2], lights=[l1]) # The ray is at z=5 (i.e. between the spheres), pointing at the further # out sphere ray = rays.Ray(points.Point(0, 0, 5), vectors.Vector(0, 0, 1)) isection = intersections.Intersection(s2, 4) comps = isection.precompute(ray) result, _ = scene.shade_hit(comps) self.assertEqual(result, colors.Color(0.1, 0.1, 0.1))
def test_pattern(self): """Test that the pattern of a material can change its color""" m = materials.Material(pattern=patterns.StripePattern( colors.Color( 0, 0, 0, ), colors.Color(1, 1, 1)), ambient=1, diffuse=0, specular=0) eyev = vectors.Vector(0, 0, -1) normalv = vectors.Vector(0, 0, -1) light = lights.PointLight(points.Point(0, 0, -10), colors.Color(1, 1, 1)) color_1 = m.lighting(light, points.Point(0.9, 0, 0), eyev, normalv, in_shadow=False) color_2 = m.lighting(light, points.Point(1.1, 0, 0), eyev, normalv, in_shadow=False) self.assertEqual(color_1, colors.Color(0, 0, 0)) self.assertEqual(color_2, colors.Color(1, 1, 1))
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_reflection__reflective(self): """Test the reflection color of a reflective material is not black""" p = shapes.Plane(material=materials.Material(reflective=0.5)) p.set_transform(transforms.Translate(0, -1, 0)) world = copy.deepcopy(self.default_scene) world.objects.append(p) r = rays.Ray(points.Point(0, 0, -3), vectors.Vector(0, -math.sqrt(2) / 2, math.sqrt(2) / 2)) i = intersections.Intersection(p, math.sqrt(2)) comps = i.precompute(r) # Test reflected color alone result, _ = world.reflected_color(comps) self.assertEqual(result, colors.Color(0.19032, 0.2379, 0.14274)) # Now test the reflected color is added to the surface color result, _ = world.shade_hit(comps) self.assertEqual(result, colors.Color(0.87677, 0.92436, 0.82918))
def test_gradient(self): """Test that the gradient pattern works""" p = patterns.GradientPattern(WHITE, BLACK) self.assertEqual(p.pattern_at(points.Point( 0, 0, 0, )), WHITE) self.assertEqual(p.pattern_at(points.Point( 0.25, 0, 0, )), colors.Color(0.75, 0.75, 0.75)) self.assertEqual(p.pattern_at(points.Point( 0.5, 0, 0, )), colors.Color(0.5, 0.5, 0.5)) self.assertEqual(p.pattern_at(points.Point( 0.75, 0, 0, )), colors.Color(0.25, 0.25, 0.25))
def test_render_scene(self): """Test we can render a pixel in a simple scene""" # Inner sphere size 0.5, centered on the origin s1 = shapes.Sphere() s1.set_transform(transforms.Scale(0.5,0.5,0.5)) # Outer sphere centered on the origin, size 1.0 s2 = shapes.Sphere() s2.material = materials.Material( color=colors.Color(0.8, 1.0, 0.6), diffuse=0.7, specular=0.2) l1 = lights.Light( position=points.Point(-10, 10, -10), intensity=colors.Color(1, 1, 1) ) scene = scenes.Scene( objects = [s1, s2], lights = [l1] ) cam = cameras.Camera(11, 11, math.pi/2) from_point = points.Point(0, 0, -5) to_point = points.Point(0, 0, 0) up = vectors.Vector(0, 1, 0) cam.transform = transforms.ViewTransform(from_point, to_point, up) image = cam.render(scene) self.assertEqual(image.get(5, 5), colors.Color(0.3807, 0.4758, 0.2855))
def test_color_multiplication(self): """Test that we can multiple two colors""" c1 = colors.Color(1, 0.2, 0.4) c2 = colors.Color(0.9, 1, 0.1) c3 = c1 * c2 self.assertEqual(c3, colors.Color(0.9, 0.2, 0.04))
def test_lighting__shadow(self): """Test that we get the ambient color if we're in shadow""" m = materials.Material() p = points.Point(0, 0, 0) eyev = vectors.Vector(0, 0, -1) normalv = vectors.Vector(0, 0, -1) light = lights.PointLight(points.Point(0, 0, -10), colors.Color(1, 1, 1)) result = m.lighting(light, p, eyev, normalv, in_shadow=True) self.assertEqual(result, colors.Color(0.1, 0.1, 0.1))
def test_color_basic_operations(self): """Test that the basic operations work on colors""" c1 = colors.Color(0.9, 0.6, 0.75) c2 = colors.Color(0.7, 0.1, 0.25) c3 = c1 + c2 self.assertEqual(c3, colors.Color(1.6, 0.7, 1.0)) c4 = c1 - c2 self.assertEqual(c4, colors.Color(0.2, 0.5, 0.5)) c5 = c1 * 2 self.assertEqual(c5, colors.Color(1.8, 1.2, 1.5))
def test_lighting(self): """Tests on the lighting function for various angles and colors""" m = materials.Material() p = points.Point(0, 0, 0) #the eye is positioned directly between the light and the surface, with #the normal pointing at the eye. Expect ambient, diffuse, and specular #to all be at full strength. This means that the total intensity should #be 0.1 (the ambient value) + 0.9 (the diffuse value) + 0.9 (the #specular value), or 1.9 eyev = vectors.Vector(0, 0, -1) normalv = vectors.Vector(0, 0, -1) light = lights.PointLight(points.Point(0, 0, -10), colors.Color(1, 1, 1)) result = m.lighting(light, p, eyev, normalv) self.assertEqual(result, colors.Color(1.9, 1.9, 1.9)) eyev = vectors.Vector(0, math.sqrt(2) / 2, -math.sqrt(2) / 2) normalv = vectors.Vector(0, 0, -1) light = lights.PointLight(points.Point(0, 0, -10), colors.Color(1, 1, 1)) result = m.lighting(light, p, eyev, normalv) self.assertEqual(result, colors.Color(1.0, 1.0, 1.0)) eyev = vectors.Vector(0, 0, -1) normalv = vectors.Vector(0, 0, -1) light = lights.PointLight(points.Point(0, 10, -10), colors.Color(1, 1, 1)) result = m.lighting(light, p, eyev, normalv) self.assertEqual(result, colors.Color(0.7364, 0.7364, 0.7364)) eyev = vectors.Vector(0, -math.sqrt(2) / 2, -math.sqrt(2) / 2) normalv = vectors.Vector(0, 0, -1) light = lights.PointLight(points.Point(0, 10, -10), colors.Color(1, 1, 1)) result = m.lighting(light, p, eyev, normalv) self.assertEqual(result, colors.Color(1.6364, 1.6364, 1.6364)) # Light behind the object, its color should be the ambient value eyev = vectors.Vector(0, 0, -1) normalv = vectors.Vector(0, 0, -1) light = lights.PointLight(points.Point(0, 0, 10), colors.Color(1, 1, 1)) result = m.lighting(light, p, eyev, normalv) self.assertEqual(result, colors.Color(0.1, 0.1, 0.1))
def __init__(self, width: int, height: int) -> None: self.width = width self.height = height self.canvas = [[colors.Color(0, 0, 0)] * height for _ in range(width)]
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_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_default_scene(self): """Test that the default scene is correct""" self.assertEqual(self.default_scene.lights[0].intensity, colors.Color(1, 1, 1)) self.assertEqual(self.default_scene.objects[0], self.s1)
def shade_hit(self, computations: intersections.Computations, remaining=5) -> Tuple[colors.Color, int]: """Given some pre-calculated values about a hit, calculate its color""" surface = colors.Color(0, 0, 0) for light in self.lights: in_shadow = self.is_shadowed(computations.over_point, light) surface += computations.object.material.lighting( light, computations.over_point, computations.eyev, computations.normalv, in_shadow=in_shadow) reflected, _ = self.reflected_color(computations, remaining=remaining) refracted, _ = self.refracted_color(computations, remaining=remaining) material = computations.object.material if material.reflective > 0 and material.transparency > 0: reflectance = computations.schlick return (surface + reflected * reflectance + refracted * (1 - reflectance), remaining) return surface + reflected + refracted, remaining
def test_color__tuple(self): """Test that we can initialize and read from a color""" p = colors.Color(-0.5, 0.4, 1.7) self.assertEqual(p.red, -0.5) self.assertEqual(p.green, 0.4) self.assertEqual(p.blue, 1.7)
def test_point_light_initialization(self): position = points.Point(-1, 3, 5) intensity = colors.Color(0.5, 1, 1) light = lights.PointLight(position, intensity) self.assertEqual(light.position, position) self.assertEqual(light.intensity, intensity)
def test_shade_hit__inside(self): """Test that we shade an individual hit the correct color when inside an object""" r = rays.Ray(points.Point(0, 0, 0), vectors.Vector(0, 0, 1)) self.default_scene.lights = [ lights.PointLight(points.Point(0, 0.25, 0), colors.Color(1, 1, 1)) ] shape = self.default_scene.objects[0] i = intersections.Intersection(shape, 0.5) computations = i.precompute(r) color, _ = self.default_scene.shade_hit(computations) self.assertEqual(color, colors.Color(0.90498, 0.90498, 0.90498))
def diagram_eval( self, *junk ): for k, v in self.tkvars.iteritems(): if isinstance( v, tuple ): self.pdict[k] = colors.Color( v[0].get(), v[1].get(), v[2].get(), v[3].get() ) else: self.pdict[k] = v.get() self.marklist = None self.s.redraw() self.s.focus()
def test_write_to_canvas(self): """Test we can write to and read from a canvas""" c = canvas.Canvas(10, 20) red = colors.Color(1, 0, 0) c.set(2, 3, red) self.assertEqual(c.canvas[2][3], red)
def setUp(self): """Set up a default scene for quick testing""" # Inner sphere size 0.5, centered on the origin self.s1 = shapes.Sphere() self.s1.set_transform(transforms.Scale(0.5, 0.5, 0.5)) # Outer sphere centered on the origin, size 1.0 self.s2 = shapes.Sphere() self.s2.material = materials.Material(color=colors.Color( 0.8, 1.0, 0.6), diffuse=0.7, specular=0.2) self.l1 = lights.Light(position=points.Point(-10, 10, -10), intensity=colors.Color(1, 1, 1)) self.default_scene = scenes.Scene(objects=[self.s1, self.s2], lights=[self.l1])
def lighting(self, light: lights.Light, point: points.Point, eyev: vectors.Vector, normalv: vectors.Vector, in_shadow: bool=False) -> colors.Color: """Calculate Phong lighting model for a material""" if self.pattern is not None: color = self.pattern.pattern_at_shape(self.shape, point) else: color = self.color if in_shadow is True: return color * self.ambient # combine the surface color with the light's color/intensity effective_color = color * light.intensity # find the direction to the light source light_to_point = light.position - point lightv = light_to_point.normalize() # compute the ambient contribution ambient = effective_color * self.ambient # light_dot_normal represents the cosine of the angle between the # light vector and the normal vector. A negative number means the # light is on the other side of the surface. light_dot_normal = lightv.dot(normalv) if light_dot_normal < 0: diffuse = BLACK specular = BLACK else: # compute the diffuse contribution diffuse = effective_color * self.diffuse * light_dot_normal # reflect_dot_eye represents the cosine of the angle between the # reflection vector and the eye vector. A negative number means the # light reflects away from the eye. reflectv = -lightv.reflect(normalv) reflect_dot_eye = reflectv.dot(eyev) if reflect_dot_eye <= 0: specular = BLACK else: # compute the specular contribution factor = math.pow(reflect_dot_eye, self.shininess) specular = light.intensity * self.specular * factor return colors.Color( red=ambient.red + diffuse.red + specular.red, green=ambient.green + diffuse.green + specular.green, blue=ambient.blue + diffuse.blue + specular.blue)
def _get_color(value): test_value = getattr(colors, value, None) if test_value is not None: return test_value with suppress(ValueError): test_value = list(map(int, value.split(','))) if len(test_value) in (3, 4): return colors.Color(*test_value) return None
def test_color_at(self): """Tests on the color_at function""" # The ray points away from the object r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 1, 0)) self.assertEqual( self.default_scene.color_at(r)[0], colors.Color(0, 0, 0)) # The ray points at the outer sphere r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) self.assertEqual( self.default_scene.color_at(r)[0], colors.Color(0.38066, 0.47583, 0.2855)) # The ray is outside the inner sphere, but inside the outer sphere, # pointing in. It should return the color of the inner sphere scene = copy.deepcopy(self.default_scene) scene.objects[0].material.ambient = 1 scene.objects[1].material.ambient = 1 r = rays.Ray(points.Point(0, 0, 0.75), vectors.Vector(0, 0, -1)) self.assertEqual(scene.color_at(r)[0], scene.objects[0].material.color)
def test_empty_canvas(self): """Test that an empty canvas is filled with black pixels""" c = canvas.Canvas(10, 20) self.assertEqual(len(c.canvas), 10) self.assertEqual(len(c.canvas[0]), 20) for i in range(10): for j in range(20): self.assertEqual(c.canvas[i][j], colors.Color(0, 0, 0))
def test_ppm_file_content(self): """Test the content of the PPM header is correct""" c = canvas.Canvas(5, 3) c1 = colors.Color(1.5, 0, 0) c2 = colors.Color(0, 0.5, 0) c3 = colors.Color(-0.5, 0, 1) c.set(0, 0, c1) c.set(2, 1, c2) c.set(4, 2, c3) ppm_content = c._get_ppm_file_content() ppm_lines = ppm_content.split("\n") self.assertEqual(ppm_lines[0], "P3") self.assertEqual(ppm_lines[1], "5 3") self.assertEqual(ppm_lines[2], "255") self.assertEqual(ppm_lines[3], "255 0 0 0 0 0 0 0 0 0 0 0 0 0 0") self.assertEqual(ppm_lines[4], "0 0 0 0 0 0 0 128 0 0 0 0 0 0 0") self.assertEqual(ppm_lines[5], "0 0 0 0 0 0 0 0 0 0 0 0 0 0 255")
def test_shade_hit__outside(self): """Test that we shade an individual hit the correct color when outside an object""" r = rays.Ray(points.Point(0, 0, -5), vectors.Vector(0, 0, 1)) shape = self.default_scene.objects[1] i = intersections.Intersection(shape, 4) computations = i.precompute(r) color, _ = self.default_scene.shade_hit(computations) self.assertEqual(color, colors.Color(0.38066, 0.47583, 0.2855))
def reflected_color(self, precomputes: intersections.Computations, remaining: int = 5) -> Tuple[colors.Color, int]: """Calculate the reflected color of a hit on a surface. remaining tracks how many levels of recursion we have done, and when it is zero, simply returns black """ if remaining == 0: # If we have gone too far down the recursion stack, just return # black return colors.Color(0, 0, 0), 0 if precomputes.object.material.reflective == 0: # If the material is not reflective, return black return colors.Color(0, 0, 0), 0 # Fire a new ray from the intersection point at the reflection angle reflect_ray = rays.Ray(precomputes.over_point, precomputes.reflectv) remaining -= 1 color, remaining = self.color_at(reflect_ray, remaining=remaining) return color * precomputes.object.material.reflective, remaining
def refracted_color(self, precomputes: intersections.Computations, remaining: int = 5) -> Tuple[colors.Color, int]: """Calculate the refracted color of a hit on a surface. remaining tracks how many levels of recursion we have done, and when it is zero, simply returns black """ if remaining == 0: # If we have gone too far down the recursion stack, just return # black return colors.Color(0, 0, 0), remaining if precomputes.object.material.transparency == 0: # If the material is not transparent, return black return colors.Color(0, 0, 0), remaining # Check for total internal refraction n_ratio = precomputes.n1 / precomputes.n2 cos_i = precomputes.eyev.dot(precomputes.normalv) sin2_t = n_ratio**2 * (1 - cos_i**2) if sin2_t > 1: return colors.Color(0, 0, 0), remaining cos_t = math.sqrt(1.0 - sin2_t) direction = (precomputes.normalv * (n_ratio * cos_i - cos_t) - precomputes.eyev * n_ratio) refract_ray = rays.Ray(precomputes.under_point, direction) color = (self.color_at(refract_ray, remaining - 1)[0] * precomputes.object.material.transparency) return color, remaining