File: light_raytracer.py

package info (click to toggle)
python-moderngl 5.12.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 4,700 kB
  • sloc: python: 15,758; cpp: 14,665; makefile: 14
file content (87 lines) | stat: -rw-r--r-- 3,233 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
# WebGL Water
# https://madebyevan.com/webgl-water/
# Copyright 2011 Evan Wallace
# Released under the MIT license

from typing import Self
import glm
import math
from light_gl import Matrices


class HitTest:
    def __init__(self, t: float, hit: glm.vec3, normal: glm.vec3) -> None:
        self.t = t
        self.hit = hit
        self.normal = normal

    def merge_with(self, other: Self):
        if other.t > 0 and other.t < self.t:
            self.t = other.t
            self.hit = other.hit
            self.normal = other.normal

    def __repr__(self) -> str:
        return f"t: {self.t} hit: {self.hit} norm: {self.normal}"


class RayTracer:
    def __init__(self, viewport: tuple[float], matrices: Matrices) -> None:
        model_view_matrix = matrices.model_view
        self.eye = matrices.eye
        self.viewport = viewport

        min_x = viewport[0]
        max_x = min_x + viewport[2]
        min_y = viewport[1]
        max_y = min_y + viewport[3]

        self.ray00 = glm.unProject(
            (min_x, min_y, 1), model_view_matrix, matrices.projection, viewport) - self.eye
        self.ray10 = glm.unProject(
            (max_x, min_y, 1), model_view_matrix, matrices.projection, viewport) - self.eye
        self.ray01 = glm.unProject(
            (min_x, max_y, 1), model_view_matrix, matrices.projection, viewport) - self.eye
        self.ray11 = glm.unProject(
            (max_x, max_y, 1), model_view_matrix, matrices.projection, viewport) - self.eye

    def get_ray_for_pixel(self, x, y) -> glm.vec3:
        x = (x - self.viewport[0]) / self.viewport[2]
        y = 1.0 - (y - self.viewport[1]) / self.viewport[3]
        ray0 = glm.lerp(self.ray00, self.ray10, x)
        ray1 = glm.lerp(self.ray01, self.ray11, x)
        return glm.normalize(glm.lerp(ray0, ray1, y))

    def hit_test_sphere(origin: glm.vec3, ray: glm.vec3, center: glm.vec3, radius: float) -> HitTest:
        offset = origin - center
        a = glm.dot(ray, ray)
        b = 2 * glm.dot(ray, offset)
        c = glm.dot(offset, offset) - radius * radius
        discriminant = b * b - 4 * a * c
        if discriminant > 0:
            t = (-b - math.sqrt(discriminant)) / (2*a)
            hit = origin + ray * t
            return HitTest(t, hit, (hit - center) / radius)
        return None

    def hit_test_triangle(origin: glm.vec3, ray: glm.vec3, vert: tuple[glm.vec3]):
        ab = vert[1] - vert[0]
        ac = vert[2] - vert[0]
        normal = glm.normalize(glm.cross(ab, ac))
        t = glm.dot(normal, vert[0] - origin) / glm.dot(normal, ray)
        if t > 0:
            hit = origin + (ray * t)
            to_hit = hit - vert[0]
            # uv = glm.inverse((glm.mat3(ac, ab, normal))) * to_hit
            dot00 = glm.dot(ac, ac)
            dot01 = glm.dot(ab, ac)
            dot11 = glm.dot(ab, ab)
            dot02 = glm.dot(ac, to_hit)
            dot12 = glm.dot(ab, to_hit)
            divide = dot00 * dot11 - dot01 * dot01
            u = (dot11 * dot02 - dot01 * dot12) / divide
            v = (dot00 * dot12 - dot01 * dot02) / divide
            print("uv", u, v)
            if u >= 0 and v >= 0 and (u + v) <= 1:
                return HitTest(t, hit, normal)
        return None