def demo(width, height, angle_deg, orthogonal, pfm_output, png_output): image = HdrImage(width, height) # Create a world and populate it with a few shapes world = World() for x in [-0.5, 0.5]: for y in [-0.5, 0.5]: for z in [-0.5, 0.5]: world.add( Sphere(transformation=translation(Vec(x, y, z)) * scaling(Vec(0.1, 0.1, 0.1)))) # Place two other balls in the bottom/left part of the cube, so # that we can check if there are issues with the orientation of # the image world.add( Sphere(transformation=translation(Vec(0.0, 0.0, -0.5)) * scaling(Vec(0.1, 0.1, 0.1)))) world.add( Sphere(transformation=translation(Vec(0.0, 0.5, 0.0)) * scaling(Vec(0.1, 0.1, 0.1)))) # Initialize a camera camera_tr = rotation_z(angle_deg=angle_deg) * translation( Vec(-1.0, 0.0, 0.0)) if orthogonal: camera = OrthogonalCamera(aspect_ratio=width / height, transformation=camera_tr) else: camera = PerspectiveCamera(aspect_ratio=width / height, transformation=camera_tr) # Run the ray-tracer tracer = ImageTracer(image=image, camera=camera) def compute_color(ray: Ray) -> Color: if world.ray_intersection(ray): return WHITE else: return BLACK tracer.fire_all_rays(compute_color) # Save the HDR image with open(pfm_output, "wb") as outf: image.write_pfm(outf) print(f"HDR demo image written to {pfm_output}") # Apply tone-mapping to the image image.normalize_image(factor=1.0) image.clamp_image() # Save the LDR image with open(png_output, "wb") as outf: image.write_ldr_image(outf, "PNG") print(f"PNG demo image written to {png_output}")
def test_translations(self): tr1 = translation(Vec(1.0, 2.0, 3.0)) assert tr1.is_consistent() tr2 = translation(Vec(4.0, 6.0, 8.0)) assert tr1.is_consistent() prod = tr1 * tr2 assert prod.is_consistent() expected = translation(Vec(5.0, 8.0, 11.0)) assert prod.is_close(expected)
def testTransformation(self): sphere = Sphere(transformation=translation(Vec(10.0, 0.0, 0.0))) ray1 = Ray(origin=Point(10, 0, 2), dir=-VEC_Z) intersection1 = sphere.ray_intersection(ray1) assert intersection1 assert HitRecord( world_point=Point(10.0, 0.0, 1.0), normal=Normal(0.0, 0.0, 1.0), surface_point=Vec2d(0.0, 0.0), t=1.0, ray=ray1, material=sphere.material, ).is_close(intersection1) ray2 = Ray(origin=Point(13, 0, 0), dir=-VEC_X) intersection2 = sphere.ray_intersection(ray2) assert intersection2 assert HitRecord( world_point=Point(11.0, 0.0, 0.0), normal=Normal(1.0, 0.0, 0.0), surface_point=Vec2d(0.0, 0.5), t=2.0, ray=ray2, material=sphere.material, ).is_close(intersection2) # Check if the sphere failed to move by trying to hit the untransformed shape assert not sphere.ray_intersection( Ray(origin=Point(0, 0, 2), dir=-VEC_Z)) # Check if the *inverse* transformation was wrongly applied assert not sphere.ray_intersection( Ray(origin=Point(-10, 0, 0), dir=-VEC_Z))
def testFlatRenderer(self): sphere_color = Color(1.0, 2.0, 3.0) sphere = Sphere(transformation=translation(Vec(2, 0, 0)) * scaling(Vec(0.2, 0.2, 0.2)), material=Material(brdf=DiffuseBRDF( pigment=UniformPigment(sphere_color)))) image = HdrImage(width=3, height=3) camera = OrthogonalCamera() tracer = ImageTracer(image=image, camera=camera) world = World() world.add_shape(sphere) renderer = FlatRenderer(world=world) tracer.fire_all_rays(renderer) assert image.get_pixel(0, 0).is_close(BLACK) assert image.get_pixel(1, 0).is_close(BLACK) assert image.get_pixel(2, 0).is_close(BLACK) assert image.get_pixel(0, 1).is_close(BLACK) assert image.get_pixel(1, 1).is_close(sphere_color) assert image.get_pixel(2, 1).is_close(BLACK) assert image.get_pixel(0, 2).is_close(BLACK) assert image.get_pixel(1, 2).is_close(BLACK) assert image.get_pixel(2, 2).is_close(BLACK)
def testRayIntersections(self): world = World() sphere1 = Sphere(transformation=translation(VEC_X * 2)) sphere2 = Sphere(transformation=translation(VEC_X * 8)) world.add_shape(sphere1) world.add_shape(sphere2) intersection1 = world.ray_intersection( Ray(origin=Point(0.0, 0.0, 0.0), dir=VEC_X)) assert intersection1 assert intersection1.world_point.is_close(Point(1.0, 0.0, 0.0)) intersection2 = world.ray_intersection( Ray(origin=Point(10.0, 0.0, 0.0), dir=-VEC_X)) assert intersection2 assert intersection2.world_point.is_close(Point(9.0, 0.0, 0.0))
def test_quick_ray_intersection(self): world = World() sphere1 = Sphere(transformation=translation(VEC_X * 2)) sphere2 = Sphere(transformation=translation(VEC_X * 8)) world.add_shape(sphere1) world.add_shape(sphere2) assert not world.is_point_visible(point=Point(10.0, 0.0, 0.0), observer_pos=Point(0.0, 0.0, 0.0)) assert not world.is_point_visible(point=Point(5.0, 0.0, 0.0), observer_pos=Point(0.0, 0.0, 0.0)) assert world.is_point_visible(point=Point(5.0, 0.0, 0.0), observer_pos=Point(4.0, 0.0, 0.0)) assert world.is_point_visible(point=Point(0.5, 0.0, 0.0), observer_pos=Point(0.0, 0.0, 0.0)) assert world.is_point_visible(point=Point(0.0, 10.0, 0.0), observer_pos=Point(0.0, 0.0, 0.0)) assert world.is_point_visible(point=Point(0.0, 0.0, 10.0), observer_pos=Point(0.0, 0.0, 0.0))
def parse_transformation(input_file, scene: Scene): result = Transformation() while True: transformation_kw = expect_keywords(input_file, [ KeywordEnum.IDENTITY, KeywordEnum.TRANSLATION, KeywordEnum.ROTATION_X, KeywordEnum.ROTATION_Y, KeywordEnum.ROTATION_Z, KeywordEnum.SCALING, ]) if transformation_kw == KeywordEnum.IDENTITY: pass # Do nothing (this is a primitive form of optimization!) elif transformation_kw == KeywordEnum.TRANSLATION: expect_symbol(input_file, "(") result *= translation(parse_vector(input_file, scene)) expect_symbol(input_file, ")") elif transformation_kw == KeywordEnum.ROTATION_X: expect_symbol(input_file, "(") result *= rotation_x(expect_number(input_file, scene)) expect_symbol(input_file, ")") elif transformation_kw == KeywordEnum.ROTATION_Y: expect_symbol(input_file, "(") result *= rotation_y(expect_number(input_file, scene)) expect_symbol(input_file, ")") elif transformation_kw == KeywordEnum.ROTATION_Z: expect_symbol(input_file, "(") result *= rotation_z(expect_number(input_file, scene)) expect_symbol(input_file, ")") elif transformation_kw == KeywordEnum.SCALING: expect_symbol(input_file, "(") result *= scaling(parse_vector(input_file, scene)) expect_symbol(input_file, ")") # We must peek the next token to check if there is another transformation that is being # chained or if the sequence ends. Thus, this is a LL(1) parser. next_kw = input_file.read_token() if (not isinstance(next_kw, SymbolToken)) or (next_kw.symbol != "*"): # Pretend you never read this token and put it back! input_file.unread_token(next_kw) break return result
def test_perspective_camera_transform(self): cam = PerspectiveCamera(transformation=translation(-VEC_Y * 2.0) * rotation_z(pi / 2.0)) ray = cam.fire_ray(0.5, 0.5) assert ray.at(1.0).is_close(Point(0.0, -2.0, 0.0))
def test_orthogonal_camera_transform(self): cam = OrthogonalCamera(transformation=translation(-VEC_Y * 2.0) * rotation_z(angle_deg=90)) ray = cam.fire_ray(0.5, 0.5) assert ray.at(1.0).is_close(Point(0.0, -2.0, 0.0))
def test_transform(self): ray = Ray(origin=Point(1.0, 2.0, 3.0), dir=Vec(6.0, 5.0, 4.0)) transformation = translation(Vec(10.0, 11.0, 12.0)) * rotation_x(90.0) transformed = ray.transform(transformation) assert transformed.origin.is_close(Point(11.0, 8.0, 14.0)) assert transformed.dir.is_close(Vec(6.0, -4.0, 5.0))
def test_parser(self): stream = StringIO(""" float clock(150) material sky_material( diffuse(uniform(<0, 0, 0>)), uniform(<0.7, 0.5, 1>) ) # Here is a comment material ground_material( diffuse(checkered(<0.3, 0.5, 0.1>, <0.1, 0.2, 0.5>, 4)), uniform(<0, 0, 0>) ) material sphere_material( specular(uniform(<0.5, 0.5, 0.5>)), uniform(<0, 0, 0>) ) plane (sky_material, translation([0, 0, 100]) * rotation_y(clock)) plane (ground_material, identity) sphere(sphere_material, translation([0, 0, 1])) camera(perspective, rotation_z(30) * translation([-4, 0, 1]), 1.0, 2.0) """) scene = parse_scene(input_file=InputStream(stream)) # Check that the float variables are ok assert len(scene.float_variables) == 1 assert "clock" in scene.float_variables.keys() assert scene.float_variables["clock"] == 150.0 # Check that the materials are ok assert len(scene.materials) == 3 assert "sphere_material" in scene.materials assert "sky_material" in scene.materials assert "ground_material" in scene.materials sphere_material = scene.materials["sphere_material"] sky_material = scene.materials["sky_material"] ground_material = scene.materials["ground_material"] assert isinstance(sky_material.brdf, DiffuseBRDF) assert isinstance(sky_material.brdf.pigment, UniformPigment) assert sky_material.brdf.pigment.color.is_close(Color(0, 0, 0)) assert isinstance(ground_material.brdf, DiffuseBRDF) assert isinstance(ground_material.brdf.pigment, CheckeredPigment) assert ground_material.brdf.pigment.color1.is_close( Color(0.3, 0.5, 0.1)) assert ground_material.brdf.pigment.color2.is_close( Color(0.1, 0.2, 0.5)) assert ground_material.brdf.pigment.num_of_steps == 4 assert isinstance(sphere_material.brdf, SpecularBRDF) assert isinstance(sphere_material.brdf.pigment, UniformPigment) assert sphere_material.brdf.pigment.color.is_close(Color( 0.5, 0.5, 0.5)) assert isinstance(sky_material.emitted_radiance, UniformPigment) assert sky_material.emitted_radiance.color.is_close( Color(0.7, 0.5, 1.0)) assert isinstance(ground_material.emitted_radiance, UniformPigment) assert ground_material.emitted_radiance.color.is_close(Color(0, 0, 0)) assert isinstance(sphere_material.emitted_radiance, UniformPigment) assert sphere_material.emitted_radiance.color.is_close(Color(0, 0, 0)) # Check that the shapes are ok assert len(scene.world.shapes) == 3 assert isinstance(scene.world.shapes[0], Plane) assert scene.world.shapes[0].transformation.is_close( translation(Vec(0, 0, 100)) * rotation_y(150.0)) assert isinstance(scene.world.shapes[1], Plane) assert scene.world.shapes[1].transformation.is_close(Transformation()) assert isinstance(scene.world.shapes[2], Sphere) assert scene.world.shapes[2].transformation.is_close( translation(Vec(0, 0, 1))) # Check that the camera is ok assert isinstance(scene.camera, PerspectiveCamera) assert scene.camera.transformation.is_close( rotation_z(30) * translation(Vec(-4, 0, 1))) assert pytest.approx(1.0) == scene.camera.aspect_ratio assert pytest.approx(2.0) == scene.camera.screen_distance
def _default_matrix(self): '''Sets a default transformation matrix for the shape ''' R1 = transformations.rotation(self.angle) T1 = transformations.translation(self.xy) return transformations.compose(R1, T1)