開発環境
- macOS Mojave - Apple (OS)
- Emacs (Text Editor)
- Windows 10 Pro (OS)
- Visual Studio Code (Text Editor)
- Python 3.7 (プログラミング言語)
- GIMP (ビットマップ画像編集・加工ソフトウェア、PPM形式(portable pixmap)の画像用)
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 コメント:
コメントを投稿