2019年5月29日水曜日

開発環境

The Ray Tracer Challenge: A Test-Driven Guide to Your First 3D Renderer (Jamis Buck(著)、Pragmatic Bookshelf)、Chapter 13(Cylinders)のIntersecting a Ray with a Cylinder、Truncating Cylindersを取り組んでみる。

コード

cylinders_test.py

#!/usr/bin/env python3
from unittest import TestCase, main
from cylinders import Cylinder
from tuples import is_equal, Point, Vector
from rays import Ray


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

    def tearDown(self):
        pass

    def test_ray_misses(self):
        cylinder = Cylinder()
        origins = [(1, 0, 0),
                   (0, 0, 0),
                   (0, 0, -5)]
        directions = [(0, 1, 0),
                      (0, 1, 0),
                      (1, 1, 1)]
        for origin, direction in zip(origins, directions):
            origin = Point(*origin)
            direction = Vector(*direction).normalize()
            ray = Ray(origin, direction)
            intersections = cylinder.intersect(ray)
            self.assertEqual(len(intersections), 0)

    def test_ray_strikes(self):
        cylinder = Cylinder()
        origins = [(1, 0, -5),
                   (0, 0, -5),
                   (0.5, 0, -5)]
        directions = [(0, 0, 1),
                      (0, 0, 1),
                      (0.1, 1, 1)]
        ts0 = [5, 4, 6.80798]
        ts1 = [5, 6, 7.08872]
        for origin, direction, t0, t1 in zip(origins, directions, ts0, ts1):
            origin = Point(*origin)
            direction = Vector(*direction).normalize()
            ray = Ray(origin, direction)
            intersections = cylinder.intersect(ray)
            self.assertEqual(len(intersections), 2)
            self.assertTrue(is_equal(intersections[0].t, t0))
            self.assertTrue(is_equal(intersections[1].t, t1))

    def test_nomral_vector(self):
        cylinder = Cylinder()
        points = [(1, 0, 0),
                  (0, 5, -1),
                  (0, -2, 1),
                  (-1, 1, 0)]
        normals = [(1, 0, 0),
                   (0, 0, -1),
                   (0, 0, 1),
                   (-1, 0, 0)]
        for point, normal in zip(points, normals):
            point = Point(*point)
            normal = Vector(*normal)
            n = cylinder.normal_at(point)
            self.assertEqual(n, normal)

    def test_intersecting_constrained(self):
        cylinder = Cylinder(minimum=1, maximum=2)
        points = [(0, 1.5, 0),
                  (0, 3, -5),
                  (0, 0, -5),
                  (0, 2, -5),
                  (0, 1, -5),
                  (0, 1.5, -2)]
        directions = [(0.1, 1, 0),
                      (0, 0, 1),
                      (0, 0, 1),
                      (0, 0, 1),
                      (0, 0, 1),
                      (0, 0, 1)]
        counts = [0, 0, 0, 0, 0, 2]
        for direction, point, count in zip(directions, points, counts):
            direction = Vector(*direction)
            direction = direction.normalize()
            point = Point(*point)
            ray = Ray(point, direction)
            intersections = cylinder.intersect(ray)
            self.assertEqual(len(intersections), count)


if __name__ == '__main__':
    main()

cylinders.py

import math
from shapes import Shape
from intersections import Intersection, Intersections
from tuples import is_equal, Vector


class Cylinder(Shape):
    def __init__(self, transform=None, material=None,
                 minimum=-math.inf, maximum=math.inf):
        super().__init__(transform=transform, material=material)
        self.minimum = minimum
        self.maximum = maximum

    def intersect(self, ray):
        ray = ray.transform(self.transform.inverse())
        a = ray.direction.x ** 2 + ray.direction.z ** 2
        if is_equal(a, 0):
            return Intersections()
        b = 2 * ray.origin.x * ray.direction.x + \
            2 * ray.origin.z * ray.direction.z
        c = ray.origin.x ** 2 + ray.origin.z ** 2 - 1
        disc = b ** 2 - 4 * a * c
        if disc < 0:
            return Intersections()
        t0 = (-b - math.sqrt(disc)) / (2 * a)
        t1 = (-b + math.sqrt(disc)) / (2 * a)
        intersections = [Intersection(t, self) for t in [t0, t1]
                         if self.minimum <
                         ray.origin.y + t * ray.direction.y <
                         self.maximum]
        return Intersections(*intersections)

    def normal_at(self, point):
        point = self.transform.inverse() * point
        return Vector(point.x, 0, point.z)

sample2.py

#!/usr/bin/env python3
import math
import time
from tuples import Point, Vector, Color
from planes import Plane
from cylinders import Cylinder
from materials import Material
from camera import Camera
from lights import Light
from world import World
from transformations import translation, view_transform
from transformations import rotation_x, rotation_y, rotation_z
print('ファイル名, rendering time(秒)')

width = 250
height = 125

wall1 = Plane(material=Material(color=Color(0, 0, 1)),
              transform=translation(0, 0, 7) *
              rotation_y(-math.pi / 4) *
              rotation_x(math.pi / 2))
wall2 = Plane(material=Material(color=Color(1, 0, 0)),
              transform=translation(0, 0, 7) *
              rotation_y(math.pi / 4) *
              rotation_x(math.pi / 2))

floor = Plane(material=Material(Color(0, 1, 0)),
              transform=translation(0, -1, 0))

cylinder = Cylinder(minimum=0, maximum=2,
                    material=Material(color=Color(1, 1, 0)))
cylinder1 = Cylinder(minimum=0, maximum=2,
                     transform=rotation_x(math.pi / 2),
                     material=Material(color=Color(1, 1, 0)))
cylinder2 = Cylinder(minimum=0, maximum=2,
                     transform=rotation_z(math.pi / 2),
                     material=Material(color=Color(1, 1, 0)))
cylinders = [cylinder, cylinder1, cylinder2]
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([floor, wall1, wall2],
              Light(Point(-10, 10, -10), Color(1, 1, 1)))
for i, cyl in enumerate(cylinders, 4):
    world.objs.append(cyl)
    start = time.time()
    canvas = camera.render(world)
    s = time.time() - start
    with open(f'sample{i}.ppm', 'w') as f:
        canvas.to_ppm(f)
    print(f'sample{i}.ppm,{s}')
    world.objs.remove(cyl)

入出力結果(Bash、cmd.exe(コマンドプロンプト)、Terminal、Jupyter(IPython))

C:\Users\...>py cyliners_test.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.010s

OK

C:\Users\...>py sample2.py
ファイル名, rendering time(秒)
sample4.ppm,206.15631413459778
sample5.ppm,199.89930176734924
sample6.ppm,200.73605513572693

C:\Users\...>

0 コメント:

コメントを投稿