def split_bounds(self): # figure out the box's largest dimension dx = abs(self.min.x - self.max.x) dy = abs(self.min.y - self.max.y) dz = abs(self.min.z - self.max.z) greatest = max(dx, dy, dz) # variables to help construct the points on # the dividing plane x0, y0, z0 = self.min.x, self.min.y, self.min.z x1, y1, z1 = self.max.x, self.max.y, self.max.z # adjust the points so that they lie on the # dividing plane if greatest == dx: x0 = x1 = x0 + dx / 2.0 elif greatest == dy: y0 = y1 = y0 + dy / 2.0 else: z0 = z1 = z0 + dz / 2.0 mid_min = point(x0, y0, z0) mid_max = point(x1, y1, z1) # construct and return the two halves of # the bounding box left = BoundingBox(self.min, mid_max) right = BoundingBox(mid_min, self.max) return left, right
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 __init__(self, min: Point = None, max: Point = None): if min is None: self.min = point(float('inf'), float('inf'), float('inf')) else: self.min = min if max is None: self.max = point(float('-inf'), float('-inf'), float('-inf')) else: self.max = max
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 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 ray_for_pixel(self, px: int, py: int) -> Ray: # the offset from the edge of the canvas to pixel's center xoffset = (px + 0.5) * self.pixel_size yoffset = (py + 0.5) * self.pixel_size # the untransformed coordinates of the pixel in world space. # (remember that the camera looks toward -z, so +x is to the *left*.) world_x = self.half_width - xoffset world_y = self.half_height - yoffset # using the camera matrix, transform the canvas point and the origin, # and then compute the ray's direction vector. # (remember that the canvas is at z=-1) pixel = self.transform.inverse() * point(world_x, world_y, -1) origin = self.transform.inverse() * point(0, 0, 0) direction = (pixel - origin).normalize() return Ray(origin, direction)
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 local_intersect(self, shape_ray: Ray) -> GroupIntersections: # the vector from the sphere's center, to the ray origin # remember: the sphere is centered at the world origin sphere_to_ray = shape_ray.origin - point(0, 0, 0) a = Vec3.dot(shape_ray.direction, shape_ray.direction) b = 2 * Vec3.dot(shape_ray.direction, sphere_to_ray) c = Vec3.dot(sphere_to_ray, sphere_to_ray) - 1 discriminant = (b**2) - (4 * a * c) if discriminant < 0: return GroupIntersections() else: t1 = Intersection((-b - math.sqrt(discriminant)) / (2 * a), self) if discriminant == 0: return GroupIntersections(t1, t1) t2 = Intersection((-b + math.sqrt(discriminant)) / (2 * a), self) return GroupIntersections( t1, t2) if t1 < t2 else GroupIntersections(t2, t1)
def transform(self, matrix: Matrix): p1 = self.min p2 = point(self.min.x, self.min.y, self.max.z) p3 = point(self.min.x, self.max.y, self.min.z) p4 = point(self.min.x, self.max.y, self.max.z) p5 = point(self.max.x, self.min.y, self.min.z) p6 = point(self.max.x, self.min.y, self.max.z) p7 = point(self.max.x, self.max.y, self.min.z) p8 = self.max new_bounding_box = BoundingBox() for p in [p1, p2, p3, p4, p5, p6, p7, p8]: new_bounding_box.add(matrix * p) return new_bounding_box
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.full_quarter * context.p assert expected == result, f'full_quarter * p != {expected}'
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.half_quarter * context.p assert expected == result, f'half_quarter * p({result}) != {expected}'
def step_impl(context, x, y, z): context.p = point(x, y, z)
def step_impl(context, attribute, min_x, min_y, min_z, max_x, max_y, max_z): setattr( context, attribute, BoundingBox(point(min_x, min_y, min_z), point(max_x, max_y, max_z)))
def step_impl(context, x, y, z): context.to_point = point(x, y, z)
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.T * context.p assert expected == result
def step_impl(context, t, x, y, z): expected = point(x, y, z) result = context.r.position(t) assert expected == result, f'position(r, 0) - {result} != {expected}'
def step_impl(context, px, py, pz, vx, vy, vz): context.r = Ray(point(px, py, pz), vector(vx, vy, vz))
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.r2.origin assert expected == result, f'r2.origin != {expected}'
def step_impl(context, x, y, z): context.origin = point(x, y, 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, attribute, x, y, z): expected = point(x, y, z) obj = getattr(context, attribute) result = obj.max is_equal = expected == result assert is_equal, f'box.max:{result} != {expected}'
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.p2 assert expected == result, f'p2({result}) != {expected}'
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.p4 assert expected == result, f'p4 != {expected}'
def convert_position_to_canvas(canvas: Canvas, world_position: Vec3): """Converts the position from world to canvas. This is done by subtracting the y world position from the canvas height, the other positions are left alone.""" return point(round(world_position.x), round(canvas.height - world_position.y), round(world_position.z))
def step_impl(context, x, y, z): context.from_point = point(x, y, z)
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.transform * context.p assert expected == result, f'transform * p != point({x}, {y}, {z})'
def bounds_of(self) -> BoundingBox: return BoundingBox(point(-1, -1, -1), point(1, 1, 1))
def step_impl(context, x, y, z): expected = point(x, y, z) result = context.inv * context.p assert expected == result, f'inv * p != point({x}, {y}, {z})'
proj.velocity = proj.velocity + env.gravity + env.wind return Projectile(proj.position, proj.velocity) # 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 y world position from the canvas height, the other positions are left alone.""" return point(round(world_position.x), round(canvas.height - world_position.y), round(world_position.z)) if __name__ == '__main__': start = point(0, 1, 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
def bounds_of(self) -> BoundingBox: a = abs(self.minimum) b = abs(self.maximum) limit = max(a, b) return BoundingBox(point(-limit, self.minimum, -limit), point(limit, self.maximum, limit))