## 2019年6月6日木曜日

### Python - Groups - Finding the Normal on a Child Object(Convert a Normal Vector from Object Space to World Space)

The Ray Tracer Challenge: A Test-Driven Guide to Your First 3D Renderer (Jamis Buck(著)、Pragmatic Bookshelf)、Chapter 14(Groups)のFinding the Normal on a Child Object、Test #8(Convert a Normal Vector from Object Space to World Space)を取り組んでみる。

コード

shapes_test.py

```#!/usr/bin/env python3
import math
from unittest import TestCase, main
from shapes import Shape
from transformations import translation
from matrices import IDENTITY_MATRIX, Matrix
from materials import Material
from rays import Ray
from tuples import Point, Vector
from transformations import scaling, rotation_y
from groups import Group
from spheres import Sphere

class ShapeTest(TestCase):
def setUp(self):
self.shape = Shape(material=Material())

def tearDown(self):
pass

def test_default_transformation(self):
self.assertEqual(self.shape.transform, IDENTITY_MATRIX)

def test_material(self):
self.assertEqual(self.shape.material.ambient, 0.1)
self.assertEqual(self.shape.material, Material())

def test_transform(self):
self.assertEqual(Shape().transform,
IDENTITY_MATRIX)
s = Shape()
t = translation(2, 3, 4)
s.transform = t
self.assertEqual(s.transform, t)

def test_parent_attribute(self):
s = Shape()
self.assertIsNone(s.parent)

def test_converting_point_from_world_to_object_space(self):
group1 = Group(transform=rotation_y(math.pi / 2))
group2 = Group(transform=scaling(2, 2, 2))
group1.add_child(group2)
sphere = Sphere(translation(5, 0, 0))
group2.add_child(sphere)
point = sphere.world_to_obj(Point(-2, 0, -10))
self.assertEqual(point, Point(0, 0, -1))

def test_converting_normal_from_obj_to_world_space(self):
group1 = Group(transform=rotation_y(math.pi / 2))
group2 = Group(transform=scaling(1, 2, 3))
group1.add_child(group2)
sphere = Sphere(translation(5, 0, 0))
group2.add_child(sphere)
normal = sphere.normal_to_world(
Vector(-1 / math.sqrt(3), 1 / math.sqrt(3), 1 / math.sqrt(3)))
self.assertEqual(normal, Vector(0.28571, 0.42857, 0.85714))

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

shapes.py

```from matrices import Matrix, IDENTITY_MATRIX
from materials import Material

class Shape:
def __init__(self, transform=None, material=None, parent=None):
if transform is None:
self.transform = IDENTITY_MATRIX
else:
self.transform = transform
if material is None:
self.material = Material()
else:
self.material = material
self.parent = None

def __repr__(self):
return f'{self.__class__.__name__}({self.transform},{self.material})'

def intersect(self, ray):
raise NotImplementedError()

def normal_at(self, point):
raise NotImplementedError()

def world_to_obj(self, point):
if self.parent is not None:
point = self.parent.world_to_obj(point)
return self.transform.inverse() * point

def normal_to_world(self, normal):
normal = self.transform.inverse().transpose() * normal
normal.w = 0
normal = normal.normalize()
if self.parent is not None:
normal = self.parent.normal_to_world(normal)
return normal
```

```C:\Users\...>py shapes_test.py
......
----------------------------------------------------------------------
Ran 6 tests in 0.004s

OK

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