開発環境
- macOS Mojave - Apple (OS)
- Emacs (Text Editor)
- Windows 10 Pro (OS)
- Visual Studio Code (Text Editor)
- Python 3.7 (プログラミング言語)
- GIMP (ビットマップ画像編集・加工ソフトウェア、PPM形式(portable pixmap)の画像用)
The Ray Tracer Challenge: A Test-Driven Guide to Your First 3D Renderer (Jamis Buck(著)、Pragmatic Bookshelf)、Chapter 10(Patterns)のPut It Together(138)、Blended patternsを取り組んでみる。
コード
Python 3
patterns_test.py
#!//usr/bin/env python3 from unittest import TestCase, main from patterns import Pattern, Solid, Stripe, Gradient, Ring, Checkers from tuples import Point, Color from spheres import Sphere from transformations import scaling, translation from matrices import IDENTITY_MATRIX class PatternTest(TestCase): def setUp(self): self.pattern = Pattern() def tearDown(self): pass def test_transformation(self): self.assertEqual(self.pattern.transform, IDENTITY_MATRIX) def test_assign_transformation(self): self.pattern.transform = translation(1, 2, 3) self.assertEqual(self.pattern.transform, translation(1, 2, 3)) def test_with_obj_transformation(self): shape = Sphere(scaling(2, 2, 2)) c = self.pattern.at_shape(shape, Point(2, 3, 4)) self.assertEqual(c, Color(1, 1.5, 2)) def test_with_pattern_transformation(self): shape = Sphere() self.pattern.transform = scaling(2, 2, 2) c = self.pattern.at_shape(shape, Point(2, 3, 4)) self.assertEqual(c, Color(1, 1.5, 2)) class StripeTest(TestCase): def setUp(self): self.black = Color(0, 0, 0) self.white = Color(1, 1, 1) self.solid_black = Solid(self.black) self.solid_white = Solid(self.white) def tearDown(self): pass def test_stripe(self): pattern = Stripe(self.solid_white, self.solid_black) for a, b in [(pattern.a, self.solid_white), (pattern.b, self.solid_black)]: self.assertEqual(a, b) def test_constant_y(self): pattern = Stripe(self.solid_white, self.solid_black) for point in [Point(0, 0, 0), Point(0, 1, 0), Point(0, 2, 0)]: self.assertEqual(pattern.at(point), self.white) def test_constant_z(self): pattern = Stripe(self.solid_white, self.solid_black) for point in [Point(0, 0, 0), Point(0, 0, 1), Point(0, 0, 2)]: self.assertEqual(pattern.at(point), self.white) def test_alternates_x(self): pattern = Stripe(self.solid_white, self.solid_black) for xyz, color in [((0, 0, 0), self.white), ((0.9, 0, 0), self.white), ((1, 0, 0), self.black), ((-0.1, 0, 0), self.black), ((-1, 0, 0), self.black), ((-1.1, 0, 0), self.white)]: self.assertEqual(pattern.at(Point(*xyz)), color) def test_with_obj_transformation(self): obj = Sphere(transform=scaling(2, 2, 2)) pattern = Stripe(self.solid_white, self.solid_black) self.assertEqual(pattern.at_shape(obj, Point(1.5, 0, 0)), self.white) def test_with_pattern_transformation(self): obj = Sphere() pattern = Stripe(self.solid_white, self.solid_black, transform=scaling(2, 2, 2)) self.assertEqual(pattern.at_shape(obj, Point(1.5, 0, 0)), self.white) def test_with_both_obj_pattern_transformation(self): obj = Sphere(scaling(2, 2, 2)) pattern = Stripe(self.solid_white, self.solid_black, translation(0.5, 0, 0)) self.assertEqual(pattern.at_shape(obj, Point(2.5, 0, 0)), self.white) class GradientTest(TestCase): def setUp(self): pass def tearDown(self): pass def test_linearly_interpolates_between_colors(self): pattern = Gradient(Solid(Color(1, 1, 1)), Solid(Color(0, 0, 0))) for point_xyz, color_xyz in [((0, 0, 0), (1, 1, 1)), ((0.25, 0, 0), (0.75, 0.75, 0.75)), ((0.5, 0, 0), (0.5, 0.5, 0.5)), ((0.75, 0, 0), (0.25, 0.25, 0.25))]: self.assertEqual(pattern.at(Point(*point_xyz)), Color(*color_xyz)) class RingTest(TestCase): def setUp(self): pass def tearDown(self): pass def test(self): ring = Ring(Solid(Color(1, 1, 1)), Solid(Color(0, 0, 0))) for point, color in [((0, 0, 0), (1, 1, 1)), ((1, 0, 0), (0, 0, 0)), ((0, 0, 1), (0, 0, 0)), ((0.708, 0, 0.708), (0, 0, 0))]: self.assertEqual(ring.at(Point(*point)), Color(*color)) class CheckersTest(TestCase): def setUp(self): self.white = Color(1, 1, 1) self.black = Color(0, 0, 0) self.solid_white = Solid(self.white) self.solid_black = Solid(self.black) def tearDown(self): pass def test_repeate_x(self): checkers = Checkers(self.solid_white, self.solid_black) for point, color in [((0, 0, 0), self.white), ((0.99, 0, 0), self.white), ((1.01, 0, 0), self.black)]: self.assertEqual(checkers.at(Point(*point)), color) def test_repeate_y(self): checkers = Checkers(self.solid_white, self.solid_black) for point, color in [((0, 0, 0), self.white), ((0, 0.99, 0), self.white), ((0, 1.01, 0), self.black)]: self.assertEqual(checkers.at(Point(*point)), color) def test_repeate_z(self): checkers = Checkers(self.solid_white, self.solid_black) for point, color in [((0, 0, 0), self.white), ((0, 0, 0.99), self.white), ((0, 0, 1.01), self.black)]: self.assertEqual(checkers.at(Point(*point)), color) if __name__ == '__main__': main()
patterns.py
import math from matrices import IDENTITY_MATRIX from tuples import is_equal, Color class Pattern: def __init__(self, transform=IDENTITY_MATRIX): self.transform = transform def at(self, point): return Color(point.x, point.y, point.z) def at_shape(self, shape, world_point): obj_point = shape.transform.inverse() * world_point point = self.transform.inverse() * obj_point return self.at(point) class Solid(Pattern): def __init__(self, color): super().__init__() self.color = color def at(self, point): return self.color class Stripe(Pattern): def __init__(self, pattern_a, pattern_b, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = pattern_a self.b = pattern_b def at(self, point): point = self.transform.inverse() * point if math.floor(point.x) % 2 == 0: return self.a.at(point) return self.b.at(point) class Gradient(Pattern): def __init__(self, pattern_a, pattern_b, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = pattern_a self.b = pattern_b def at(self, point): point = self.transform.inverse() * point distance = self.b.at(point) - self.a.at(point) fraction = point.x - math.floor(point.x) return self.a.at(point) + distance * fraction class Ring(Pattern): def __init__(self, pattern_a, pattern_b, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = pattern_a self.b = pattern_b def at(self, point): point = self.transform.inverse() * point if is_equal(math.floor(math.sqrt(point.x ** 2 + point.z ** 2)) % 2, 0): return self.a.at(point) return self.b.at(point) class Checkers(Pattern): def __init__(self, pattern_a, pattern_b, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = pattern_a self.b = pattern_b def at(self, point): point = self.transform.inverse() * point if is_equal(sum([math.floor(p) for p in [point.x, point.y, point.z]]) % 2, 0): return self.a.at(point) return self.b.at(point) class RadialGradient(Pattern): def __init__(self, pattern_a, pattern_b, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = pattern_a self.b = pattern_b def at(self, point): color_distance = self.b - self.a distance = math.sqrt(point.x ** 2 + point.z ** 2) fraction = distance - math.floor(distance) return self.a + color_distance * fraction class Blend(Pattern): def __init__(self, pattern_a, pattern_b, transform=IDENTITY_MATRIX): super().__init__(transform) self.a = pattern_a self.b = pattern_b def at(self, point): point = self.transform.inverse() * point color_a = self.a.at(point) color_b = self.b.at(point) return (color_a + color_b) / 2
sample3.py
#!/usr/bin/env python3 import math import time from tuples import Point, Vector, Color from planes import Plane from materials import Material from patterns import Solid, Stripe, Blend from camera import Camera from lights import Light from world import World from transformations import view_transform, rotation_y print('ファイル名, rendering time(秒)') width = 500 height = 250 stripe1 = Stripe(Solid(Color(1, 0, 0)), Solid(Color(1, 1, 1)), transform=rotation_y(math.pi / 4)) stripe2 = Stripe(Solid(Color(1, 0, 0)), Solid(Color(1, 1, 1)), transform=rotation_y(-math.pi / 4)) blend = Blend(stripe1, stripe2) material = Material(specular=0, pattern=blend) plane = Plane(material=material) camera = Camera(width, height, math.pi / 2, transform=view_transform(Point(0, 1.5, -5), Point(0, 1, 0), Vector(0, 1, 0))) world = World([plane], Light(Point(-10, 10, -10), Color(1, 1, 1))) start = time.time() canvas = camera.render(world) s = time.time() - start with open(f'sample5.ppm', 'w') as f: canvas.to_ppm(f) print(f'sample5.ppm,{s}')
入出力結果(Bash、cmd(コマンドプロンプト)、Terminal、Jupyter(IPython))
C:\Users\...>py materials_test.py ........ ---------------------------------------------------------------------- Ran 8 tests in 0.003s OK C:\Users\...>py patterns_test.py ................ ---------------------------------------------------------------------- Ran 16 tests in 0.007s OK C:\Users\...>py sample3.py ファイル名, rendering time(秒) sample5.ppm,519.0295162200928 C:\Users\...>
0 コメント:
コメントを投稿