## 2019年5月4日土曜日

### Python - Tuples, Points, and Vectors - Operations - Scalar Multiplication and Division、Magnitude、Normalization、Dot Product、Cross Product

The Ray Tracer Challenge: A Test-Driven Guide to Your First 3D Renderer (Jamis Buck(著)、Pragmatic Bookshelf)、Chapter 1(Tuples, Points, and Vectors)のOperations、Scalar Multiplication and Division、Magnitude、Normalization、Dot Product、Cross Productを取り組んでみる。

コード

Python 3

tuples_test.py

```#!/usr/bin/env python3
from unittest import TestCase, main
from tuples import Tuple, Point, Vector
import math

class TupleTest(TestCase):
def setUp(self):
pass

def tearDown(self):
pass

def test_is_point(self):
a = Point(4.3, -4.2, 3.1)
self.assertEqual(a.x, 4.3, )
self.assertEqual(a.y, -4.2)
self.assertEqual(a.z, 3.1)
self.assertEqual(a.w, 1.0)
self.assertEqual(type(a), Point)
self.assertNotEqual(type(a), Vector)

a1 = Tuple(3, -2, 5, 1)
a2 = Tuple(-2, 3, 1, 0)
self.assertEqual(a1 + a2, Tuple(1, 1, 6, 1))

def test_sub(self):
p1 = Point(3, 2, 1)
p2 = Point(5, 6, 7)
self.assertEqual(p1 - p2, Vector(-2, -4, -6))
self.assertEqual(type(p1 - p2), Vector)

def test_sub_vector_from_point(self):
p = Point(3, 2, 1)
v = Vector(5, 6, 7)
self.assertEqual(p - v, Point(-2, -4, -6))

def test_sub_vector(self):
v1 = Vector(3, 2, 1)
v2 = Vector(5, 6, 7)
self.assertEqual(v1 - v2, Vector(-2, -4, -6))

def test_sub_vect_from_zero_vect(self):
zero = Vector(0, 0, 0)
v = Vector(1, -2, 3)
self.assertEqual(zero - v, Vector(-1, 2, -3))

def test_neg(self):
a = Tuple(1, -2, 3, -4)
self.assertEqual(-a, Tuple(-1, 2, -3, 4))

def test_scalar_mul(self):
a = Tuple(1, -2, 3, -4)
self.assertEqual(a * 3.5, Tuple(3.5, -7, 10.5, -14))
self.assertEqual(a * 0.5, Tuple(0.5, -1, 1.5, -2))

def test_div(self):
a = Tuple(1, -2, 3, -4)
self.assertEqual(a / 2, Tuple(0.5, -1, 1.5, -2))

def test_mag_vector(self):
vectors = [Vector(1, 0, 0),
Vector(0, 1, 0),
Vector(0, 0, 1),
Vector(1, 2, 3),
Vector(-1, -2, -3)]
mags = [1, 1, 1, math.sqrt(14), math.sqrt(14)]
for vector, mag in zip(vectors, mags):
self.assertEqual(vector.magnitude(), mag)

def test_normalizing_vector(self):
v = Vector(4, 0, 0)
self.assertEqual(v.normalize(), Vector(1, 0, 0))
v = Vector(1, 2, 3)
self.assertEqual(v.normalize(),
Vector(1 / math.sqrt(14),
2 / math.sqrt(14),
3 / math.sqrt(14)))
norm = v.normalize()
self.assertEqual(norm.magnitude(), 1)

def test_dot_product(self):
a = Vector(1, 2, 3)
b = Vector(2, 3, 4)
self.assertEqual(a.dot(b), 20)

def test_cross_product(self):
a = Vector(1, 2, 3)
b = Vector(2, 3, 4)
self.assertEqual(a.cross(b), Vector(-1, 2, - 1))
self.assertEqual(b.cross(a), Vector(1, -2, 1))

if __name__ == '__main__':
main()
```

tuples.py

```#!/usr/bin/env python3
import math

EPSILON = 0.00001

def is_equal(a: float, b: float):
return abs(a - b) < EPSILON

class Tuple:
def __init__(self, x: float, y: float, z: float, w: float):
self.x = x
self.y = y
self.z = z
self.w = w

def __eq__(self, other):
return is_equal(self.x, other.x) and is_equal(self.y, other.y) and \
is_equal(self.z, other.z) and is_equal(self.w, other.w)

return self.__class__(self.x + other.x, self.y + other.y,
self.z + other.z, self.w + other.w)

def __sub__(self, other):
return self.__class__(self.x - other.x, self.y - other.y,
self.z - other.z, self.w - other.w)

def __neg__(self):
return self.__class__(-self.x, -self.y, -self.z, -self.w)

def __mul__(self, other):
return self.__class__(self.x * other, self.y * other, self.z * other,
self.w * other)

def __truediv__(self, other):
return self * (1 / other)

def magnitude(self):
return math.sqrt(self.x ** 2 + self.y ** 2 + self.z ** 2 + self.w ** 2)

def normalize(self):
mag = self.magnitude()
return self.__class__(self.x, self.y, self.z, self.w) / mag

def dot(self, other):
return sum([a * b for a, b in zip([self.x, self.y, self.z],
[other.x, other.y, other.z])])

class Point(Tuple):
def __init__(self, x: float, y: float, z: float, w: float = 1):
super().__init__(x, y, z, w)

def __sub__(self, other):
t = super().__sub__(other)
if type(other) == Point:
return Vector(t.x, t.y, t.z)
elif type(other) == Vector:
return Point(t.x, t.y, t.z)
raise TypeError(
"unsupported operand type(s) for -: "
f"'{type(self)}' and '{type(other)}'")

class Vector(Tuple):
def __init__(self, x: float, y: float, z: float, w: float = 0):
super().__init__(x, y, z, w)

def __sub__(self, other):
t = super().__sub__(other)
return Vector(t.x, t.y, t.z)

def cross(self, other):
return Vector(self.y * other.z - self.z * other.y,
self.z * other.x - self.x * other.z,
self.x * other.y - self.y * other.x)
```

```C:\Users\...>py tuples_test.py
.............
----------------------------------------------------------------------
Ran 13 tests in 0.001s

OK

C:\Users\...>py tuples_test.py -v
test_cross_product (__main__.TupleTest) ... ok
test_div (__main__.TupleTest) ... ok
test_dot_product (__main__.TupleTest) ... ok
test_is_point (__main__.TupleTest) ... ok
test_mag_vector (__main__.TupleTest) ... ok
test_neg (__main__.TupleTest) ... ok
test_normalizing_vector (__main__.TupleTest) ... ok
test_scalar_mul (__main__.TupleTest) ... ok
test_sub (__main__.TupleTest) ... ok
test_sub_vect_from_zero_vect (__main__.TupleTest) ... ok
test_sub_vector (__main__.TupleTest) ... ok
test_sub_vector_from_point (__main__.TupleTest) ... ok

----------------------------------------------------------------------
Ran 13 tests in 0.001s

OK

C:\Users\...>
```