## 2019年5月19日日曜日

### Python - Patterns - Blended patterns, Stripes

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)

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)

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)

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}')
```

```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\...>
```