class Shape: def __init__(self): self.transform = Identity() self.material = Material() self.parent = None def set_material(self, material: Material): self.material = material def set_transform(self, t: Matrix): self.transform *= t def __eq__(self, other): return (self.__class__ == other.__class__ and self.material == other.material and self.parent == other.parent and self.transform == other.transform) def intersect(self, ray: Ray): local_ray = ray.transform(self.transform.inverse()) return self.local_intersect(local_ray) def normal_at(self, world_point: Point): local_point = self.world_to_object(world_point) local_normal = self.local_normal_at(local_point) return self.normal_to_world(local_normal) def world_to_object(self, point: Point): if self.parent is not None: point = self.parent.world_to_object(point) return self.transform.inverse() * point def normal_to_world(self, normal: Vector): normalv = self.transform.inverse().transpose() * normal normalv.w = 0 normalv = normalv.normalize() if self.parent is not None: normalv = self.parent.normal_to_world(normalv) return normalv def local_normal_at(self, local_point): raise TypeError("Generic Shapes cannot be evaluated") def local_intersect(self, ray): raise TypeError("Generic Shapes cannot be evaluated") def __str__(self): return f"{self.__class__}: Transform:\n{self.transform}"
class Camera: def __init__(self, hsize: int, vsize: int, fov: float, transform: Matrix = None): self.hsize = hsize self.vsize = vsize self.fov = fov if transform is None: self.transform = Identity() else: self.transform = transform half_view = math.tan(self.fov / 2) aspect = self.hsize / self.vsize if aspect >= 1: self.half_width = half_view self.half_height = half_view / aspect else: self.half_width = half_view * aspect self.half_height = half_view self.pixel_size = (self.half_width * 2) / self.hsize def ray_for_pixel(self, px, py): x_offset = (px + 0.5) * self.pixel_size y_offset = (py + 0.5) * self.pixel_size world_x = self.half_width - x_offset world_y = self.half_height - y_offset 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 render(self, world: World): image = Canvas(self.hsize, self.vsize) for y in range(self.vsize): print(y) for x in range(self.hsize): ray = self.ray_for_pixel(x, y) color = world.color_at(ray) image.write_pixel(x, y, color) return image
class Pattern: def __init__(self): self.transform = Identity() def set_pattern_transform(self, t: Matrix): self.transform *= t def pattern_at_shape(self, shape, world_point: Point): object_point = shape.world_to_object(world_point) pattern_point = self.transform.inverse() * object_point return self.pattern_at(pattern_point) def pattern_at(self, point): raise TypeError("Generic Pattern Cannot be evaluated")