2019年5月31日金曜日

開発環境

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、Conesを取り組んでみる。

コード

cones_test.py

#!/usr/bin/env python3
import math
from unittest import TestCase, main
from cones import Cone
from tuples import is_equal, Point, Vector
from rays import Ray


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

    def tearDown(self):
        pass

    def test_itersecting(self):
        cone = Cone()
        origins = [(0, 0, -5),
                   (0, 0, - 5),
                   (1, 1, -5)]
        directions = [(0, 0, 1),
                      (1, 1, 1),
                      (-0.5, -1, 1)]
        ts0 = [5, 8.66025, 4.55006]
        ts1 = [5, 8.66025, 49.44994]
        for direction, origin, t0, t1 in zip(directions, origins, ts0, ts1):
            direction = Vector(*direction)
            direction = direction.normalize()
            origin = Point(*origin)
            ray = Ray(origin, direction)
            intersections = cone.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_intersecting_ray_parallel_to_one_of_its_havles(self):
        cone = Cone()
        direction = Vector(0, 1, 1).normalize()
        ray = Ray(Point(0, 0, -1), direction)
        intersections = cone.intersect(ray)
        self.assertEqual(len(intersections), 1)
        self.assertTrue(is_equal(intersections[0].t, 0.35355))

    def test_intersecting_end_caps(self):
        cone = Cone(minimum=-0.5, maximum=0.5, is_closed=True)
        origins = [(0, 0, -5),
                   (0, 0, -0.25),
                   (0, 0, -0.25)]
        directions = [(0, 1, 0),
                      (0, 1, 1),
                      (0, 1, 0)]
        counts = [0, 2, 4]
        for direction, origin, count in zip(directions, origins, counts):
            direction = Vector(*direction).normalize()
            origin = Point(*origin)
            ray = Ray(origin, direction)
            intersections = cone.intersect(ray)
            self.assertEqual(len(intersections), count)

    def test_normal_vector(self):
        cone = Cone()
        points = [(0, 0, 0),
                  (1, 1, 1),
                  (-1, -1, 0)]
        normals = [(0, 0, 0),
                   (1, -math.sqrt(2), 1),
                   (-1, 1, 0)]
        for point,  normal in zip(points, normals):
            n = cone.normal_at(Point(*point))
            self.assertEqual(n, Vector(*normal))


if __name__ == '__main__':
    main()

cones.py

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


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

    def intersect(self, ray):
        intersections = []
        ray = ray.transform(self.transform.inverse())
        if self.is_closed and not is_equal(ray.direction.y, 0):
            ts = [(self.minimum - ray.origin.y) / ray.direction.y,
                  (self.maximum - ray.origin.y) / ray.direction.y]
            intersections = [Intersection(t, self) for t in ts
                             if check_cap(ray, t)]
        a = ray.direction.x ** 2 - ray.direction.y ** 2 + ray.direction.z ** 2
        b = 2 * ray.origin.x * ray.direction.x - \
            2 * ray.origin.y * ray.direction.y + \
            2 * ray.origin.z * ray.direction.z
        c = ray.origin.x ** 2 - ray.origin.y ** 2 + ray.origin.z ** 2
        if is_equal(a, 0):
            intersections += [Intersection(-c / (2 * b), self)]
            return Intersections(*intersections)
        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
        dist = point.x ** 2 + point.z ** 2
        if dist < point.y ** 2 and point.y >= self.maximum - EPSILON:
            return Vector(0, 1, 0)
        if dist < point.y ** 2 and point.y <= self.minimum + EPSILON:
            return Vector(0, -1, 0)
        y = math.sqrt(point.x ** 2 + point.z ** 2)
        if point.y > 0:
            y = -y
        return Vector(point.x, y, point.z)


def check_cap(ray, t):
    x = ray.origin.x + t * ray.direction.x
    z = ray.origin.z + t * ray.direction.z
    y = ray.origin.y + t * ray.direction.y
    return (x ** 2 + z ** 2) <= y ** 2

sample4.py

#!/usr/bin/env python3
import math
import time
from tuples import Point, Vector, Color
from planes import Plane
from cones import Cone
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))

yellow = Color(1, 1, 0)
cone1 = Cone(material=Material(color=yellow),
             minimum=-1,
             maximum=2)
cone2 = Cone(material=Material(color=yellow),
             transform=rotation_x(math.pi / 2),
             minimum=-1,
             maximum=2)
cone3 = Cone(material=Material(color=yellow),
             transform=rotation_x(math.pi / 2),
             minimum=-1,
             maximum=2,
             is_closed=True)
cones = [cone1, cone2, cone3]
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, cone in enumerate(cones, 8):
    world.objs.append(cone)
    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(cone)

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

C:\Users\...>py cones_test.py
....
----------------------------------------------------------------------
Ran 4 tests in 0.006s

OK

C:\Users\...>py sample4.py
ファイル名, rendering time(秒)
sample8.ppm,208.59996008872986
sample9.ppm,205.59838008880615
sample10.ppm,202.44608807563782

C:\Users\...>

0 コメント:

コメントを投稿