def refracted_color(self, comps: Precomputed, remaining: int = 5) -> Color: if remaining <= 0: return Color(0, 0, 0) if comps.object.material.transparency == 0: return Color(0, 0, 0) # Find the ratio of the first index of refraction to the second. # (Yup, this is inverted from the definition of Snell's Law.) n_ratio = comps.n1 / comps.n2 # cos(theta_i) is the same as the dot product of the two vectors cos_i = Vec3.dot(comps.eyev, comps.normalv) # Find sin(theta_t)^2 via trigonometric identity sin2_t = n_ratio**2 * (1 - cos_i**2) if sin2_t > 1: return Color(0, 0, 0) # Find cos(theta_t) via trigonometric identity cos_t = math.sqrt(1.0 - sin2_t) # Compute the direction of the refracted ray direction = comps.normalv * (n_ratio * cos_i - cos_t) - comps.eyev * n_ratio # Create the refracted ray refract_ray = Ray(comps.under_point, direction) # Find the color of the refracted ray, making sure to multiply # by the transparency value to account for any opacity result = self.color_at(refract_ray, remaining - 1) *\ comps.object.material.transparency return result
def ch7(): # Step 1 floor = Sphere() floor.transform = Matrix.scaling(10, 0.01, 10) floor.material = Material() floor.material.color = Color(1, 0.9, 0.9) floor.material.specular = 0 # Step 2 left_wall = Sphere() left_wall.transform = Matrix.translation(0, 0, 5) * Matrix.rotation_y(-45) * \ Matrix.rotation_x(90) * Matrix.scaling(10, 0.01, 10) left_wall.material = floor.material # Step 3 right_wall = Sphere() right_wall.transform = Matrix.translation(0, 0, 5) * Matrix.rotation_y(45) * \ Matrix.rotation_x(90) * Matrix.scaling(10, 0.01, 10) right_wall.material = floor.material # Step 4 middle = Sphere() middle.transform = Matrix.translation(-0.5, 1, 0.5) middle.material = Material() middle.material.color = Color(0.1, 1, 0.5) middle.material.diffuse = 0.7 middle.material.specular = 0.3 # Step 5 right = Sphere() right.transform = Matrix.translation(1.5, 0.5, -0.5) * Matrix.scaling( 0.5, 0.5, 0.5) right.material = Material() right.material.color = Color(0.5, 1, 0.1) right.material.diffuse = 0.7 right.material.specular = 0.3 # Step 6 left = Sphere() left.transform = Matrix.translation(-1.5, 0.33, -0.75) * Matrix.scaling( 0.33, 0.33, 0.33) left.material = Material() left.material.color = Color(1, 0.8, 0.1) left.material.diffuse = 0.7 left.material.specular = 0.3 world = World() world.light = PointLight(point(-10, 10, -10), Color(1, 1, 1)) world.objects.extend([floor, left_wall, right_wall, middle, right, left]) camera = Camera(100, 50, 60) # camera = Camera(500, 250, 60) camera.transform = view_transform(point(0, 1.5, -5), point(0, 1, 0), vector(0, 1, 0)) canvas = camera.render(world) with open('ch8.ppm', 'w') as fp: fp.write(canvas.to_ppm())
def reflected_color(self, comps: Precomputed, remaining: int = 5) -> Color: if remaining <= 0: return Color(0, 0, 0) if comps.object.material.reflective == 0.0: return Color(0, 0, 0) reflect_ray = Ray(comps.over_point, comps.reflectv) color = self.color_at(reflect_ray, remaining - 1) return color * comps.object.material.reflective
def lighting(self, obj, light: PointLight, point: Point, eyev: Vector, normalv: Vector, in_shadow: bool) -> Color: """Calculates the color shaded on the surface of the object using various properties from the material. :param obj: The Shape object that will be used if a pattern is set. :param light: A light source that potentially intersects with the surface. :param point: A point in the space. :param eyev: A vector representing the direction from the point on the surface to the eye of the view :param normalv: The normal of the surface. :param in_shadow: Boolean indicating if the point is in shadow between an object and the light source. :return: """ color = self.pattern.pattern_at_shape( obj, point) if self.pattern is not None else self.color # combine the surface color with the light's color/intensity effective_color = color * light.intensity # find the direction to the light source lightv = (light.position - point).normalize() # compute the ambient contribution ambient = effective_color * self.ambient # if the point is in shadow, ambient should be the only color factored into if in_shadow: return ambient # light_dot_normal represent 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 = Vec3.dot(lightv, normalv) if light_dot_normal < 0: diffuse = Color(0, 0, 0) specular = Color(0, 0, 0) 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 = reflect(-lightv, normalv) reflect_dot_eye = Vec3.dot(reflectv, eyev) if reflect_dot_eye <= 0: specular = Color(0, 0, 0) else: # compute the specular contribution factor = reflect_dot_eye**self.shininess specular = light.intensity * self.specular * factor return ambient + diffuse + specular
def default_world(): world = World() world.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 world.objects.append(s1) s2 = Sphere() s2.transform = Matrix.scaling(0.5, 0.5, 0.5) world.objects.append(s2) return world
def color_at(self, ray: Ray, remaining: int = 5) -> Color: intersections = self.intersect_world(ray) intersect = intersections.hit() if intersect is None: return Color(0, 0, 0) precomputed = intersect.prepare_computations(ray) return self.shade_hit(precomputed, remaining)
def ch9(): # Step 1 floor = Plane() floor.transform = Matrix.scaling(10, 0.01, 10) floor.material = Material() floor.material.color = Color(1, 0.9, 0.9) floor.material.specular = 0 floor.material.pattern = StripePattern(Color(1, 0, 0), Color(0, 0, 1)) # Middle biggest sphere middle = Sphere() middle.transform = Matrix.translation(-0.5, 1, 0.5) middle.material = Material() middle.material.color = Color(0.1, 1, 0.5) middle.material.diffuse = 0.7 middle.material.specular = 0.3 middle.material.pattern = RingPattern(Color(1, 0, 1), Color(1, 1, 1)) middle.material.pattern.transform = Matrix.scaling(0.25, 0.5, 0.25) # Smaller right sphere right = Sphere() right.transform = Matrix.translation(1.5, 0.5, -0.5) * Matrix.scaling(0.5, 0.5, 0.5) right.material = Material() right.material.color = Color(0.5, 1, 0.1) right.material.diffuse = 0.7 right.material.specular = 0.3 right.material.reflective = 1.0 # Left yellow sphere left = Sphere() left.transform = Matrix.translation(-1.5, 0.33, -0.75) * Matrix.scaling(0.33, 0.33, 0.33) left.material = Material() left.material.color = Color(1, 0.8, 0.1) left.material.diffuse = 0.7 left.material.specular = 0.3 world = World() world.light = PointLight(point(-10, 10, -10), Color(1, 1, 1)) world.objects.extend([floor, middle, right, left]) camera = Camera(100, 50, 60) # camera = Camera(500, 250, 60) camera.transform = view_transform(point(0, 1.5, -5), point(0, 1, 0), vector(0, 1, 0)) canvas = camera.render(world) with open('ch9.ppm', 'w') as fp: fp.write(canvas.to_ppm())
def __init__(self, width, height): self.width = width self.height = height self.pixels = [] # Set each pixel to be black by default for row in range(self.height): pixel_rows = [] for column in range(self.width): pixel_rows.append(Color(0, 0, 0)) self.pixels.append(pixel_rows)
def ch14(): world = World() world.light = PointLight(point(-10, 10, -10), Color(1, 1, 1)) hex = hexagon() world.objects.append(hex) camera = Camera(100, 50, 60) camera.transform = view_transform(point(0, 2, -1), point(0, 0, 0), vector(0, 1, 0)) canvas = camera.render(world) with open('ch14.ppm', 'w') as fp: fp.write(canvas.to_ppm())
def __init__(self, color=None, ambient=0.1, diffuse=0.9, specular=0.9, shininess=200.0): self.color = Color(1, 1, 1) if color is None else color self.ambient = ambient self.diffuse = diffuse self.specular = specular self.shininess = shininess self.pattern = None self.reflective = 0.0 self.transparency = 0.0 self.refractive_index = 1.0
def clean(self, filter_func): new_img_data = [] color_black = (0, 0, 0) color_white = (255, 255, 255) for i, colordata in enumerate(self.im.convert("HSV").getdata()): color = Color(colordata) if filter_func(color): new_img_data.append(color_black) continue new_img_data.append(color_white) self.im = Image.new(self.im.mode, self.im.size) self.im.putdata(new_img_data) return
# Can probably have this done internally in the Canvas class, but left # it here for simplicity and it doesn't hide how the conversion is done. def convert_position_to_canvas(canvas: Canvas, world_position: Vec3): """Converts the position from world to canvas. This is done by subtracting the z world position from the canvas height, the other positions are left alone.""" return point( round(world_position.x + (canvas.width / 2)), round(world_position.y), round((canvas.height - (canvas.height / 2) - world_position.z))) if __name__ == '__main__': width = 400 height = 400 canvas = Canvas(width, height) hour_rotation = 360 / 12 hour_rotation_matrix = Matrix.rotation_y(hour_rotation) radius = (3 / 8) * canvas.width print(f'Radius: {radius}') position = point(0, 0, 1) * radius for i in range(12): canvas_point = convert_position_to_canvas(canvas, position) print(f'Position:{position}\nCanvas:{canvas_point}') canvas.write_pixel(canvas_point.x, canvas_point.z, Color(1, 1, 1)) position = hour_rotation_matrix * position with open('clock2.ppm', 'w') as fp: fp.write(canvas.to_ppm())
def step_impl(context, attribute, r, g, b): expected = Color(r, g, b) result = context for prop in attribute.split('.'): result = getattr(result, prop) assert expected == result, f'{result} != {expected}'
def step_impl(context, attribute, r, g, b): setattr(context, attribute, Color(r, g, b))
0) # The starting position can be set anywhere different velocity = vector(1, 1.8, 0).normalize( ) * 11.25 # This can be changed to any value to change the trajectory p = Projectile(start, velocity) gravity = vector( 0, -0.1, 0) # This can be changed to force the projectile down faster/slower wind = vector( -0.01, 0, 0) # This can changed to force the projectile forward less or more e = Environment(gravity, wind) screen_width = 900 # You can play around with the width of the canvas screen_height = 550 # You can play around with the height of the canvas c = Canvas(screen_width, screen_height) # It's ok if the y position is above the canvas as it can come down into it still. # But with the x-axis, it's not worth it since, once it's off screen, the wind is # is too strong in the beginning or the it will not return on-screen on the right end while p.position.y > 0 and 0 <= p.position.x < c.width: canvas_position = convert_position_to_canvas(c, p.position) c.write_pixel(canvas_position.x, canvas_position.y, Color(1, 0, 0)) p = tick(e, p) # The final process of storing the canvas to a .ppm file. # You can change the name of the file to whatever you like with open('ch1and2.ppm', 'w') as fp: fp.write(c.to_ppm())
def ch5and6(): # start the ray at z = -5 ray_origin = point(0, 0, -5) # put the wall at z = 10 wall_z = 10 wall_size = 7 canvas_pixels = 100 pixel_size = wall_size / canvas_pixels half = wall_size / 2 canvas = Canvas(canvas_pixels, canvas_pixels) shape = Sphere() # Optional sphere transformations - uncomment to see the sphere changes # shape.transform = Matrix.rotation_z(45) * Matrix.scaling(0.5, 1, 1) # shape.transform = Matrix.scaling(1, 0.5, 1) # shape.transform = Matrix.shearing(1, 0, 0, 0, 0, 0) * Matrix.scaling(0.5, 1, 1) # CH 6 # CH 6.1 shape.material.color = Color(1, 0.2, 1) # CH 6.2 light_position = point(-10, 10, -10) light_color = Color(1, 1, 1) light = PointLight(light_position, light_color) # for each row of pixels in the canvas for y in range(canvas_pixels): # compute the world y coordinate (top = +half, bottom = -half) # canvas -> world coordinate conversion # (0, 0) -> (-3.5, 3.5) # (99, 99) -> (3.43, -3.43) world_y = half - pixel_size * y # for each pixel in the row for x in range(canvas_pixels): # compute the world x coordinate (left = -half, right = half) world_x = -half + pixel_size * x # describe the point on the wall that the ray will target position = point(world_x, world_y, wall_z) r = Ray(ray_origin, (position - ray_origin).normalize()) xs = shape.intersect(r) intersection = xs.hit() if intersection is not None: # World position of the intersection intersect_point = r.position(intersection.t) # World position of the normal at the point intersect_normal = intersection.obj.normal_at(intersect_point) # Reverse ray direction to get vector from intersection to eye eye = -r.direction # Color is calculate by the material of the sphere, ambient, diffuse, specular, and the light sources color = intersection.obj.material.lighting( light, intersect_point, eye, intersect_normal) canvas.write_pixel(x, y, color) with open('ch6.ppm', 'w') as fp: fp.write(canvas.to_ppm())
def step_impl(context, x, y, r, g, b): expected = Color(r, g, b) result = context.image.pixel_at(x, y) assert expected == result, f'{result} != {expected}'
def pattern_at(self, point: Vec3) -> Color: return Color(point.x, point.y, point.z)
def step_impl(context, x, y, z, r, g, b): context.w.light = PointLight(point(x, y, z), Color(r, g, b))
def step_impl(context, r1, g1, b1, r2, g2, b2): context.m.pattern = StripePattern(Color(r1, g1, b1), Color(r2, g2, b2))
def step_impl(context, x, y, z): expected = Color(0, 0, 0) result = context.pattern.pattern_at(point(x, y, z)) assert expected == result, f'{result} != {expected}'
def step_impl(context, r, g, b): expected = Color(r, g, b) result = context.result assert expected == result, f'{result} != {expected}'
# Copyright(c) Eric Steinberger 2018 from src.Color import Color from src.config import RobotConfig BLACK = Color(name="black", rgb=[25, 25, 25], position=RobotConfig.POS_COLOR_BLACK, color_id=0, code=[1, 1, 1, 0]) GREEN = Color(name="green", rgb=[0, 148, 84], position=RobotConfig.POS_COLOR_GREEN, color_id=1, code=[1, 0, 0, 0]) BLUE = Color(name="blue", rgb=[1, 51, 236], position=RobotConfig.POS_COLOR_BLUE, color_id=2, code=[1, 0, 1, 0]) YELLOW = Color(name="yellow", rgb=[255, 219, 0], position=RobotConfig.POS_COLOR_YELLOW, color_id=3, code=[0, 1, 0, 0]) RED = Color(name="red", rgb=[240, 14, 17], position=RobotConfig.POS_COLOR_RED, color_id=4, code=[0, 1, 1, 0]) WHITE = Color(name="white",
def step_impl(context, r, g, b): context.intensity = Color(r, g, b)