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}"
def test_default_orientation_matrix(): f = Point(0, 0, 0) to = Point(0, 0, -1) up = Vector(0, 1, 0) t = ViewTransform(f, to, up) print(t) assert t == Identity()
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 test_camera_attributes(): hsize = 160 vsize = 120 fov = math.pi / 2 c = Camera(hsize, vsize, fov) assert c.hsize == 160 assert c.vsize == 120 assert c.fov == math.pi / 2 assert c.transform == Identity()
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")
def __init__(self): self.transform = Identity() self.material = Material() self.parent = None
def test_default_transform(): pattern = _TestPattern() assert pattern.transform == Identity()
def __init__(self): self.transform = Identity()
def test_group_creation(): g = Group() assert g.transform == Identity() assert len(g.objects) == 0
def test_default_transform(): s = _TestShape() assert s.transform == Identity()
def test_identity_transpose(): m = Identity() assert m == m.transpose()
def test_identity_mult(): m = Matrix([[0, 1, 2, 4], [1, 2, 4, 8], [2, 4, 8, 16], [4, 8, 16, 32]]) i = Identity() assert m * i == m
def test_glassy_sphere(): s = GlassSphere() assert s.transform == Identity() assert s.material.transparency == 1.0 assert s.material.refractive_index == 1.5