diff --git a/Programming/TRAK/.gitignore b/Programming/TRAK/.gitignore
new file mode 100644
index 00000000..126fa691
--- /dev/null
+++ b/Programming/TRAK/.gitignore
@@ -0,0 +1,166 @@
+.vscode/
+
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# poetry
+# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
+# This is especially recommended for binary packages to ensure reproducibility, and is more
+# commonly ignored for libraries.
+# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
+#poetry.lock
+
+# pdm
+# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
+#pdm.lock
+# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
+# in version control.
+# https://pdm.fming.dev/latest/usage/project/#working-with-version-control
+.pdm.toml
+.pdm-python
+.pdm-build/
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+.env
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+
+# PyCharm
+# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
+# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
+# and can be added to the global gitignore or merged into this file. For a more nuclear
+# option (not recommended) you can uncomment the following to ignore the entire idea folder.
+#.idea/
+
+*.exr
\ No newline at end of file
diff --git a/Programming/TRAK/.idea/.gitignore b/Programming/TRAK/.idea/.gitignore
new file mode 100644
index 00000000..13566b81
--- /dev/null
+++ b/Programming/TRAK/.idea/.gitignore
@@ -0,0 +1,8 @@
+# Default ignored files
+/shelf/
+/workspace.xml
+# Editor-based HTTP Client requests
+/httpRequests/
+# Datasource local storage ignored files
+/dataSources/
+/dataSources.local.xml
diff --git a/Programming/TRAK/.idea/TRAK.iml b/Programming/TRAK/.idea/TRAK.iml
new file mode 100644
index 00000000..8dedcfa7
--- /dev/null
+++ b/Programming/TRAK/.idea/TRAK.iml
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Programming/TRAK/.idea/inspectionProfiles/profiles_settings.xml b/Programming/TRAK/.idea/inspectionProfiles/profiles_settings.xml
new file mode 100644
index 00000000..105ce2da
--- /dev/null
+++ b/Programming/TRAK/.idea/inspectionProfiles/profiles_settings.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Programming/TRAK/.idea/misc.xml b/Programming/TRAK/.idea/misc.xml
new file mode 100644
index 00000000..8f7d572f
--- /dev/null
+++ b/Programming/TRAK/.idea/misc.xml
@@ -0,0 +1,7 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Programming/TRAK/.idea/modules.xml b/Programming/TRAK/.idea/modules.xml
new file mode 100644
index 00000000..2c5ef4e6
--- /dev/null
+++ b/Programming/TRAK/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Programming/TRAK/.idea/vcs.xml b/Programming/TRAK/.idea/vcs.xml
new file mode 100644
index 00000000..35eb1ddf
--- /dev/null
+++ b/Programming/TRAK/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Programming/TRAK/.python-version b/Programming/TRAK/.python-version
new file mode 100644
index 00000000..2d4715b6
--- /dev/null
+++ b/Programming/TRAK/.python-version
@@ -0,0 +1 @@
+3.11.11
diff --git a/Programming/TRAK/.vscode/extensions.json b/Programming/TRAK/.vscode/extensions.json
new file mode 100644
index 00000000..88c93d43
--- /dev/null
+++ b/Programming/TRAK/.vscode/extensions.json
@@ -0,0 +1,7 @@
+{
+ "recommendations": [
+ "ms-python.black-formatter",
+ "ms-python.autopep8",
+ "ms-python.pylint"
+ ]
+}
\ No newline at end of file
diff --git a/Programming/TRAK/README.md b/Programming/TRAK/README.md
new file mode 100644
index 00000000..0552da05
--- /dev/null
+++ b/Programming/TRAK/README.md
@@ -0,0 +1,23 @@
+## Wywoływanie z terminala
+
+```bash
+# Wywołanie algorytmu ray tracing z domyślnymi parametrami i sceną
+python main.py --algorithm ray_tracing
+```
+
+```bash
+# Wywołanie algorytmu ray tracing ze specyfikacją sceny z folderu scenes, liczbą sampli na pixek, rozdzielczością, środowiskiem i rozmyciem środowiska
+python main.py --scene three_spheres --samples_per_pixel 100 --resolution 100x100 --environment lake.png --env_blur 10
+```
+
+```bash
+# Wywołanie algorytmu *photon mapping* ze specyfikacją liczby fotonów i maksymalnej głębokości
+python main.py --algorithm photon_mapping --max_depth 4 --num_photons 1000
+```
+
+```bash
+# Wywołanie algorytmu *photon mapping* wykonanego w c++
+cd photonmappnig/cpp
+./compile.sh
+./photon_mapping
+```
\ No newline at end of file
diff --git a/Programming/TRAK/code/mapa_srodowiska/README.md b/Programming/TRAK/code/mapa_srodowiska/README.md
new file mode 100644
index 00000000..a0efbc03
--- /dev/null
+++ b/Programming/TRAK/code/mapa_srodowiska/README.md
@@ -0,0 +1,2 @@
+Download file in (!) EXR (!) format
+https://polyhaven.com/a/lilienstein
\ No newline at end of file
diff --git a/Programming/TRAK/code/mapa_srodowiska/lilienstein_1k.exr b/Programming/TRAK/code/mapa_srodowiska/lilienstein_1k.exr
new file mode 100644
index 00000000..ebcdb200
Binary files /dev/null and b/Programming/TRAK/code/mapa_srodowiska/lilienstein_1k.exr differ
diff --git a/Programming/TRAK/code/mapa_srodowiska/main.py b/Programming/TRAK/code/mapa_srodowiska/main.py
new file mode 100644
index 00000000..311581f8
--- /dev/null
+++ b/Programming/TRAK/code/mapa_srodowiska/main.py
@@ -0,0 +1,162 @@
+import OpenEXR
+import Imath
+import os
+import OpenGL.GL as gl
+import OpenGL.GLUT as glut
+import OpenGL.GLU as glu
+import math
+
+# Camera parameters
+camera_angle_x = 0.0
+camera_angle_y = 0.0
+camera_distance = 2.0
+mouse_last_x = 0
+mouse_last_y = 0
+mouse_left_down = False
+
+def load_hdr_environment_map(filepath):
+ """
+ Load an HDR environment map from an OpenEXR file.
+
+ Args:
+ filepath (str): Path to the HDR file.
+
+ Returns:
+ tuple: A tuple containing the width, height, and a bytes object representing the HDR environment map in RGB format.
+ """
+ if not os.path.exists(filepath):
+ raise FileNotFoundError(f"File not found: {filepath}")
+
+ # Check file permissions
+ if not os.access(filepath, os.R_OK):
+ raise PermissionError(f"File is not readable: {filepath}")
+
+ # Open the EXR file
+ try:
+ exr_file = OpenEXR.InputFile(filepath)
+ except Exception as e:
+ raise OSError(f"Unable to open '{filepath}' for read: {str(e)}")
+
+ # Get the image dimensions
+ header = exr_file.header()
+ dw = header['dataWindow']
+ width = dw.max.x - dw.min.x + 1
+ height = dw.max.y - dw.min.y + 1
+
+ # Define the channel names (R, G, B)
+ channels = ['R', 'G', 'B']
+
+ # Read the channel data
+ channel_data = {
+ channel: exr_file.channel(channel, Imath.PixelType(Imath.PixelType.FLOAT))
+ for channel in channels
+ }
+
+ # Combine channel data into a single bytes object
+ hdr_image = bytearray(width * height * 3 * 4) # 3 channels, 4 bytes per float
+ for i, channel in enumerate(channels):
+ channel_buffer = channel_data[channel]
+ for j in range(height):
+ for k in range(width):
+ index = (j * width + k) * 3 * 4 + i * 4
+ hdr_image[index:index + 4] = channel_buffer[(j * width + k) * 4:(j * width + k + 1) * 4]
+
+ # Flip the image vertically
+ flipped_hdr_image = bytearray(width * height * 3 * 4)
+ row_size = width * 3 * 4
+ for j in range(height):
+ src_index = j * row_size
+ dst_index = (height - 1 - j) * row_size
+ flipped_hdr_image[dst_index:dst_index + row_size] = hdr_image[src_index:src_index + row_size]
+
+ return width, height, bytes(flipped_hdr_image)
+
+def display_hdr_image(width, height, hdr_image):
+ gl.glClear(gl.GL_COLOR_BUFFER_BIT | gl.GL_DEPTH_BUFFER_BIT)
+
+ # Enable texture mapping
+ gl.glEnable(gl.GL_TEXTURE_2D)
+ texture_id = gl.glGenTextures(1)
+ gl.glBindTexture(gl.GL_TEXTURE_2D, texture_id)
+
+ # Set texture parameters
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_S, gl.GL_CLAMP_TO_EDGE)
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_WRAP_T, gl.GL_CLAMP_TO_EDGE)
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MIN_FILTER, gl.GL_LINEAR)
+ gl.glTexParameteri(gl.GL_TEXTURE_2D, gl.GL_TEXTURE_MAG_FILTER, gl.GL_LINEAR)
+
+ # Load the texture
+ gl.glTexImage2D(gl.GL_TEXTURE_2D, 0, gl.GL_RGB32F, width, height, 0, gl.GL_RGB, gl.GL_FLOAT, hdr_image)
+
+ # Set up the viewport and projection
+ gl.glViewport(0, 0, glut.glutGet(glut.GLUT_WINDOW_WIDTH), glut.glutGet(glut.GLUT_WINDOW_HEIGHT))
+ gl.glMatrixMode(gl.GL_PROJECTION)
+ gl.glLoadIdentity()
+ glu.gluPerspective(45.0, glut.glutGet(glut.GLUT_WINDOW_WIDTH) / float(glut.glutGet(glut.GLUT_WINDOW_HEIGHT)), 0.1, 100.0)
+ gl.glMatrixMode(gl.GL_MODELVIEW)
+ gl.glLoadIdentity()
+
+ # Apply camera transformations
+ gl.glTranslatef(0.0, 0.0, -camera_distance)
+ gl.glRotatef(camera_angle_y, 1.0, 0.0, 0.0)
+ gl.glRotatef(camera_angle_x, 0.0, 1.0, 0.0)
+
+ # Draw a textured sphere to simulate being inside the HDR environment
+ quadric = glu.gluNewQuadric()
+ glu.gluQuadricTexture(quadric, gl.GL_TRUE)
+ glu.gluSphere(quadric, 50.0, 50, 50)
+ glu.gluDeleteQuadric(quadric)
+
+ # Disable texture mapping
+ gl.glDisable(gl.GL_TEXTURE_2D)
+
+ glut.glutSwapBuffers()
+
+def mouse_motion(x, y):
+ global mouse_last_x, mouse_last_y, camera_angle_x, camera_angle_y
+ if mouse_left_down:
+ dx = x - mouse_last_x
+ dy = y - mouse_last_y
+ camera_angle_x += dx * 0.1
+ camera_angle_y += dy * 0.1
+ mouse_last_x = x
+ mouse_last_y = y
+ glut.glutPostRedisplay()
+
+def mouse_button(button, state, x, y):
+ global mouse_left_down
+ if button == glut.GLUT_LEFT_BUTTON:
+ if state == glut.GLUT_DOWN:
+ mouse_left_down = True
+ mouse_last_x = x
+ mouse_last_y = y
+ elif state == glut.GLUT_UP:
+ mouse_left_down = False
+
+def main(filepath):
+ try:
+ width, height, hdr_map = load_hdr_environment_map(filepath)
+ print("HDR Map Loaded. Dimensions:", width, "x", height)
+
+ # Initialize GLUT and create window
+ glut.glutInit()
+ glut.glutInitDisplayMode(glut.GLUT_DOUBLE | glut.GLUT_RGB | glut.GLUT_DEPTH)
+ glut.glutInitWindowSize(800, 600) # Set initial window size
+ glut.glutCreateWindow(b"HDR Environment Map")
+
+ # Set display callback
+ glut.glutDisplayFunc(lambda: display_hdr_image(width, height, hdr_map))
+
+ # Set mouse callbacks
+ glut.glutMotionFunc(mouse_motion)
+ glut.glutMouseFunc(mouse_button)
+
+ # Start the GLUT main loop
+ glut.glutMainLoop()
+ except Exception as e:
+ print(e)
+
+# Example usage
+if __name__ == "__main__":
+ filepath = "lilienstein_4k.exr"
+ main(filepath)
diff --git a/Programming/TRAK/code/photonmapping/cpp/.gitignore b/Programming/TRAK/code/photonmapping/cpp/.gitignore
new file mode 100644
index 00000000..d6a6547e
--- /dev/null
+++ b/Programming/TRAK/code/photonmapping/cpp/.gitignore
@@ -0,0 +1,3 @@
+photon_mapping
+*.ppm
+*.jpg
\ No newline at end of file
diff --git a/Programming/TRAK/code/photonmapping/cpp/Plane.h b/Programming/TRAK/code/photonmapping/cpp/Plane.h
new file mode 100644
index 00000000..75b789bb
--- /dev/null
+++ b/Programming/TRAK/code/photonmapping/cpp/Plane.h
@@ -0,0 +1,27 @@
+#ifndef PLANE_H
+
+class Plane {
+public:
+ Vector3 point; // A point on the plane
+ Vector3 normal; // Normal to the plane
+ Vector3 color;
+
+ Plane(const Vector3& p, const Vector3& n, const Vector3& col)
+ : point(p), normal(n.normalize()), color(col) {}
+
+ bool intersect(const Vector3& ray_origin, const Vector3& ray_direction, double& t, Vector3& hit_point, Vector3& hit_normal) const {
+ double denom = normal.dot(ray_direction);
+ if (std::abs(denom) > 1e-6) {
+ t = (point - ray_origin).dot(normal) / denom;
+ if (t >= 1e-4) {
+ hit_point = ray_origin + ray_direction * t;
+ hit_normal = normal;
+ return true;
+ }
+ }
+ return false;
+ }
+};
+
+
+#endif
\ No newline at end of file
diff --git a/Programming/TRAK/code/photonmapping/cpp/Sphere.h b/Programming/TRAK/code/photonmapping/cpp/Sphere.h
new file mode 100644
index 00000000..38c2d223
--- /dev/null
+++ b/Programming/TRAK/code/photonmapping/cpp/Sphere.h
@@ -0,0 +1,31 @@
+#ifndef SPHERE_H
+#define SPHERE_H
+
+#include "Vector3.h"
+
+class Sphere {
+public:
+ Vector3 center;
+ double radius;
+ Vector3 color;
+
+ Sphere(const Vector3& c, double r, const Vector3& col)
+ : center(c), radius(r), color(col) {}
+
+ bool intersect(const Vector3& ray_origin, const Vector3& ray_direction, double& t, Vector3& hit_point, Vector3& normal) const {
+ Vector3 oc = ray_origin - center;
+ double a = ray_direction.dot(ray_direction);
+ double b = 2.0 * oc.dot(ray_direction);
+ double c = oc.dot(oc) - radius * radius;
+ double discriminant = b * b - 4 * a * c;
+ if (discriminant > 0) {
+ t = (-b - std::sqrt(discriminant)) / (2.0 * a);
+ hit_point = ray_origin + ray_direction * t;
+ normal = (hit_point - center).normalize();
+ return true;
+ }
+ return false;
+ }
+};
+
+#endif // SPHERE_H
\ No newline at end of file
diff --git a/Programming/TRAK/code/photonmapping/cpp/Vector3.h b/Programming/TRAK/code/photonmapping/cpp/Vector3.h
new file mode 100644
index 00000000..cf19b746
--- /dev/null
+++ b/Programming/TRAK/code/photonmapping/cpp/Vector3.h
@@ -0,0 +1,23 @@
+#ifndef VECTOR3_H
+#define VECTOR3_H
+
+#include
+
+class Vector3 {
+public:
+ double x, y, z;
+
+ Vector3(double x_=0, double y_=0, double z_=0): x(x_), y(y_), z(z_) {}
+
+ Vector3 operator + (const Vector3& v) const { return Vector3(x+v.x, y+v.y, z+v.z); }
+ Vector3 operator - (const Vector3& v) const { return Vector3(x-v.x, y-v.y, z-v.z); }
+ Vector3 operator * (double scalar) const { return Vector3(x*scalar, y*scalar, z*scalar); }
+ Vector3 operator / (double scalar) const { return Vector3(x/scalar, y/scalar, z/scalar); }
+ Vector3 operator - () const { return Vector3(-x, -y, -z); }
+ Vector3 operator * (const Vector3& v) const { return Vector3(x*v.x, y*v.y, z*v.z); }
+ double dot(const Vector3& v) const { return x*v.x + y*v.y + z*v.z; }
+ double norm() const { return std::sqrt(x*x + y*y + z*z); }
+ Vector3 normalize() const { double n = norm(); return Vector3(x/n, y/n, z/n); }
+};
+
+#endif // VECTOR3_H
\ No newline at end of file
diff --git a/Programming/TRAK/code/photonmapping/cpp/compile.sh b/Programming/TRAK/code/photonmapping/cpp/compile.sh
new file mode 100755
index 00000000..19b163a4
--- /dev/null
+++ b/Programming/TRAK/code/photonmapping/cpp/compile.sh
@@ -0,0 +1 @@
+g++ -O2 main.cpp -o photon_mapping
\ No newline at end of file
diff --git a/Programming/TRAK/code/photonmapping/cpp/main.cpp b/Programming/TRAK/code/photonmapping/cpp/main.cpp
new file mode 100644
index 00000000..64c6ad37
--- /dev/null
+++ b/Programming/TRAK/code/photonmapping/cpp/main.cpp
@@ -0,0 +1,296 @@
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "Vector3.h"
+#include "Sphere.h"
+#include "Plane.h"
+
+
+// Define the photon
+struct Photon {
+ Vector3 position;
+ Vector3 direction;
+ Vector3 power;
+
+ Photon(const Vector3& pos, const Vector3& dir, const Vector3& pow)
+ : position(pos), direction(dir), power(pow) {}
+};
+
+// Define a simple plane
+
+// Random number generators
+std::mt19937 rng;
+std::uniform_real_distribution uni_dist(0.0, 1.0);
+
+// Random unit vector in a sphere
+Vector3 random_unit_vector() {
+ double theta = 2 * M_PI * uni_dist(rng);
+ double z = 2 * uni_dist(rng) - 1;
+ double r = std::sqrt(1 - z*z);
+ return Vector3(r * std::cos(theta), r * std::sin(theta), z);
+}
+
+// Random direction in hemisphere around normal
+Vector3 random_hemisphere_direction(const Vector3& normal) {
+ Vector3 dir = random_unit_vector();
+ if (dir.dot(normal) < 0) {
+ dir = dir * -1;
+ }
+ return dir;
+}
+
+// Global variables
+std::vector photon_map;
+std::vector objects; // Pointers to objects
+std::vector object_types; // 0 for Sphere, 1 for Plane
+int ray_number = 0;
+int photon_number = 0;
+
+
+// Scene setup
+Sphere sphere(Vector3(0, 0, -5), 1.0, Vector3(1, 0, 0)); // Red sphere
+Plane plane(Vector3(0, -1, 0), Vector3(0, 1, 0), Vector3(0.5, 0.5, 0.5)); // Gray plane
+// Light source
+Vector3 light_position(-5, 5, -5);
+Vector3 light_power(1000.0, 1000.0, 1000.0); // Intense white light, will scale in code
+
+
+// Functions
+void emit_photons(const int num_photons, const int max_depth);
+void trace_photon(Photon photon, int depth, const int max_depth);
+Vector3 trace_ray(const Vector3& ray_origin, const Vector3& ray_direction, const double gather_radius);
+Vector3 compute_direct_light(const Vector3& point, const Vector3& normal);
+Vector3 estimate_radiance(const Vector3& point, const Vector3& normal, const double gather_radius);
+
+// Main execution
+int main(int argc, char* argv[]) {
+ auto start = std::chrono::high_resolution_clock::now(); // Start timer
+ int num_photons = 10000;
+ int max_depth = 5;
+ double gather_radius = 0.5;
+
+ if (argc > 1) {
+ num_photons = std::atoi(argv[1]);
+ if (num_photons <= 0) {
+ std::cerr << "Invalid number of photons. Using default: 10000" << std::endl;
+ num_photons = 10000;
+ }
+ }
+ if (argc > 2) {
+ max_depth = std::atoi(argv[2]);
+ if (max_depth < 0) {
+ std::cerr << "Invalid max_depth. Using default: 5" << std::endl;
+ max_depth = 5;
+ }
+ }
+ if (argc > 3) {
+ gather_radius = std::atof(argv[3]);
+ if (gather_radius <= 0) {
+ std::cerr << "Invalid gather_radius. Using default: 0.5" << std::endl;
+ gather_radius = 0.5;
+ }
+ }
+ std::cout << "Number of photons: " << num_photons << std::endl;
+ std::cout << "Max depth: " << max_depth << std::endl;
+ std::cout << "Gather radius: " << gather_radius << std::endl;
+
+ // Seed random number generator
+ rng.seed(std::random_device()());
+
+ std::cout << "Emitting photons..." << std::endl;
+ // Add objects to the scene
+ objects.push_back(&sphere); object_types.push_back(0);
+ objects.push_back(&plane); object_types.push_back(1);
+
+ emit_photons(num_photons, max_depth);
+ const int photons_in_map = photon_map.size();
+
+ std::cout << "Rendering image..." << std::endl;
+ int width = 200;
+ int height = 100;
+ std::vector image(width * height);
+
+ double aspect_ratio = double(width) / height;
+ double fov = M_PI / 3.0; // 60 degrees field of view
+
+ for (int y = 0; y < height; ++y) {
+ for (int x = 0; x < width; ++x) {
+ // Convert pixel coordinate to camera ray
+ double px = (2 * (x + 0.5) / double(width) - 1) * tan(fov / 2.0) * aspect_ratio;
+ double py = (1 - 2 * (y + 0.5) / double(height)) * tan(fov / 2.0);
+ Vector3 ray_origin(0, 0, 0);
+ Vector3 ray_direction = Vector3(px, py, -1).normalize();
+ Vector3 color = trace_ray(ray_origin, ray_direction, gather_radius);
+ image[y * width + x] = Vector3(
+ std::min(color.x, 1.0),
+ std::min(color.y, 1.0),
+ std::min(color.z, 1.0)
+ );
+ }
+ }
+
+ // Save image to PPM file
+ std::ofstream ofs("render.ppm");
+ ofs << "P3\n" << width << " " << height << "\n255\n";
+ double gamma = 1.0 / 2.2; // Corrected gamma division
+ for (const auto& pixel : image) {
+ int r = static_cast(std::pow(pixel.x, gamma) * 255);
+ int g = static_cast(std::pow(pixel.y, gamma) * 255);
+ int b = static_cast(std::pow(pixel.z, gamma) * 255);
+ // Clamp values between 0 and 255
+ r = std::max(0, std::min(255, r));
+ g = std::max(0, std::min(255, g));
+ b = std::max(0, std::min(255, b));
+ ofs << r << " " << g << " " << b << "\n";
+ }
+ ofs.close();
+ std::cout << "Image generated" << std::endl;
+ std::cout << "width: " << width << std::endl;
+ std::cout << "height: " << width << std::endl;
+ std::cout << "photons in map: " << photons_in_map << std::endl;
+ std::cout << "count of photons: " << photon_number << std::endl;
+ std::cout << "count of rays: " << ray_number << std::endl;
+ std::cout << "Image saved to render.ppm" << std::endl;
+ std::chrono::duration elapsed = std::chrono::high_resolution_clock::now() - start;
+ std::cout << "Execution time: " << elapsed.count() << " seconds" << std::endl;
+ return 0;
+}
+
+void emit_photons(const int num_photons, const int max_depth) {
+ Vector3 per_photon_power = light_power / num_photons; // Scale light power per photon
+ for (int i = 0; i < num_photons; ++i) {
+ Vector3 direction = random_unit_vector();
+ Photon photon(light_position, direction, per_photon_power);
+ trace_photon(photon, 0, max_depth);
+ }
+}
+
+void trace_photon(Photon photon, int depth, const int max_depth) {
+ photon_number++;
+ if (depth > max_depth) {
+ return;
+ }
+ double closest_t = std::numeric_limits::infinity();
+ void* hit_object = nullptr;
+ int hit_type = -1;
+ Vector3 hit_point, normal;
+ // Find the nearest intersection
+ for (size_t i = 0; i < objects.size(); ++i) {
+ double t;
+ Vector3 temp_hit_point, temp_normal;
+ bool hit = false;
+ if (object_types[i] == 0) {
+ Sphere* obj = static_cast(objects[i]);
+ hit = obj->intersect(photon.position, photon.direction, t, temp_hit_point, temp_normal);
+ } else if (object_types[i] == 1) {
+ Plane* obj = static_cast(objects[i]);
+ hit = obj->intersect(photon.position, photon.direction, t, temp_hit_point, temp_normal);
+ }
+ if (hit && t < closest_t) {
+ closest_t = t;
+ hit_object = objects[i];
+ hit_type = object_types[i];
+ hit_point = temp_hit_point;
+ normal = temp_normal;
+ }
+ }
+ if (hit_object) {
+ photon_map.push_back(Photon(hit_point, photon.direction, photon.power));
+ // Diffuse reflection
+ Vector3 new_direction = random_hemisphere_direction(normal);
+ photon.position = hit_point;
+ photon.direction = new_direction;
+ // Absorb some power
+ photon.power = photon.power * 0.8; // Simple absorption
+ trace_photon(photon, depth + 1, max_depth);
+ }
+}
+
+Vector3 trace_ray(const Vector3& ray_origin, const Vector3& ray_direction, const double gather_radius) {
+ ray_number++;
+ double closest_t = std::numeric_limits::infinity();
+ void* hit_object = nullptr;
+ int hit_type = -1;
+ Vector3 hit_point, normal;
+ Vector3 obj_color;
+ // Find the nearest intersection
+ for (size_t i = 0; i < objects.size(); ++i) {
+ double t;
+ Vector3 temp_hit_point, temp_normal;
+ bool hit = false;
+ if (object_types[i] == 0) {
+ Sphere* obj = static_cast(objects[i]);
+ hit = obj->intersect(ray_origin, ray_direction, t, temp_hit_point, temp_normal);
+ if (hit) obj_color = obj->color;
+ } else if (object_types[i] == 1) {
+ Plane* obj = static_cast(objects[i]);
+ hit = obj->intersect(ray_origin, ray_direction, t, temp_hit_point, temp_normal);
+ if (hit) obj_color = obj->color;
+ }
+ if (hit && t < closest_t) {
+ closest_t = t;
+ hit_object = objects[i];
+ hit_type = object_types[i];
+ hit_point = temp_hit_point;
+ normal = temp_normal;
+ }
+ }
+ if (hit_object) {
+ Vector3 direct_light = compute_direct_light(hit_point, normal);
+ Vector3 indirect_light = estimate_radiance(hit_point, normal, gather_radius);
+ return obj_color * (direct_light + indirect_light); // Component-wise multiplication
+ } else {
+ return Vector3(0, 0, 0); // Background color
+ }
+}
+
+Vector3 compute_direct_light(const Vector3& point, const Vector3& normal) {
+ // Simple Lambertian reflection from light source
+ Vector3 direction_to_light = (light_position - point).normalize();
+ // Shadow ray
+ Vector3 shadow_origin = point + normal * 1e-5;
+ Vector3 shadow_ray = direction_to_light;
+ bool in_shadow = false;
+ for (size_t i = 0; i < objects.size(); ++i) {
+ double t;
+ Vector3 temp_hit_point, temp_normal;
+ bool hit = false;
+ if (object_types[i] == 0) {
+ Sphere* obj = static_cast(objects[i]);
+ hit = obj->intersect(shadow_origin, shadow_ray, t, temp_hit_point, temp_normal);
+ } else if (object_types[i] == 1) {
+ Plane* obj = static_cast(objects[i]);
+ hit = obj->intersect(shadow_origin, shadow_ray, t, temp_hit_point, temp_normal);
+ }
+ if (hit) {
+ in_shadow = true;
+ break;
+ }
+ }
+ if (in_shadow) {
+ return Vector3(0, 0, 0);
+ } else {
+ double intensity = std::max(0.0, normal.dot(direction_to_light));
+ double distance2 = (light_position - point).dot(light_position - point);
+ return (light_power * intensity) / (4 * M_PI * distance2);
+ }
+}
+
+Vector3 estimate_radiance(const Vector3& point, const Vector3& normal, const double gather_radius) {
+ // Gather photons within the gather_radius
+ Vector3 accumulated_power(0.0, 0.0, 0.0);
+ for (const auto& photon : photon_map) {
+ double distance = (photon.position - point).norm();
+ if (distance < gather_radius) {
+ double weight = std::max(0.0, normal.dot((-photon.direction).normalize()));
+ accumulated_power = accumulated_power + photon.power * weight;
+ }
+ }
+ double area = M_PI * gather_radius * gather_radius;
+ return accumulated_power / area; // Removed division by num_photons
+}
\ No newline at end of file
diff --git a/Programming/TRAK/code/photonmapping/python/main.py b/Programming/TRAK/code/photonmapping/python/main.py
new file mode 100644
index 00000000..92567914
--- /dev/null
+++ b/Programming/TRAK/code/photonmapping/python/main.py
@@ -0,0 +1,235 @@
+import numpy as np
+import matplotlib.pyplot as plt
+import time
+
+PHOTONS = 0
+
+# Define basic vector operations
+class Vector3:
+ def __init__(self, x, y, z):
+ self.x = x
+ self.y = y
+ self.z = z
+
+ def __add__(self, other):
+ return Vector3(self.x+other.x, self.y+other.y, self.z+other.z)
+
+ def __sub__(self, other):
+ return Vector3(self.x-other.x, self.y-other.y, self.z-other.z)
+
+ def __mul__(self, scalar):
+ return Vector3(self.x*scalar, self.y*scalar, self.z*scalar)
+
+ def dot(self, other):
+ return self.x*other.x + self.y*other.y + self.z*other.z
+
+ def norm(self):
+ return np.sqrt(self.dot(self))
+
+ def normalize(self):
+ n = self.norm()
+ return Vector3(self.x/n, self.y/n, self.z/n)
+
+# Define the photon
+class Photon:
+ def __init__(self, position, direction, power):
+ self.position = position
+ self.direction = direction
+ self.power = power
+
+# Define a simple sphere
+class Sphere:
+ def __init__(self, center, radius, color):
+ self.center = center
+ self.radius = radius
+ self.color = color
+
+ def intersect(self, ray_origin, ray_direction):
+ # Solve quadratic equation for intersection
+ oc = ray_origin - self.center
+ a = ray_direction.dot(ray_direction)
+ b = 2.0 * oc.dot(ray_direction)
+ c = oc.dot(oc) - self.radius*self.radius
+ discriminant = b*b - 4*a*c
+ if discriminant < 0:
+ return None # No intersection
+ else:
+ t = (-b - np.sqrt(discriminant)) / (2.0*a)
+ if t < 0:
+ t = (-b + np.sqrt(discriminant)) / (2.0*a)
+ if t < 0:
+ return None
+ hit_point = ray_origin + ray_direction * t
+ normal = (hit_point - self.center).normalize()
+ return (t, hit_point, normal)
+
+# Define a simple plane
+class Plane:
+ def __init__(self, point, normal, color):
+ self.point = point
+ self.normal = normal.normalize()
+ self.color = color
+
+ def intersect(self, ray_origin, ray_direction):
+ denom = self.normal.dot(ray_direction)
+ if abs(denom) > 1e-6:
+ t = (self.point - ray_origin).dot(self.normal) / denom
+ if t >= 0:
+ hit_point = ray_origin + ray_direction * t
+ return (t, hit_point, self.normal)
+ return None
+
+# Scene setup
+sphere = Sphere(Vector3(0, 0, -5), 1.0, np.array([1, 0, 0])) # Red sphere
+plane = Plane(Vector3(0, -1, 0), Vector3(0, 1, 0), np.array([0.5, 0.5, 0.5])) # Gray plane
+
+objects = [sphere, plane]
+
+# Light source
+light_position = Vector3(-5, 5, -5)
+light_power = np.array([1, 1, 1]) * 1000 # Intense white light
+
+# Photon map
+photon_map = []
+
+# Parameters
+num_photons = 10000 # Number of photons to emit
+max_depth = 5 # Maximum number of bounces
+gather_radius = 0.5 # Radius for radiance estimation
+
+def emit_photons():
+ for _ in range(num_photons):
+ # Emit photons in random directions from the light source
+ direction = random_unit_vector()
+ power = light_power / num_photons
+ photon = Photon(light_position, direction, power)
+ trace_photon(photon, 0)
+
+def trace_photon(photon, depth):
+ global PHOTONS
+ PHOTONS += 1
+ if depth > max_depth:
+ return
+ closest_t = np.inf
+ hit_object = None
+ hit_info = None
+ # Find the nearest intersection
+ for obj in objects:
+ result = obj.intersect(photon.position, photon.direction)
+ if result:
+ t, hit_point, normal = result
+ if t < closest_t:
+ closest_t = t
+ hit_object = obj
+ hit_info = (hit_point, normal)
+ if hit_object:
+ hit_point, normal = hit_info
+ photon_map.append((hit_point, photon.power))
+ # Diffuse reflection
+ new_direction = random_hemisphere_direction(normal)
+ photon.position = hit_point
+ photon.direction = new_direction
+ # Absorb some power
+ photon.power = photon.power * 0.8 # Simple absorption
+ trace_photon(photon, depth+1)
+
+def random_unit_vector():
+ theta = np.random.uniform(0, 2*np.pi)
+ z = np.random.uniform(-1, 1)
+ r = np.sqrt(1 - z*z)
+ return Vector3(r * np.cos(theta), r * np.sin(theta), z)
+
+def random_hemisphere_direction(normal):
+ dir = random_unit_vector()
+ if dir.dot(normal) < 0:
+ dir = Vector3(-dir.x, -dir.y, -dir.z)
+ return dir
+
+def render_image(width, height):
+ aspect_ratio = width / height
+ fov = np.pi / 3 # 60 degrees field of view
+ image = np.zeros((height, width, 3))
+ for y in range(height):
+ for x in range(width):
+ # Convert pixel coordinate to camera ray
+ px = (2 * (x + 0.5) / width - 1) * np.tan(fov / 2) * aspect_ratio
+ py = (1 - 2 * (y + 0.5) / height) * np.tan(fov / 2)
+ ray_origin = Vector3(0, 0, 0)
+ ray_direction = Vector3(px, py, -1).normalize()
+ color = trace_ray(ray_origin, ray_direction)
+ image[y, x, :] = np.clip(color, 0, 1)
+ return image
+
+def trace_ray(ray_origin, ray_direction):
+ closest_t = np.inf
+ hit_object = None
+ hit_info = None
+ # Find the nearest intersection
+ for obj in objects:
+ result = obj.intersect(ray_origin, ray_direction)
+ if result:
+ t, hit_point, normal = result
+ if t < closest_t:
+ closest_t = t
+ hit_object = obj
+ hit_info = (hit_point, normal, obj.color)
+ if hit_object:
+ hit_point, normal, color = hit_info
+ direct_light = compute_direct_light(hit_point, normal)
+ indirect_light = estimate_radiance(hit_point, normal)
+ return color * (direct_light + indirect_light)
+ else:
+ return np.array([0, 0, 0]) # Background color
+
+def compute_direct_light(point, normal):
+ # Simple Lambertian reflection from light source
+ direction_to_light = (light_position - point).normalize()
+ # Shadow ray
+ shadow_origin = point + normal * 1e-5
+ shadow_ray = direction_to_light
+ in_shadow = False
+ for obj in objects:
+ result = obj.intersect(shadow_origin, shadow_ray)
+ if result:
+ in_shadow = True
+ break
+ if in_shadow:
+ return np.array([0, 0, 0])
+ else:
+ intensity = max(0, normal.dot(direction_to_light))
+ return intensity * light_power / (4 * np.pi * (light_position - point).norm()**2)
+
+def estimate_radiance(point, normal):
+ # Gather photons within the gather_radius
+ accumulated_power = np.array([0.0, 0.0, 0.0])
+ for photon_pos, photon_power in photon_map:
+ distance = (photon_pos - point).norm()
+ if distance < gather_radius:
+ weight = max(0, normal.dot((photon_pos - point).normalize()))
+ accumulated_power += photon_power * weight
+ area = np.pi * gather_radius ** 2
+ return accumulated_power / (area * num_photons)
+
+# Main execution
+if __name__ == '__main__':
+ print("Emitting photons...")
+ emit_photons()
+
+ print("Rendering image...")
+ width = 100
+ height = 100
+ t0 = time.time()
+ image = render_image(width, height)
+
+ print (f"Render Took: {round(time.time() - t0, 2)}s\n"
+ f"resolution: {width}x{height}\n"
+ f"photons (emitted): {num_photons}\n"
+ f"photons (reflected): {PHOTONS - num_photons}\n"
+ f"photons (total): {PHOTONS}\n"
+ f"rays: {width*height}"
+ )
+
+ # Display the image
+ plt.imshow(image)
+ plt.axis('off')
+ plt.show()
diff --git a/Programming/TRAK/config.ini b/Programming/TRAK/config.ini
new file mode 100644
index 00000000..987bfe7b
--- /dev/null
+++ b/Programming/TRAK/config.ini
@@ -0,0 +1,16 @@
+[DEFAULT]
+algorithm = ray_tracing
+scene = cornell_box
+environment = lake.png
+env_blur = 0
+resolution = 400x300
+output = output.png
+
+[ray_tracing]
+max_depth = 5
+samples_per_pixel = 6
+
+[photon_mapping]
+num_photons = 10000
+max_depth = 5
+gather_radius = 0.5
\ No newline at end of file
diff --git a/Programming/TRAK/docs/TRAK___Projekty.pdf b/Programming/TRAK/docs/TRAK___Projekty.pdf
new file mode 100644
index 00000000..f6aaf826
Binary files /dev/null and b/Programming/TRAK/docs/TRAK___Projekty.pdf differ
diff --git a/Programming/TRAK/environments/README.md b/Programming/TRAK/environments/README.md
new file mode 100644
index 00000000..913b13a2
--- /dev/null
+++ b/Programming/TRAK/environments/README.md
@@ -0,0 +1,3 @@
+# Environment directory
+
+Environments with .hdr or .hdri extension should stored in this directory. These files are used to create the skybox in the scene.
\ No newline at end of file
diff --git a/Programming/TRAK/environments/lake.png b/Programming/TRAK/environments/lake.png
new file mode 100644
index 00000000..f7541a2c
Binary files /dev/null and b/Programming/TRAK/environments/lake.png differ
diff --git a/Programming/TRAK/environments/miramar.jpeg b/Programming/TRAK/environments/miramar.jpeg
new file mode 100644
index 00000000..1a720c20
Binary files /dev/null and b/Programming/TRAK/environments/miramar.jpeg differ
diff --git a/Programming/TRAK/environments/stormydays.png b/Programming/TRAK/environments/stormydays.png
new file mode 100644
index 00000000..0e76d1a1
Binary files /dev/null and b/Programming/TRAK/environments/stormydays.png differ
diff --git a/Programming/TRAK/main.py b/Programming/TRAK/main.py
new file mode 100644
index 00000000..53d8a8db
--- /dev/null
+++ b/Programming/TRAK/main.py
@@ -0,0 +1,76 @@
+import argparse
+from rendering import ray_trace
+from utils import load_config, parse_resolution
+import importlib
+import os
+import matplotlib.pyplot as plt
+from photon_mapping import render_photon_mapping
+
+def main():
+ # default config
+ config = load_config('config.ini')
+
+ # Parse
+ parser = argparse.ArgumentParser(description="Rendering Program")
+ parser.add_argument('--algorithm', type=str, help='Algorithm to use', default=config.get('DEFAULT', 'algorithm'))
+ parser.add_argument('--scene', type=str, help='Name of the scene to render (without .py).', default=config.get('DEFAULT', 'scene'))
+ parser.add_argument('--environment', type=str, help='Environment file', default=config.get('DEFAULT', 'environment'))
+ parser.add_argument('--env_blur', type=str, help='Environment blur', default=config.get('DEFAULT', 'env_blur'))
+ parser.add_argument('--resolution', type=str, help='Image resolution (WIDTHxHEIGHT)',
+ default=config.get('DEFAULT', 'resolution'))
+ parser.add_argument("--samples_per_pixel", type=int, default=config.get('ray_tracing', 'samples_per_pixel'), help="Samples per pixel for rendering.")
+ parser.add_argument("--output", type=str, default=config.get('DEFAULT', 'output'), help="Output file name.")
+
+ parser.add_argument('--num_spheres', type=int, default=3, help='Number of spheres in the scene for Ray Tracing 0')
+ parser.add_argument('--num_photons', type=int, default=config.getint('photon_mapping', 'num_photons'), help='Number of photons for photon mapping')
+ parser.add_argument('--max_depth', type=int, default=config.getint('photon_mapping', 'max_depth'),
+ help='Maximum depth for photon tracing')
+ parser.add_argument('--gather_radius', type=float, default=config.getfloat('photon_mapping', 'gather_radius'),
+ help='Radius for radiance estimation in photon mapping')
+
+
+ args = parser.parse_args()
+
+ width, height = parse_resolution(args.resolution)
+
+ # Run the selected algorithm
+ if args.algorithm == "ray_tracing0":
+ print("Starting ray tracing zero...")
+ # ray_trace(args.scene, args.environment_map, image_width=width, image_height=height, output_file="output_ray_traced.png")
+ ray_trace(args.num_spheres, args.environment_map, image_width=width, image_height=height, # na razie generujemy w kodzie, ale potem trzeba będzie obj wczytywać
+ output_file="output_ray_traced.png")
+ elif args.algorithm == "ray_tracing":
+ print("Starting ray tracing...")
+ try:
+ print(args.scene)
+ scene_module = importlib.import_module(f"scenes.{args.scene}")
+ except ModuleNotFoundError:
+ print(f"Error: Scene '{args.scene}' not found in the 'scenes' directory.")
+ return
+ try:
+ scene = scene_module.setup_scene(width=width, height=height, environment=f"{args.environment}")
+ except AttributeError:
+ print(f"Error: Scene '{args.scene}' does not define a `setup_scene` function.")
+ return
+ # Renderowanie
+ print(f"Rendering scene '{args.scene}' with {args.samples_per_pixel} samples per pixel...")
+ img = scene.render(samples_per_pixel=args.samples_per_pixel)
+ output_path = os.path.join("outputs", args.output)
+ img.save(output_path)
+ print(f"Image saved to {output_path}")
+ img.show()
+ elif args.algorithm == "photon_mapping":
+ print("Starting photon mapping...")
+ image = render_photon_mapping(width, height, args.num_photons, args.max_depth, args.gather_radius)
+ plt.imshow(image)
+ plt.axis('off')
+ output_path = os.path.join("outputs", args.output)
+ plt.savefig(output_path)
+ print(f"Image saved to {output_path}")
+ plt.show()
+ else:
+ print(f"Unknown algorithm: {args.algorithm}")
+ return
+
+if __name__ == '__main__':
+ main()
diff --git a/Programming/TRAK/output_ray_traced.png b/Programming/TRAK/output_ray_traced.png
new file mode 100644
index 00000000..7da47ec3
Binary files /dev/null and b/Programming/TRAK/output_ray_traced.png differ
diff --git a/Programming/TRAK/outputs/output.png b/Programming/TRAK/outputs/output.png
new file mode 100644
index 00000000..dddfe4ce
Binary files /dev/null and b/Programming/TRAK/outputs/output.png differ
diff --git a/Programming/TRAK/outputs/output_photonmapping_cube_ball_wall_200x200.png b/Programming/TRAK/outputs/output_photonmapping_cube_ball_wall_200x200.png
new file mode 100644
index 00000000..d7cba27e
Binary files /dev/null and b/Programming/TRAK/outputs/output_photonmapping_cube_ball_wall_200x200.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_cornell_box.png b/Programming/TRAK/outputs/ray_tracing_cornell_box.png
new file mode 100644
index 00000000..abb88b64
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_cornell_box.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_cornell_box_200_200x200.png b/Programming/TRAK/outputs/ray_tracing_cornell_box_200_200x200.png
new file mode 100644
index 00000000..8156f967
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_cornell_box_200_200x200.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_cornell_box_200x200.png b/Programming/TRAK/outputs/ray_tracing_cornell_box_200x200.png
new file mode 100644
index 00000000..e8466126
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_cornell_box_200x200.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_transparent_cuboid.png b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid.png
new file mode 100644
index 00000000..61c53872
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_200_200x200.png b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_200_200x200.png
new file mode 100644
index 00000000..6fe11088
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_200_200x200.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_200x200.png b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_200x200.png
new file mode 100644
index 00000000..6ec58ab9
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_200x200.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_300x300.png b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_300x300.png
new file mode 100644
index 00000000..c96ba70c
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_transparent_cuboid_300x300.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_two_spheres.png b/Programming/TRAK/outputs/ray_tracing_two_spheres.png
new file mode 100644
index 00000000..9ea72e38
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_two_spheres.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_two_spheres_200_200x200.png b/Programming/TRAK/outputs/ray_tracing_two_spheres_200_200x200.png
new file mode 100644
index 00000000..115c9a21
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_two_spheres_200_200x200.png differ
diff --git a/Programming/TRAK/outputs/ray_tracing_two_spheres_200x200.png b/Programming/TRAK/outputs/ray_tracing_two_spheres_200x200.png
new file mode 100644
index 00000000..1d1e3c3b
Binary files /dev/null and b/Programming/TRAK/outputs/ray_tracing_two_spheres_200x200.png differ
diff --git a/Programming/TRAK/photon_mapping.py b/Programming/TRAK/photon_mapping.py
new file mode 100644
index 00000000..befe273c
--- /dev/null
+++ b/Programming/TRAK/photon_mapping.py
@@ -0,0 +1,191 @@
+import numpy as np
+import matplotlib.pyplot as plt
+
+# Define basic vector operations
+class Vector3:
+ def __init__(self, x, y, z):
+ self.x = x
+ self.y = y
+ self.z = z
+
+ def __add__(self, other):
+ return Vector3(self.x + other.x, self.y + other.y, self.z + other.z)
+
+ def __sub__(self, other):
+ return Vector3(self.x - other.x, self.y - other.y, self.z - other.z)
+
+ def __mul__(self, scalar):
+ return Vector3(self.x * scalar, self.y * scalar, self.z * scalar)
+
+ def dot(self, other):
+ return self.x * other.x + self.y * other.y + self.z * other.z
+
+ def norm(self):
+ return np.sqrt(self.dot(self))
+
+ def normalize(self):
+ n = self.norm()
+ return Vector3(self.x / n, self.y / n, self.z / n)
+
+
+class Photon:
+ def __init__(self, position, direction, power):
+ self.position = position
+ self.direction = direction
+ self.power = power
+
+
+class Sphere:
+ def __init__(self, center, radius, color):
+ self.center = center
+ self.radius = radius
+ self.color = color
+
+ def intersect(self, ray_origin, ray_direction):
+ oc = ray_origin - self.center
+ a = ray_direction.dot(ray_direction)
+ b = 2.0 * oc.dot(ray_direction)
+ c = oc.dot(oc) - self.radius * self.radius
+ discriminant = b * b - 4 * a * c
+ if discriminant < 0:
+ return None
+ else:
+ t = (-b - np.sqrt(discriminant)) / (2.0 * a)
+ if t < 0:
+ t = (-b + np.sqrt(discriminant)) / (2.0 * a)
+ if t < 0:
+ return None
+ hit_point = ray_origin + ray_direction * t
+ normal = (hit_point - self.center).normalize()
+ return (t, hit_point, normal)
+
+
+class Plane:
+ def __init__(self, point, normal, color):
+ self.point = point
+ self.normal = normal.normalize()
+ self.color = color
+
+ def intersect(self, ray_origin, ray_direction):
+ denom = self.normal.dot(ray_direction)
+ if abs(denom) > 1e-6:
+ t = (self.point - ray_origin).dot(self.normal) / denom
+ if t >= 0:
+ hit_point = ray_origin + ray_direction * t
+ return (t, hit_point, self.normal)
+ return None
+
+
+def render_photon_mapping(width, height, num_photons, max_depth, gather_radius):
+ # Photon mapping logic
+ photon_map = []
+ sphere = Sphere(Vector3(0, 0, -5), 1.0, np.array([1, 0, 0])) # Red sphere
+ plane = Plane(Vector3(0, -1, 0), Vector3(0, 1, 0), np.array([0.5, 0.5, 0.5])) # Gray plane
+ objects = [sphere, plane]
+
+ light_position = Vector3(-5, 5, -5)
+ light_power = np.array([1, 1, 1]) * 1000
+
+ def emit_photons():
+ for _ in range(num_photons):
+ direction = random_unit_vector()
+ power = light_power / num_photons
+ photon = Photon(light_position, direction, power)
+ trace_photon(photon, 0)
+
+ def trace_photon(photon, depth):
+ if depth > max_depth:
+ return
+ closest_t = np.inf
+ hit_object = None
+ hit_info = None
+ for obj in objects:
+ result = obj.intersect(photon.position, photon.direction)
+ if result:
+ t, hit_point, normal = result
+ if t < closest_t:
+ closest_t = t
+ hit_object = obj
+ hit_info = (hit_point, normal)
+ if hit_object:
+ hit_point, normal = hit_info
+ photon_map.append((hit_point, photon.power))
+ new_direction = random_hemisphere_direction(normal)
+ photon.position = hit_point
+ photon.direction = new_direction
+ photon.power = photon.power * 0.8
+ trace_photon(photon, depth + 1)
+
+ def random_unit_vector():
+ theta = np.random.uniform(0, 2 * np.pi)
+ z = np.random.uniform(-1, 1)
+ r = np.sqrt(1 - z * z)
+ return Vector3(r * np.cos(theta), r * np.sin(theta), z)
+
+ def random_hemisphere_direction(normal):
+ dir = random_unit_vector()
+ if dir.dot(normal) < 0:
+ dir = Vector3(-dir.x, -dir.y, -dir.z)
+ return dir
+
+ def trace_ray(ray_origin, ray_direction):
+ closest_t = np.inf
+ hit_object = None
+ hit_info = None
+ for obj in objects:
+ result = obj.intersect(ray_origin, ray_direction)
+ if result:
+ t, hit_point, normal = result
+ if t < closest_t:
+ closest_t = t
+ hit_object = obj
+ hit_info = (hit_point, normal, obj.color)
+ if hit_object:
+ hit_point, normal, color = hit_info
+ direct_light = compute_direct_light(hit_point, normal)
+ indirect_light = estimate_radiance(hit_point, normal)
+ return color * (direct_light + indirect_light)
+ else:
+ return np.array([0, 0, 0])
+
+ def compute_direct_light(point, normal):
+ direction_to_light = (light_position - point).normalize()
+ shadow_origin = point + normal * 1e-5
+ shadow_ray = direction_to_light
+ in_shadow = False
+ for obj in objects:
+ result = obj.intersect(shadow_origin, shadow_ray)
+ if result:
+ in_shadow = True
+ break
+ if in_shadow:
+ return np.array([0, 0, 0])
+ else:
+ intensity = max(0, normal.dot(direction_to_light))
+ return intensity * light_power / (4 * np.pi * (light_position - point).norm() ** 2)
+
+ def estimate_radiance(point, normal):
+ accumulated_power = np.array([0.0, 0.0, 0.0])
+ for photon_pos, photon_power in photon_map:
+ distance = (photon_pos - point).norm()
+ if distance < gather_radius:
+ weight = max(0, normal.dot((photon_pos - point).normalize()))
+ accumulated_power += photon_power * weight
+ area = np.pi * gather_radius ** 2
+ return accumulated_power / area
+
+ emit_photons()
+
+ aspect_ratio = width / height
+ fov = np.pi / 3
+ image = np.zeros((height, width, 3))
+ for y in range(height):
+ for x in range(width):
+ px = (2 * (x + 0.5) / width - 1) * np.tan(fov / 2) * aspect_ratio
+ py = (1 - 2 * (y + 0.5) / height) * np.tan(fov / 2)
+ ray_origin = Vector3(0, 0, 0)
+ ray_direction = Vector3(px, py, -1).normalize()
+ color = trace_ray(ray_origin, ray_direction)
+ image[y, x, :] = np.clip(color, 0, 1)
+
+ return image
diff --git a/Programming/TRAK/ray tracing testy.pdf b/Programming/TRAK/ray tracing testy.pdf
new file mode 100644
index 00000000..afa418d4
Binary files /dev/null and b/Programming/TRAK/ray tracing testy.pdf differ
diff --git a/Programming/TRAK/rendering.py b/Programming/TRAK/rendering.py
new file mode 100644
index 00000000..b0112517
--- /dev/null
+++ b/Programming/TRAK/rendering.py
@@ -0,0 +1,425 @@
+#!/usr/bin/env python3
+""" Renders an image using raytracing """
+import numpy as np
+import matplotlib.pyplot as plt
+import time
+
+def ray_trace(num_spheres, environment, image_width=400, image_height=300, output_file="fig.png"):
+ IMAGE_WIDTH = image_width
+ IMAGE_HEIGHT = image_height
+
+
+ def normalize(vector):
+ """
+ Normalize a vector.
+
+ Parameters:
+ vector (numpy.ndarray): The input vector to be normalized.
+
+ Returns:
+ numpy.ndarray: The normalized vector.
+ """
+ vector /= np.linalg.norm(vector)
+ return vector
+
+
+ def intersect_plane(ray_origin, ray_direction, plane_point, plane_normal):
+ """
+ Calculate the intersection of a ray with a plane.
+
+ Parameters:
+ ray_origin (numpy.ndarray): A 3D point representing the origin of the ray.
+ ray_direction (numpy.ndarray): A normalized 3D vector representing the
+ direction of the ray.
+ plane_point (numpy.ndarray): A 3D point representing a point on the plane.
+ plane_normal (numpy.ndarray): A normalized 3D vector representing
+ the normal of the plane.
+
+ Returns:
+ float: The distance from the origin ray_origin to the intersection
+ point with the plane.
+ Returns +inf if there is no intersection or if the intersection is
+ behind the origin.
+ """
+ denom = np.dot(ray_direction, plane_normal)
+ if np.abs(denom) < 1e-6:
+ return np.inf
+ d = np.dot(plane_point - ray_origin, plane_normal) / denom
+ if d < 0:
+ return np.inf
+ return d
+
+
+ def intersect_sphere(ray_origin, ray_direction, sphere_center, sphere_radius):
+ """
+ Calculate the intersection of a ray with a sphere.
+
+ Parameters:
+ ray_origin (numpy.ndarray): A 3D point representing the origin of the ray.
+ ray_direction (numpy.ndarray): A normalized 3D vector representing the
+ direction of the ray.
+ sphere_center (numpy.ndarray): A 3D point representing
+ the center of the sphere.
+ sphere_radius (float): The radius of the sphere.
+
+ Returns:
+ float: The distance from the origin ray_origin to the intersection
+ point with the sphere.
+ Returns +inf if there is no intersection or if the intersection is
+ behind the origin.
+ """
+ a = np.dot(ray_direction, ray_direction)
+ origin_to_center = ray_origin - sphere_center
+ b = 2 * np.dot(ray_direction, origin_to_center)
+ radius_squared = sphere_radius * sphere_radius
+ c = np.dot(origin_to_center, origin_to_center) - radius_squared
+ disc = b * b - 4 * a * c
+ return calculate_sphere_intersection(a, b, c, disc)
+
+
+ def calculate_sphere_intersection(a, b, c, disc):
+ """
+ Calculate the
+ intersection distance of a ray with a sphere using the quadratic formula.
+
+ Parameters:
+ a (float): Coefficient of t^2 in the quadratic equation.
+ b (float): Coefficient of t in the quadratic equation.
+ c (float): Constant term in the quadratic equation.
+ disc (float): Discriminant of the quadratic equation.
+
+ Returns:
+ float:
+ The distance from the origin to the intersection point with the sphere.
+ Returns +inf if there is no intersection
+ or if the intersection is behind the origin.
+ """
+ if disc > 0:
+ distance_squared = np.sqrt(disc)
+ # q is used to find the roots of the quadratic equation
+ if b < 0:
+ q = (-b - distance_squared) / 2.0
+ else:
+ q = (-b + distance_squared) / 2.0
+ t0 = q / a
+ t1 = c / q
+ t0, t1 = min(t0, t1), max(t0, t1)
+ if t1 >= 0:
+ return t1 if t0 < 0 else t0
+ return np.inf
+
+
+ def intersect(ray_origin, ray_direction, object_):
+ """
+ Calculate the intersection of a ray with an object.
+
+ Parameters:
+ ray_origin (numpy.ndarray): A 3D point representing the origin of the ray.
+ ray_direction (numpy.ndarray): A normalized 3D vector representing the
+ direction of the ray.
+ obj (dict): A dictionary representing the object with keys
+ 'type', 'position', 'normal' (for planes), and 'radius' (for spheres).
+
+ Returns:
+ float: The distance from the origin ray_origin to the intersection
+ point with the object.
+ Returns +inf if there is no intersection or if the intersection is
+ behind the origin.
+ """
+ if object_['type'] == 'plane':
+ return intersect_plane(ray_origin, ray_direction,
+ object_['position'], object_['normal'])
+ # object_['type'] == 'sphere':
+ return intersect_sphere(ray_origin, ray_direction,
+ object_['position'], object_['radius'])
+
+
+ def get_normal(object_, intersection_point):
+ """
+ Calculate the normal at the intersection point on the object.
+
+ Parameters:
+ obj (dict): A dictionary representing the object with keys
+ 'type' and 'position'.
+ intersection_point (numpy.ndarray): A 3D point representing the
+ intersection point on the object.
+
+ Returns:
+ numpy.ndarray: The normal vector at the intersection point.
+ """
+ if object_['type'] == 'sphere':
+ normal = normalize(intersection_point - object_['position'])
+ elif object_['type'] == 'plane':
+ normal = object_['normal']
+ else:
+ raise ValueError(f"Unknown object type: {object_['type']}")
+ return normal
+
+
+ def get_color(object_, intersection_point):
+ """
+ Get the color of the object at the intersection point.
+
+ Parameters:
+ object_ (dict): A dictionary representing the object with a key 'color'.
+ intersection_point (numpy.ndarray): A 3D point representing the
+ intersection point on the object.
+
+ Returns:
+ numpy.ndarray: The color of the object at the intersection point.
+ """
+ color = object_['color']
+ if not hasattr(color, '__len__'):
+ color = color(intersection_point)
+ return color
+
+
+ def trace_ray(ray_origin, ray_direction):
+ """
+ Trace a ray and find the color at the intersection point.
+
+ Parameters:
+ ray_origin (numpy.ndarray): A 3D point representing the origin of the ray.
+ ray_direction (numpy.ndarray):
+ A normalized 3D vector representing the direction of the ray.
+
+ Returns:
+ tuple: A tuple containing the object,
+ intersection point, normal at the intersection,
+ and the color at the intersection point.
+ Returns None if there is no intersection.
+ """
+ t, obj_idx = find_intersection(ray_origin, ray_direction)
+ if t == np.inf:
+ return None
+ object_, intersection_point = get_intersection_details(
+ ray_origin, ray_direction, t, obj_idx)
+ normal, color = get_normal(object_, intersection_point), get_color(
+ object_, intersection_point)
+ if is_shadowed(intersection_point, normal, obj_idx):
+ return None
+ return compute_color(
+ object_, intersection_point, normal, color, ray_origin)
+
+
+ def find_intersection(ray_origin, ray_direction):
+ """
+ Find the intersection of a ray with the objects in the scene.
+
+ Parameters:
+ ray_origin (numpy.ndarray): A 3D point representing the origin of the ray.
+ ray_direction (numpy.ndarray):
+ A normalized 3D vector representing the direction of the ray.
+
+ Returns:
+ tuple: A tuple containing the distance to the intersection point
+ and the index of the intersected object.
+ """
+ t = np.inf
+ obj_idx = -1
+ for index, object_ in enumerate(scene):
+ t_obj = intersect(ray_origin, ray_direction, object_)
+ if t_obj < t:
+ t, obj_idx = t_obj, index
+ return t, obj_idx
+
+
+ def get_intersection_details(ray_origin, ray_direction, t, obj_idx):
+ """
+ Get the details of the intersection point on the object.
+
+ Parameters:
+ ray_origin (numpy.ndarray): A 3D point representing the origin of the ray.
+ ray_direction (numpy.ndarray):
+ A normalized 3D vector representing the direction of the ray.
+ t (float): The distance to the intersection point.
+ obj_idx (int): The index of the intersected object in the scene.
+
+ Returns:
+ tuple: A tuple containing the intersected object
+ and the intersection point.
+ """
+ object_ = scene[obj_idx]
+ intersection_point = ray_origin + ray_direction * t
+ return object_, intersection_point
+
+
+ def is_shadowed(intersection_point, normal, obj_idx):
+ """
+ Determine if the intersection point is in shadow.
+
+ Parameters:
+ intersection_point (numpy.ndarray):
+ A 3D point representing the intersection point on the object.
+ normal (numpy.ndarray): The normal vector at the intersection point.
+ obj_idx (int): The index of the intersected object in the scene.
+
+ Returns:
+ bool: True if the intersection point is in shadow, False otherwise.
+ """
+ to_light = normalize(L - intersection_point)
+ shadow_intersections = [intersect(
+ intersection_point + normal * .0001, to_light, obj_sh)
+ for k, obj_sh in enumerate(scene) if k != obj_idx]
+ return shadow_intersections and min(shadow_intersections) < np.inf
+
+
+ def compute_color(object_, intersection_point, normal, color, ray_origin):
+ """
+ Compute the color at the intersection point using shading techniques.
+
+ Parameters:
+ object_ (dict): A dictionary representing the intersected object.
+ intersection_point (numpy.ndarray):
+ A 3D point representing the intersection point on the object.
+ normal (numpy.ndarray): The normal vector at the intersection point.
+ color (numpy.ndarray): The base color of the object.
+ ray_origin (numpy.ndarray): A 3D point representing the origin of the ray.
+
+ Returns:
+ tuple:
+ A tuple containing the intersected object, intersection point, normal,
+ and the computed color.
+ """
+ to_light = normalize(L - intersection_point)
+ to_origin = normalize(ray_origin - intersection_point)
+ color_ray = AMBIENT
+ diffuse_intensity = object_.get('diffuse_c', DIFFUSE_C) * max(
+ np.dot(normal, to_light), 0)
+ color_ray += diffuse_intensity * color
+ half_vector = normalize(to_light + to_origin)
+ specular_intensity = object_.get('specular_c', SPECULAR_C) * max(
+ np.dot(normal, half_vector), 0) ** SPECULAR_K
+ color_ray += specular_intensity * color_light
+ return object_, intersection_point, normal, color_ray
+
+
+ def add_sphere(position, radius, color):
+ """
+ Create a dictionary representing a sphere object.
+
+ Parameters:
+ position (list or numpy.ndarray):
+ A 3D point representing the position of the sphere.
+ radius (float): The radius of the sphere.
+ color (list or numpy.ndarray): The color of the sphere.
+
+ Returns:
+ dict: A dictionary representing the sphere object.
+ """
+ return {
+ 'type': 'sphere',
+ 'position': np.array(position),
+ 'radius': np.array(radius),
+ 'color': np.array(color),
+ 'reflection': .5
+ }
+
+
+ def add_plane(position, normal):
+ """
+ Create a dictionary representing a plane object.
+
+ Parameters:
+ position (list or numpy.ndarray):
+ A 3D point representing a point on the plane.
+ normal (list or numpy.ndarray):
+ A normalized 3D vector representing the normal of the plane.
+
+ Returns:
+ dict: A dictionary representing the plane object.
+ """
+ return {
+ 'type': 'plane',
+ 'position': np.array(position),
+ 'normal': np.array(normal),
+ 'color': lambda M: (color_plane0
+ if (int(M[0] * 2) % 2) == (int(M[2] * 2) % 2)
+ else color_plane1),
+ 'diffuse_c': .75,
+ 'specular_c': .5,
+ 'reflection': .25
+ }
+
+ scene = []
+
+ # List of objects.
+ color_plane0 = 1. * np.ones(3)
+ color_plane1 = 0. * np.ones(3)
+ scene.append(add_plane([0., -0.5, 0.], [0., 1., 0.]))
+ base_radius = 1 / np.sqrt(num_spheres) # Im więcej kul, tym mniejsze
+ base_distance = 4.5 / num_spheres
+
+ for i in range(num_spheres):
+ # Wyliczanie pozycji każdej kuli
+ x = (i - num_spheres // 2) * base_distance
+ y = 0.1
+ z = 1. + i * 0.5
+
+ # Dynamiczny kolor (gradient na podstawie indeksu)
+ color = np.array([i / num_spheres, (num_spheres - i) / num_spheres, 0.5])
+
+ # Dodanie kuli do sceny
+ scene.append(add_sphere([x, y, z], base_radius, color))
+
+ # Light position and color.
+ L = np.array([5., 5., -10.])
+ color_light = np.ones(3)
+
+ # Default light and material parameters.
+ AMBIENT = .05
+ DIFFUSE_C = 1.
+ SPECULAR_C = 1.
+ SPECULAR_K = 50
+
+ DEPTH_MAX = 5 # Maximum number of light reflections.
+ col = np.zeros(3) # Current color.
+ camera_origin = np.array([0., 0.35, -1.]) # Camera.
+ Q = np.array([0., 0., 0.]) # Camera pointing to.
+ img = np.zeros((IMAGE_HEIGHT, IMAGE_WIDTH, 3))
+
+ r = float(IMAGE_WIDTH) / IMAGE_HEIGHT
+ # Screen coordinates: x0, y0, x1, y1.
+ S = (-1., -1. / r + .25, 1., 1. / r + .25)
+
+ renderTime = time.time()
+ reflections = 0
+ rays = 0
+ initialRays = 0
+ # Loop through all pixels.
+ for i, x in enumerate(np.linspace(S[0], S[2], IMAGE_WIDTH)):
+ if i % 10 == 0:
+ print(round(i / float(IMAGE_WIDTH) * 100, 2), "%")
+ for j, y in enumerate(np.linspace(S[1], S[3], IMAGE_HEIGHT)):
+ col[:] = 0
+ Q[:2] = (x, y)
+ D = normalize(Q - camera_origin)
+ DEPTH = 0
+ rayO, rayD = camera_origin, D
+ REFLECTION = 1.
+ initialRays += 1
+ # Loop through initial and secondary rays.
+ while DEPTH < DEPTH_MAX:
+ traced = trace_ray(rayO, rayD)
+ rays += 1
+ if not traced:
+ break
+ reflections += 1
+ obj, M, N, col_ray = traced
+ # Reflection: create a new ray.
+ rayO, rayD = M + \
+ N * .0001, normalize(rayD - 2 * np.dot(rayD, N) * N)
+ DEPTH += 1
+ col += REFLECTION * col_ray
+ REFLECTION *= obj.get('reflection', 1.)
+ img[IMAGE_HEIGHT - j - 1, i, :] = np.clip(col, 0, 1)
+ renderTime = time.time() - renderTime
+
+ plt.imsave(output_file, img)
+ print(f"Image saved as {output_file}\n"
+ f"resolution: {IMAGE_WIDTH}x{IMAGE_HEIGHT}\n"
+ f"render time: {round(renderTime, 2)} s\n"
+ f"reflections: {reflections}\n"
+ f"rays (initial): {initialRays}\n"
+ f"rays (secondary): {rays - initialRays}\n"
+ f"rays (total): {rays}")
diff --git a/Programming/TRAK/requirements.txt b/Programming/TRAK/requirements.txt
new file mode 100644
index 00000000..3085b712
--- /dev/null
+++ b/Programming/TRAK/requirements.txt
@@ -0,0 +1,11 @@
+glfw
+numpy
+pyrr
+PyOpenGL
+matplotlib
+flake8
+black
+autopep8
+flake8-max-function-length
+pillow
+progressbar
\ No newline at end of file
diff --git a/Programming/TRAK/scenes/README.md b/Programming/TRAK/scenes/README.md
new file mode 100644
index 00000000..96272e79
--- /dev/null
+++ b/Programming/TRAK/scenes/README.md
@@ -0,0 +1,3 @@
+# Scene directory
+
+This is scene directory. Put scene files here.
\ No newline at end of file
diff --git a/Programming/TRAK/scenes/__init__.py b/Programming/TRAK/scenes/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/Programming/TRAK/scenes/cornell_box.py b/Programming/TRAK/scenes/cornell_box.py
new file mode 100644
index 00000000..404ac37e
--- /dev/null
+++ b/Programming/TRAK/scenes/cornell_box.py
@@ -0,0 +1,118 @@
+from sightpy import *
+
+# define materials to use
+def setup_scene(width=400, height=300, environment=None):
+
+ Sc = Scene(ambient_color=rgb(0.00, 0.00, 0.00))
+
+ angle = -0
+
+ Sc.add_Camera(
+ screen_width=width,
+ screen_height=height,
+ look_from=vec3(278, 278, 800),
+ look_at=vec3(278, 278, 0),
+ focal_distance=1.0,
+ field_of_view=40,
+ )
+
+ # define materials to use
+
+ green_diffuse = Diffuse(diff_color=rgb(0.12, 0.45, 0.15))
+ red_diffuse = Diffuse(diff_color=rgb(0.65, 0.05, 0.05))
+ white_diffuse = Diffuse(diff_color=rgb(0.73, 0.73, 0.73))
+ emissive_white = Emissive(color=rgb(15.0, 15.0, 15.0))
+ emissive_blue = Emissive(color=rgb(2.0, 2.0, 3.5))
+ blue_glass = Refractive(n=vec3(1.5 + 0.05e-8j, 1.5 + 0.02e-8j, 1.5 + 0.0j))
+
+ # this is the light
+ Sc.add(
+ Plane(
+ material=emissive_white,
+ center=vec3(213 + 130 / 2, 554, -227.0 - 105 / 2),
+ width=130.0,
+ height=105.0,
+ u_axis=vec3(1.0, 0.0, 0),
+ v_axis=vec3(0.0, 0, 1.0),
+ ),
+ importance_sampled=True,
+ )
+
+ Sc.add(
+ Plane(
+ material=white_diffuse,
+ center=vec3(555 / 2, 555 / 2, -555.0),
+ width=555.0,
+ height=555.0,
+ u_axis=vec3(0.0, 1.0, 0),
+ v_axis=vec3(1.0, 0, 0.0),
+ )
+ )
+
+ Sc.add(
+ Plane(
+ material=green_diffuse,
+ center=vec3(-0.0, 555 / 2, -555 / 2),
+ width=555.0,
+ height=555.0,
+ u_axis=vec3(0.0, 1.0, 0),
+ v_axis=vec3(0.0, 0, -1.0),
+ )
+ )
+
+ Sc.add(
+ Plane(
+ material=red_diffuse,
+ center=vec3(555.0, 555 / 2, -555 / 2),
+ width=555.0,
+ height=555.0,
+ u_axis=vec3(0.0, 1.0, 0),
+ v_axis=vec3(0.0, 0, -1.0),
+ )
+ )
+
+ Sc.add(
+ Plane(
+ material=white_diffuse,
+ center=vec3(555 / 2, 555, -555 / 2),
+ width=555.0,
+ height=555.0,
+ u_axis=vec3(1.0, 0.0, 0),
+ v_axis=vec3(0.0, 0, -1.0),
+ )
+ )
+
+ Sc.add(
+ Plane(
+ material=white_diffuse,
+ center=vec3(555 / 2, 0.0, -555 / 2),
+ width=555.0,
+ height=555.0,
+ u_axis=vec3(1.0, 0.0, 0),
+ v_axis=vec3(0.0, 0, -1.0),
+ )
+ )
+
+ cb = Cuboid(
+ material=white_diffuse,
+ center=vec3(182.5, 165, -285 - 160 / 2),
+ width=165,
+ height=165 * 2,
+ length=165,
+ shadow=False,
+ )
+ cb.rotate(θ=15, u=vec3(0, 1, 0))
+ Sc.add(cb)
+
+ Sc.add(
+ Sphere(
+ material=blue_glass,
+ center=vec3(370.5, 165 / 2, -65 - 185 / 2),
+ radius=165 / 2,
+ shadow=False,
+ max_ray_depth=3,
+ ),
+ importance_sampled=True,
+ )
+
+ return Sc
diff --git a/Programming/TRAK/scenes/soap_bubble.py b/Programming/TRAK/scenes/soap_bubble.py
new file mode 100644
index 00000000..7f022618
--- /dev/null
+++ b/Programming/TRAK/scenes/soap_bubble.py
@@ -0,0 +1,19 @@
+from sightpy import *
+
+# define materials to use
+def setup_scene(width=400, height=300, environment="lake.png"):
+ # Set Scene
+
+ Sc = Scene(ambient_color=rgb(0.01, 0.01, 0.01))
+
+ angle = -np.pi * 0.5
+ Sc.add_Camera(screen_height=height, screen_width=width,
+ look_from=vec3(4.0 * np.sin(angle), 0.00, 4.0 * np.cos(angle)),
+ look_at=vec3(0., 0.05, 0.0))
+
+ soap_bubble = ThinFilmInterference(thickness=330, noise=60.)
+ Sc.add(Sphere(material=soap_bubble, center=vec3(1., 0.0, 1.5), radius=1.7, shadow=False, max_ray_depth=5))
+
+ Sc.add_Background(environment, blur=10.)
+
+ return Sc
diff --git a/Programming/TRAK/scenes/three_spheres.py b/Programming/TRAK/scenes/three_spheres.py
new file mode 100644
index 00000000..830113a7
--- /dev/null
+++ b/Programming/TRAK/scenes/three_spheres.py
@@ -0,0 +1,35 @@
+from sightpy import *
+
+# define materials to use
+def setup_scene(width=400, height=300, environment="miramar.jpeg"):
+
+ blue_glass = Refractive(n=vec3(1.5 + 4e-8j, 1.5 + 4e-8j, 1.5 + 0.j)) # n = index of refraction
+ green_glass = Refractive(n=vec3(1.5 + 4e-8j, 1.5 + 0.j, 1.5 + 4e-8j))
+ red_glass = Refractive(n=vec3(1.5 + 0.j, 1.5 + 5e-8j, 1.5 + 5e-8j))
+
+ floor = Glossy(diff_color=image("checkered_floor.png", repeat=80.), n=vec3(1.2 + 0.3j, 1.2 + 0.3j, 1.1 + 0.3j),
+ roughness=0.2, spec_coeff=0.3, diff_coeff=0.9)
+
+ # Set Scene
+
+ Sc = Scene(ambient_color=rgb(0.05, 0.05, 0.05))
+
+ angle = np.pi / 2 * 0.3
+ Sc.add_Camera(look_from=vec3(2.5 * np.sin(angle), 0.25, 2.5 * np.cos(angle) - 1.5),
+ look_at=vec3(0., 0.25, -1.5),
+ screen_width=width,
+ screen_height=height)
+
+ Sc.add_DirectionalLight(Ldir=vec3(0.52, 0.45, -0.5), color=rgb(0.15, 0.15, 0.15))
+
+ Sc.add(Sphere(material=blue_glass, center=vec3(-1.2, 0.0, -1.5), radius=.5, shadow=False, max_ray_depth=3))
+ Sc.add(Sphere(material=green_glass, center=vec3(0., 0.0, -1.5), radius=.5, shadow=False, max_ray_depth=3))
+ Sc.add(Sphere(material=red_glass, center=vec3(1.2, 0.0, -1.5), radius=.5, shadow=False, max_ray_depth=3))
+
+ Sc.add(Plane(material=floor, center=vec3(0, -0.5, -3.0), width=120.0, height=120.0, u_axis=vec3(1.0, 0, 0),
+ v_axis=vec3(0, 0, -1.0), max_ray_depth=3))
+
+ # see sightpy/backgrounds
+ Sc.add_Background(environment)
+
+ return Sc
diff --git a/Programming/TRAK/scenes/transparent_cuboid.py b/Programming/TRAK/scenes/transparent_cuboid.py
new file mode 100644
index 00000000..475cb523
--- /dev/null
+++ b/Programming/TRAK/scenes/transparent_cuboid.py
@@ -0,0 +1,27 @@
+from sightpy import *
+
+# define materials to use
+def setup_scene(width=400, height=300, environment="stormydays.png"):
+ floor = Glossy(diff_color=image("checkered_floor.png", repeat=2.), roughness=0.2, spec_coeff=0.3, diff_coeff=0.7,
+ n=vec3(2.2, 2.2, 2.2)) # n = index of refraction
+ green_glass = Refractive(n=vec3(1.5 + 4e-8j, 1.5 + 0.j, 1.5 + 4e-8j))
+
+ Sc = Scene()
+ Sc.add_Camera(look_from=vec3(0., 0.25, 1.), look_at=vec3(0., 0.25, -3.),
+ screen_width=width,
+ screen_height=height)
+
+ Sc.add_DirectionalLight(Ldir=vec3(0.0, 0.5, 0.5), color=rgb(0.5, 0.5, 0.5))
+
+ Sc.add(Plane(material=floor, center=vec3(0, -0.5, -3.0), width=6.0, height=6.0, u_axis=vec3(1.0, 0, 0),
+ v_axis=vec3(0, 0, -1.0), max_ray_depth=5))
+
+ cb = Cuboid(material=green_glass, center=vec3(0.00, 0.0001, -0.8), width=0.9, height=1.0, length=0.4, shadow=False,
+ max_ray_depth=5)
+ cb.rotate(θ=30, u=vec3(0, 1, 0))
+ Sc.add(cb)
+
+ # see sightpy/backgrounds
+ Sc.add_Background(environment)
+
+ return Sc
\ No newline at end of file
diff --git a/Programming/TRAK/scenes/two_spheres.py b/Programming/TRAK/scenes/two_spheres.py
new file mode 100644
index 00000000..846bea3c
--- /dev/null
+++ b/Programming/TRAK/scenes/two_spheres.py
@@ -0,0 +1,38 @@
+from sightpy import *
+
+# define materials to use
+def setup_scene(width=400, height=300, environment="stormydays.png"):
+ gold_metal = Glossy(diff_color = rgb(1., .572, .184), n = vec3(0.15+3.58j, 0.4+2.37j, 1.54+1.91j), roughness = 0.0, spec_coeff = 0.2, diff_coeff= 0.8) # n = index of refraction
+ bluish_metal = Glossy(diff_color = rgb(0.0, 0, 0.1), n = vec3(1.3+1.91j, 1.3+1.91j, 1.4+2.91j), roughness = 0.2,spec_coeff = 0.5, diff_coeff= 0.3)
+
+ floor = Glossy(diff_color = image("checkered_floor.png", repeat = 80.),
+ n = vec3(1.2+ 0.3j, 1.2+ 0.3j, 1.1+ 0.3j), roughness = 0.2, spec_coeff = 0.3, diff_coeff= 0.9 )
+
+
+
+
+
+ # Set Scene
+ Sc = Scene(ambient_color = rgb(0.05, 0.05, 0.05))
+
+
+ angle = -np.pi/2 * 0.3
+ Sc.add_Camera(look_from = vec3(2.5*np.sin(angle), 0.25, 2.5*np.cos(angle) -1.5 ),
+ look_at = vec3(0., 0.25, -3.),
+ screen_width = width ,
+ screen_height = height)
+
+
+
+ Sc.add_DirectionalLight(Ldir = vec3(0.52,0.45, -0.5), color = rgb(0.15, 0.15, 0.15))
+
+
+ Sc.add(Sphere(material = gold_metal, center = vec3(-.75, .1, -3.),radius = .6, max_ray_depth = 3))
+ Sc.add(Sphere(material = bluish_metal, center = vec3(1.25, .1, -3.), radius = .6, max_ray_depth = 3))
+
+ Sc.add(Plane(material = floor, center = vec3(0, -0.5, -3.0), width = 120.0,height = 120.0, u_axis = vec3(1.0, 0, 0), v_axis = vec3(0, 0, -1.0), max_ray_depth = 3))
+
+ #see sightpy/backgrounds
+ Sc.add_Background(environment)
+
+ return Sc
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/__init__.py b/Programming/TRAK/sightpy/__init__.py
new file mode 100644
index 00000000..19c250df
--- /dev/null
+++ b/Programming/TRAK/sightpy/__init__.py
@@ -0,0 +1,17 @@
+from .utils.constants import *
+from .utils.vector3 import *
+from .utils.colour_functions import *
+from .utils.image_functions import *
+
+from .ray import *
+from .scene import *
+from .geometry import *
+from .lights import *
+from .materials import *
+from .textures.texture import *
+from .animation import *
+
+
+
+
+
diff --git a/Programming/TRAK/sightpy/animation.py b/Programming/TRAK/sightpy/animation.py
new file mode 100644
index 00000000..30411a9b
--- /dev/null
+++ b/Programming/TRAK/sightpy/animation.py
@@ -0,0 +1,54 @@
+from .scene import Scene
+import numpy as np
+from pathlib import Path
+
+
+def create_animation(scene,samples_per_pixel, fps, start_time, final_time, update_scene, name):
+
+ """
+ this function render a list of frames and saves them in ./frames folder. You can make an animation the using ffmpeg running
+ from the command prompt:
+ """
+ #ffmpeg -r 60 -f image2 -s 854x480 -i your_image_%d.png -vcodec libx264 -crf 1 -pix_fmt yuv420p your_video.mp4
+ #fps #resoluion #crf = quality (less is better)
+
+
+ number_of_frames = int(fps*(final_time - start_time))
+ dt = (final_time - start_time)/number_of_frames
+ t = start_time
+
+ try:
+ Path("./frames").mkdir()
+
+ except FileExistsError:
+ pass
+
+
+ for i in range(0,number_of_frames):
+ update_scene(scene, t)
+ img = scene.render(samples_per_pixel)
+ t += dt
+ img.save("frames/" + name + "_" + str(i) + ".png")
+
+
+
+
+def create_animation_using_opencv(scene, samples_per_pixel , fps, start_time, final_time, update_scene, name):
+
+ import cv2
+ number_of_frames = int(fps*(final_time - start_time))
+ dt = (final_time - start_time)/number_of_frames
+ t = start_time
+
+
+ videodims = (scene.camera.screen_width, scene.camera.screen_height)
+ fourcc = cv2.VideoWriter_fourcc('M', 'J', 'P', 'G')
+ video = cv2.VideoWriter(name,fourcc, fps,videodims)
+
+ for i in range(0,number_of_frames):
+ update_scene(scene, t)
+ frame = scene.render(samples_per_pixel)
+ video.write(cv2.cvtColor(np.array(frame), cv2.COLOR_RGB2BGR))
+ t += dt
+
+ video.release()
diff --git a/Programming/TRAK/sightpy/backgrounds/__init__.py b/Programming/TRAK/sightpy/backgrounds/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/Programming/TRAK/sightpy/backgrounds/lake.png b/Programming/TRAK/sightpy/backgrounds/lake.png
new file mode 100644
index 00000000..f7541a2c
Binary files /dev/null and b/Programming/TRAK/sightpy/backgrounds/lake.png differ
diff --git a/Programming/TRAK/sightpy/backgrounds/lightmaps/lake.png b/Programming/TRAK/sightpy/backgrounds/lightmaps/lake.png
new file mode 100644
index 00000000..349a96a6
Binary files /dev/null and b/Programming/TRAK/sightpy/backgrounds/lightmaps/lake.png differ
diff --git a/Programming/TRAK/sightpy/backgrounds/miramar.jpeg b/Programming/TRAK/sightpy/backgrounds/miramar.jpeg
new file mode 100644
index 00000000..1a720c20
Binary files /dev/null and b/Programming/TRAK/sightpy/backgrounds/miramar.jpeg differ
diff --git a/Programming/TRAK/sightpy/backgrounds/panorama.py b/Programming/TRAK/sightpy/backgrounds/panorama.py
new file mode 100644
index 00000000..3eb9867f
--- /dev/null
+++ b/Programming/TRAK/sightpy/backgrounds/panorama.py
@@ -0,0 +1,18 @@
+from ..geometry import Sphere_Collider, Primitive
+from ..materials import Material
+from ..utils.vector3 import vec3
+from ..utils.constants import SKYBOX_DISTANCE
+from ..utils.image_functions import load_image, load_image_as_linear_sRGB
+from .util.blur_background import blur_skybox
+from .skybox import SkyBox_Material
+
+class Panorama(Primitive):
+ def __init__(self, panorama, center = vec3(0.,0.,0.), light_intensity = 0.0, blur = 0.0):
+ super().__init__(center, SkyBox_Material(panorama, light_intensity, blur), shadow = False)
+ l = SKYBOX_DISTANCE
+ self.light_intensity = light_intensity
+ self.collider_list += [Sphere_Collider(assigned_primitive = self, center = center , radius = SKYBOX_DISTANCE)]
+
+
+ def get_uv(self, hit):
+ return hit.collider.get_uv(hit)
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/backgrounds/skybox.py b/Programming/TRAK/sightpy/backgrounds/skybox.py
new file mode 100644
index 00000000..93802d37
--- /dev/null
+++ b/Programming/TRAK/sightpy/backgrounds/skybox.py
@@ -0,0 +1,57 @@
+from ..geometry import Cuboid_Collider, Primitive
+from ..materials import Material
+from ..utils.vector3 import vec3
+from ..utils.constants import SKYBOX_DISTANCE
+from ..utils.image_functions import load_image, load_image_as_linear_sRGB
+from .util.blur_background import blur_skybox
+
+class SkyBox(Primitive):
+ def __init__(self, cubemap, center = vec3(0.,0.,0.), light_intensity = 0.0, blur = 0.0):
+ super().__init__(center, SkyBox_Material(cubemap, light_intensity, blur), shadow = False)
+ l = SKYBOX_DISTANCE
+ self.light_intensity = light_intensity
+ #BOTTOM
+ self.collider_list += [Cuboid_Collider(assigned_primitive = self, center = center, width = 2*l, height =2*l ,length =2*l )]
+
+
+ def get_uv(self, hit):
+ u,v = hit.collider.get_uv(hit)
+ u,v = u/4,v/3
+ return u,v
+
+
+class SkyBox_Material(Material):
+ def __init__(self, cubemap, light_intensity, blur):
+ self.texture = load_image_as_linear_sRGB("sightpy/backgrounds/" + cubemap)
+
+ if light_intensity != 0.0:
+ self.lightmap = load_image("sightpy/backgrounds/lightmaps/" + cubemap)
+
+ if blur != 0.0:
+ self.blur_image = blur_skybox(load_image("sightpy/backgrounds/" + cubemap), blur, cubemap)
+
+ self.blur = blur
+ self.light_intensity = light_intensity
+ self.repeat = 1.0
+
+ def get_texture_color(self, hit, ray):
+ u,v = hit.get_uv()
+
+ if (self.blur != 0.0) :
+ im = self.blur_image[-((v * self.blur_image.shape[0]*self.repeat ).astype(int)% self.blur_image.shape[0]) , (u * self.blur_image.shape[1]*self.repeat).astype(int) % self.blur_image.shape[1] ].T
+ else:
+ im = self.texture[-((v * self.texture.shape[0]*self.repeat ).astype(int)% self.texture.shape[0]) , (u * self.texture.shape[1]*self.repeat).astype(int) % self.texture.shape[1] ].T
+
+ if (ray.depth != 0) and (self.light_intensity != 0.0):
+ ls = self.lightmap[-((v * self.texture.shape[0]*self.repeat ).astype(int)% self.texture.shape[0]) , (u * self.texture.shape[1]*self.repeat).astype(int) % self.texture.shape[1] ].T
+ color = vec3(im[0] + self.light_intensity * ls[0], im[1] + self.light_intensity * ls[1], im[2] + self.light_intensity * ls[2])
+
+ else:
+ color = vec3(im[0] , im[1] , im[2] )
+ return color
+
+
+ def get_color(self, scene, ray, hit):
+ hit.point = (ray.origin + ray.dir * hit.distance)
+ return hit.material.get_texture_color(hit,ray)
+
diff --git a/Programming/TRAK/sightpy/backgrounds/stormydays.png b/Programming/TRAK/sightpy/backgrounds/stormydays.png
new file mode 100644
index 00000000..0e76d1a1
Binary files /dev/null and b/Programming/TRAK/sightpy/backgrounds/stormydays.png differ
diff --git a/Programming/TRAK/sightpy/backgrounds/util/__init__.py b/Programming/TRAK/sightpy/backgrounds/util/__init__.py
new file mode 100644
index 00000000..e69de29b
diff --git a/Programming/TRAK/sightpy/backgrounds/util/blur_background.py b/Programming/TRAK/sightpy/backgrounds/util/blur_background.py
new file mode 100644
index 00000000..02f6ca5d
--- /dev/null
+++ b/Programming/TRAK/sightpy/backgrounds/util/blur_background.py
@@ -0,0 +1,158 @@
+from PIL import Image, ImageFilter
+import numpy as np
+from ...utils.colour_functions import sRGB_to_sRGB_linear
+
+
+
+def to_image(arr):
+ img = [Image.fromarray((255 * arr[:,:,i]).astype(np.uint8), "L") for i in range(0,3)]
+ return Image.merge("RGB", img)
+
+def to_array(img):
+ return np.asarray(img)/256.
+
+
+
+
+
+def blur_skybox(img_array, blur, cubemap):
+
+ print("blurring " + cubemap)
+
+ N = int(img_array.shape[0]/3)
+
+ left = img_array[ 1*N:2*N, 0*N:1*N]
+ front = img_array[N:2*N, N:2*N]
+ right = img_array[ N:2*N, 2*N:3*N]
+ back = img_array[N:2*N, 3*N:4*N]
+ top = img_array[0:1*N, 1*N:2*N]
+ bottom = img_array[2*N:3*N, 1*N:2*N]
+
+
+ cubelist = [[None,top,None, None],
+ [left,front,right, back],
+ [None,bottom,None, None]]
+
+
+
+ back_blur = np.zeros((N*3, N*3,3))
+
+
+ back_blur[ 1*N:2*N, 0*N:1*N] = cubelist[1][0-2] #left
+ back_blur[N:2*N, N:2*N] = cubelist[1][1-2] #front
+ back_blur[ N:2*N, 2*N:3*N] = cubelist[1][2-2] # right
+ back_blur[2*N:3*N, 1*N:2*N] = np.rot90(cubelist[2][1] , k=2) #bottom
+ back_blur[0:1*N, 1*N:2*N] = np.rot90(cubelist[0][1], k=2) #top
+
+
+ back_blur = to_image(back_blur)
+ back_blur = (back_blur).filter(ImageFilter.GaussianBlur(radius=blur))
+
+ back_blur = to_array(back_blur)
+
+ back_blur = back_blur[N:2*N, N:2*N]
+
+
+ top_blur = np.zeros((N*3, N*3,3))
+ top_blur[ 1*N:2*N, 0*N:1*N] = np.rot90(cubelist[1][0], k=-1) #left
+ top_blur[N:2*N, N:2*N] = cubelist[1-1][1] #front
+ top_blur[ N:2*N, 2*N:3*N] = np.rot90(cubelist[1][2], k=1) # right
+ top_blur[2*N:3*N, 1*N:2*N] = cubelist[1][1] #bottom
+ top_blur[0:1*N, 1*N:2*N] = np.rot90(cubelist[1][3], k=2) #top
+ top_blur = to_image(top_blur)
+ top_blur = (top_blur).filter(ImageFilter.GaussianBlur(radius=blur))
+
+ top_blur = to_array(top_blur)
+
+ top_blur = top_blur[N:2*N, N:2*N]
+
+
+
+
+ bottom_blur = np.zeros((N*3, N*3,3))
+
+
+ bottom_blur[ 1*N:2*N, 0*N:1*N] = np.rot90(cubelist[1][0], k=1) #left
+ bottom_blur[N:2*N, N:2*N] = cubelist[1+1][1] #front
+ bottom_blur[ N:2*N, 2*N:3*N] = np.rot90(cubelist[1][2], k=-1) # right
+ bottom_blur[2*N:3*N, 1*N:2*N] = np.rot90(cubelist[1][3], k=2) #bottom
+ bottom_blur[0:1*N, 1*N:2*N] = cubelist[1][1] #top
+
+ bottom_blur = to_image(bottom_blur)
+ bottom_blur = (bottom_blur).filter(ImageFilter.GaussianBlur(radius=blur))
+
+ bottom_blur = to_array(bottom_blur)
+
+ bottom_blur = bottom_blur[N:2*N, N:2*N]
+
+
+
+ right_blur = np.zeros((N*3, N*3,3))
+
+ right_blur[ 1*N:2*N, 0*N:1*N] = cubelist[1][0+1] #left
+ right_blur[N:2*N, N:2*N] = cubelist[1][1+1] #front
+ right_blur[ N:2*N, 2*N:3*N] = cubelist[1][2+1] # right
+ right_blur[2*N:3*N, 1*N:2*N] = np.rot90(cubelist[2][1] ) #bottom
+ right_blur[0:1*N, 1*N:2*N] = np.rot90(cubelist[0][1], k=-1) #top
+
+ right_blur = to_image(right_blur).filter(ImageFilter.GaussianBlur(radius=blur))
+
+
+ right_blur = to_array(right_blur)
+
+ right_blur = right_blur[N:2*N, N:2*N]
+
+
+
+ front_blur = np.zeros((N*3, N*3,3))
+
+
+ front_blur [ 1*N:2*N, 0*N:1*N] = cubelist[1][0] #left
+ front_blur [N:2*N, N:2*N] = cubelist[1][1] #front
+ front_blur [ N:2*N, 2*N:3*N] = cubelist[1][2] # right
+ front_blur [2*N:3*N, 1*N:2*N] = cubelist[2][1] #bottom
+ front_blur [0:1*N, 1*N:2*N] = cubelist[0][1] #top
+
+ front_blur = to_image(front_blur)
+
+ front_blur = (front_blur).filter(ImageFilter.GaussianBlur(radius=blur))
+
+ front_blur = to_array(front_blur)
+
+ front_blur = front_blur[N:2*N, N:2*N]
+
+
+
+
+ left_blur = np.zeros((N*3, N*3,3))
+
+ left_blur[ 1*N:2*N, 0*N:1*N] = cubelist[1][0-1] #left
+ left_blur[N:2*N, N:2*N] = cubelist[1][1-1] #front
+ left_blur[ N:2*N, 2*N:3*N] = cubelist[1][2-1]
+ left_blur[2*N:3*N, 1*N:2*N] = np.rot90(cubelist[2][1], k=-1 ) #bottom
+ left_blur[0:1*N, 1*N:2*N] = np.rot90(cubelist[0][1], k=1) #top
+
+
+ left_blur = to_image(left_blur).filter(ImageFilter.GaussianBlur(radius=blur))
+
+
+ left_blur = to_array(left_blur)
+
+ left_blur = left_blur[N:2*N, N:2*N]
+
+
+
+
+
+
+ skybox_blurred = np.zeros((N*3, N*4,3))
+
+ skybox_blurred[ 1*N:2*N, 0*N:1*N] = left_blur
+ skybox_blurred[N:2*N, N:2*N] = front_blur
+ skybox_blurred[ N:2*N, 2*N:3*N] = right_blur
+ skybox_blurred[N:2*N, 3*N:4*N] = back_blur
+ skybox_blurred[0:1*N, 1*N:2*N] = top_blur
+ skybox_blurred[2*N:3*N, 1*N:2*N] = bottom_blur
+
+ return sRGB_to_sRGB_linear(skybox_blurred)
+
diff --git a/Programming/TRAK/sightpy/camera.py b/Programming/TRAK/sightpy/camera.py
new file mode 100644
index 00000000..0f29133a
--- /dev/null
+++ b/Programming/TRAK/sightpy/camera.py
@@ -0,0 +1,52 @@
+from .utils.vector3 import vec3, rgb
+from .utils.random import random_in_unit_disk
+import numpy as np
+from .ray import Ray
+
+class Camera():
+ def __init__(self, look_from, look_at, screen_width = 400 ,screen_height = 300, field_of_view = 90., aperture = 0., focal_distance = 1.):
+ self.screen_width = screen_width
+ self.screen_height = screen_height
+ self.aspect_ratio = float(screen_width) / screen_height
+
+ self.look_from = look_from
+ self.look_at = look_at
+ self.camera_width = np.tan(field_of_view * np.pi/180 /2.)*2.
+ self.camera_height = self.camera_width/self.aspect_ratio
+
+ #camera reference basis in world coordinates
+ self.cameraFwd = (look_at - look_from).normalize()
+ self.cameraRight = (self.cameraFwd.cross(vec3(0.,1.,0.))).normalize()
+ self.cameraUp = self.cameraRight.cross(self.cameraFwd)
+
+
+
+ #if you use a lens_radius >= 0.0 make sure that samples_per_pixel is a large number. Otherwise you'll get a lot of noise
+ self.lens_radius = aperture / 2.
+ self.focal_distance = focal_distance
+
+
+
+ # Pixels coordinates in camera basis:
+ self.x = np.linspace(-self.camera_width/2., self.camera_width/2., self.screen_width)
+ self.y = np.linspace(self.camera_height/2., -self.camera_height/2., self.screen_height)
+
+ # we are going to cast a total of screen_width * screen_height * samples_per_pixel rays
+ # xx,yy store the origin of each ray in a 3d array where the first and second dimension are the x,y coordinates of each pixel
+ # and the third dimension is the sample index of each pixel
+ xx,yy = np.meshgrid(self.x,self.y)
+ self.x = xx.flatten()
+ self.y = yy.flatten()
+
+ def get_ray(self,n): # n = index of refraction of scene main medium (for air n = 1.)
+
+ # in each pixel, take a random position to avoid aliasing.
+ x = self.x + (np.random.rand(len(self.x )) - 0.5)*self.camera_width /(self.screen_width)
+ y = self.y + (np.random.rand(len(self.y )) - 0.5)*self.camera_height /(self.screen_height)
+
+ # set ray direction in world space:
+ rx, ry = random_in_unit_disk(x.shape[0])
+ ray_origin = self.look_from + self.cameraRight *rx* self.lens_radius + self.cameraUp *ry* self.lens_radius
+ ray_dir = (self.look_from + self.cameraUp*y*self.focal_distance + self.cameraRight*x*self.focal_distance + self.cameraFwd*self.focal_distance - ray_origin ).normalize()
+ return Ray(origin=ray_origin, dir=ray_dir, depth=0, n=n, reflections = 0, transmissions = 0, diffuse_reflections = 0)
+
diff --git a/Programming/TRAK/sightpy/geometry/__init__.py b/Programming/TRAK/sightpy/geometry/__init__.py
new file mode 100644
index 00000000..cdeaa1bd
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/__init__.py
@@ -0,0 +1,13 @@
+from .primitive import *
+from .collider import *
+from .sphere import *
+from .plane import *
+from .triangle import *
+from .triangle_mesh import *
+from .cuboid import *
+
+
+
+
+
+
diff --git a/Programming/TRAK/sightpy/geometry/collider.py b/Programming/TRAK/sightpy/geometry/collider.py
new file mode 100644
index 00000000..97a500fa
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/collider.py
@@ -0,0 +1,17 @@
+import numpy as np
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+from abc import abstractmethod
+
+class Collider:
+ def __init__(self,assigned_primitive, center):
+ self.assigned_primitive = assigned_primitive
+ self.center = center
+
+ @abstractmethod
+ def intersect(self, O, D):
+ pass
+
+ @abstractmethod
+ def get_Normal(self, hit):
+ pass
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/geometry/cuboid.py b/Programming/TRAK/sightpy/geometry/cuboid.py
new file mode 100644
index 00000000..086b5231
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/cuboid.py
@@ -0,0 +1,145 @@
+import numpy as np
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+from ..geometry import Primitive, Collider
+
+class Cuboid(Primitive):
+ def __init__(self,center, material, width,height, length,max_ray_depth = 5, shadow = True):
+ super().__init__(center, material, max_ray_depth, shadow = shadow)
+ self.width = width
+ self.height = height
+ self.length = length
+ self.bounded_sphere_radius = np.sqrt((self.width/2)**2 + (self.height/2)**2 + (self.length/2)**2)
+
+ self.collider_list += [Cuboid_Collider(assigned_primitive = self, center = center, width = width, height =height ,length =length )]
+
+
+ def get_uv(self, hit):
+ u,v = hit.collider.get_uv(hit)
+ u,v = u/4,v/3
+ return u,v
+
+
+"""
+ This was the old approach, but remplaced by a Box collider that is more efficient
+ #we model a cuboid as six planes
+
+
+ #BOTTOM #BOTTOM
+ self.collider_list += [Plane_Collider(assigned_primitive = self, center = center + vec3(0.0,-h, 0.0), u_axis = vec3(1.0, 0.0, 0.0), v_axis = vec3(0.0, 0.0, 1.0), w = w, h = l, uv_shift = (1,0))]
+ #TOP #TOP
+ self.collider_list += [Plane_Collider(assigned_primitive = self, center = center + vec3(0.0,h, 0.0), u_axis = vec3(1.0, 0.0, 0.0), v_axis = vec3(0.0, 0.0, -1.0), w = w, h = l, uv_shift= (1,2))]
+ #RIGHT #RIGHT
+ self.collider_list += [Plane_Collider(assigned_primitive = self, center = center + vec3(w,0.0, 0.0), u_axis = vec3(0.0, 0.0, -1.0), v_axis = vec3(0.0, 1.0, 0.0), w = l, h = h, uv_shift= (2,1))]
+ #LEFT #LEFT
+ self.collider_list += [Plane_Collider(assigned_primitive = self, center = center + vec3(-w,0.0, 0.0), u_axis = vec3(0.0, 0.0, 1.0), v_axis = vec3(0.0, 1.0, 0.0), w = l, h = h, uv_shift= (0,1))]
+ #FRONT #FRONT
+ self.collider_list += [Plane_Collider(assigned_primitive = self, center = center + vec3(0,0, l), u_axis = vec3(1.0, 0.0, 0.0), v_axis = vec3(0.0, 1.0, 0.0), w = w, h = h, uv_shift= (1,1))]
+ #BACK #BACK
+ self.collider_list += [Plane_Collider(assigned_primitive = self, center = center + vec3(0,0, -l), u_axis = vec3(-1.0, 0.0, 0.0), v_axis = vec3(0.0, 1.0, 0.0), w = w, h = h, uv_shift= (3,1))]
+"""
+
+
+class Cuboid_Collider(Collider):
+ def __init__(self, width, height,length,**kwargs):
+ super().__init__(**kwargs)
+
+ self.lb = self.center - vec3(width/2, height/2, length/2)
+ self.rt = self.center + vec3(width/2, height/2, length/2)
+
+ self.lb_local_basis = self.lb
+ self.rt_local_basis = self.rt
+
+ self.width = width
+ self.height = height
+ self.length = length
+
+ # basis vectors
+ self.ax_w = vec3(1.,0.,0.)
+ self.ax_h = vec3(0.,1.,0.)
+ self.ax_l = vec3(0.,0.,1.)
+
+ self.inverse_basis_matrix = np.array([[self.ax_w.x, self.ax_h.x, self.ax_l.x],
+ [self.ax_w.y, self.ax_h.y, self.ax_l.y],
+ [self.ax_w.z, self.ax_h.z, self.ax_l.z]])
+
+ self.basis_matrix = self.inverse_basis_matrix.T
+
+
+ def rotate(self,M, center):
+ self.ax_w = self.ax_w.matmul(M)
+ self.ax_h = self.ax_h.matmul(M)
+ self.ax_l = self.ax_l.matmul(M)
+
+ self.inverse_basis_matrix = np.array([[self.ax_w.x, self.ax_h.x, self.ax_l.x],
+ [self.ax_w.y, self.ax_h.y, self.ax_l.y],
+ [self.ax_w.z, self.ax_h.z, self.ax_l.z]])
+
+ self.basis_matrix = self.inverse_basis_matrix.T
+
+ self.lb = center + (self.lb-center).matmul(M)
+ self.rt = center + (self.rt-center).matmul(M)
+
+ self.lb_local_basis = self.lb.matmul(self.basis_matrix)
+ self.rt_local_basis = self.rt.matmul(self.basis_matrix)
+
+ def intersect(self, O, D):
+
+
+ O_local_basis = O.matmul(self.basis_matrix)
+ D_local_basis = D.matmul(self.basis_matrix)
+
+ dirfrac = 1.0 / D_local_basis
+
+ # lb is the corner of AABB with minimal coordinates - left bottom, rt is maximal corner
+ t1 = (self.lb_local_basis.x - O_local_basis.x)*dirfrac.x;
+ t2 = (self.rt_local_basis.x - O_local_basis.x)*dirfrac.x;
+ t3 = (self.lb_local_basis.y - O_local_basis.y)*dirfrac.y;
+ t4 = (self.rt_local_basis.y - O_local_basis.y)*dirfrac.y;
+ t5 = (self.lb_local_basis.z - O_local_basis.z)*dirfrac.z;
+ t6 = (self.rt_local_basis.z - O_local_basis.z)*dirfrac.z;
+
+ tmin = np.maximum(np.maximum(np.minimum(t1, t2), np.minimum(t3, t4)), np.minimum(t5, t6))
+ tmax = np.minimum(np.minimum(np.maximum(t1, t2), np.maximum(t3, t4)), np.maximum(t5, t6))
+
+ # if tmax < 0, ray (line) is intersecting AABB, but the whole AABB is behind us
+ # if tmin > tmax, ray doesn't intersect AAB
+ mask1 = (tmax < 0) | (tmin > tmax)
+
+ # if tmin < 0 then the ray origin is inside of the AABB and tmin is behind the start of the ray so tmax is the first intersection
+ mask2 = tmin < 0
+ return np.select([mask1,mask2,True] , [FARAWAY , [tmax, np.tile(UPDOWN, tmin.shape)] , [tmin, np.tile(UPWARDS, tmin.shape)]])
+
+
+ def get_Normal(self, hit):
+
+ P = (hit.point-self.center).matmul(self.basis_matrix)
+ absP = vec3(1./self.width, 1./self.height, 1./self.length)*np.abs(P)
+ Pmax = np.maximum(np.maximum(absP.x, absP.y), absP.z)
+ P.x = np.where(Pmax == absP.x, np.sign(P.x), 0.)
+ P.y = np.where(Pmax == absP.y, np.sign(P.y), 0.)
+ P.z = np.where(Pmax == absP.z, np.sign(P.z), 0.)
+
+ return P.matmul(self.inverse_basis_matrix)
+
+ def get_uv(self, hit):
+ hit.N = self.get_Normal(hit)
+ M_C = hit.point - self.center
+
+ BOTTOM = (hit.N == vec3(0.,-1.,0.))
+ TOP = (hit.N == vec3(0., 1.,0.))
+ RIGHT = (hit.N == vec3(1.,0.,0.))
+ LEFT = (hit.N == vec3(-1.,0.,0.) )
+ FRONT = (hit.N == vec3(0.,0.,1.))
+ BACK = (hit.N == vec3(0.,0.,-1.))
+
+ #0.985 to avoid corners
+ u = np.select([BOTTOM , TOP, RIGHT, LEFT , FRONT , BACK], [((self.ax_w.dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 1), ((self.ax_w.dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 1), ((self.ax_l.dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 2), (((self.ax_l*-1).dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 0), (((self.ax_w*-1).dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 3), (( self.ax_w.dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 1)])
+ v = np.select([BOTTOM , TOP, RIGHT, LEFT , FRONT , BACK], [(((self.ax_l*-1).dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 0), ((self.ax_l.dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 2), ((self.ax_h.dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 1), (((self.ax_h).dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 1), (((self.ax_h).dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 1), (( self.ax_h.dot(M_C)/self.width*2 *0.985 + 1 ) /2 + 1)])
+ return u,v
+
+
+
+
+
+
diff --git a/Programming/TRAK/sightpy/geometry/plane.py b/Programming/TRAK/sightpy/geometry/plane.py
new file mode 100644
index 00000000..9af46b1c
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/plane.py
@@ -0,0 +1,81 @@
+import numpy as np
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+from ..geometry import Primitive, Collider
+
+
+class Plane(Primitive):
+ def __init__(self,center, material, width,height, u_axis, v_axis, max_ray_depth = 5, shadow = True):
+ super().__init__(center, material, max_ray_depth,shadow = shadow)
+ self.collider_list += [Plane_Collider(assigned_primitive = self, center = center, u_axis = u_axis, v_axis = v_axis, w= width/2, h=height/2)]
+ self.width = width
+ self.height = height
+ self.bounded_sphere_radius = np.sqrt((width/2)**2 + (height/2)**2)
+
+ def get_uv(self, hit):
+ return hit.collider.get_uv(hit)
+
+
+class Plane_Collider(Collider):
+ def __init__(self, u_axis, v_axis, w, h, uv_shift = (0.,0.),**kwargs):
+ super().__init__(**kwargs)
+ self.normal = u_axis.cross(v_axis).normalize()
+
+
+ self.w = w
+ self.h = h
+ self.u_axis = u_axis
+ self.v_axis = v_axis
+ self.uv_shift = uv_shift
+ self.inverse_basis_matrix = np.array([[self.u_axis.x, self.v_axis.x, self.normal.x],
+ [self.u_axis.y, self.v_axis.y, self.normal.y],
+ [self.u_axis.z, self.v_axis.z, self.normal.z]])
+ self.basis_matrix = self.inverse_basis_matrix.T
+
+
+
+
+ def intersect(self, O, D):
+ N = self.normal
+
+ NdotD = N.dot(D)
+ NdotD = np.where(NdotD == 0., NdotD + 0.0001, NdotD) #avoid zero division
+
+ NdotC_O = N.dot(self.center - O)
+ d = D * NdotC_O / NdotD
+ M = O + d # intersection point
+ dis = d.length()
+
+ M_C = M - self.center
+
+ #plane basis coordinates
+ u = self.u_axis.dot(M_C)
+ v = self.v_axis.dot(M_C)
+
+
+ hit_inside = (np.abs(u) <= self.w) & (np.abs(v) <= self.h) & (NdotC_O * NdotD > 0)
+ hit_UPWARDS = (NdotD < 0)
+ hit_UPDOWN = np.logical_not(hit_UPWARDS)
+
+
+ pred1 = hit_inside & hit_UPWARDS
+ pred2 = hit_inside & hit_UPDOWN
+ pred3 = True
+ return np.select([pred1,pred2,pred3] , [[dis, np.tile(UPWARDS, dis.shape) ], [dis,np.tile(UPDOWN, dis.shape)], FARAWAY])
+
+ def rotate(self,M, center):
+ self.u_axis = self.u_axis.matmul(M)
+ self.v_axis = self.v_axis.matmul(M)
+ self.normal = self.normal.matmul(M)
+ self.center = center + (self.center-center).matmul(M)
+
+ def get_uv(self, hit):
+ M_C = hit.point - self.center
+ u = ((self.u_axis.dot(M_C)/self.w + 1 ) /2 + self.uv_shift[0])
+ v = ((self.v_axis.dot(M_C)/self.h + 1 ) /2 + self.uv_shift[1])
+ return u,v
+
+
+ def get_Normal(self, hit):
+ return self.normal
+
diff --git a/Programming/TRAK/sightpy/geometry/primitive.py b/Programming/TRAK/sightpy/geometry/primitive.py
new file mode 100644
index 00000000..9895a942
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/primitive.py
@@ -0,0 +1,30 @@
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+import numpy as np
+
+
+class Primitive:
+ def __init__(self, center, material, max_ray_depth = 1, shadow = True):
+ self.center = center
+ self.material = material
+ self.material.assigned_primitive = self
+ self.shadow = shadow
+ self.collider_list = []
+ self.max_ray_depth = max_ray_depth
+
+ def rotate(self, θ, u):
+
+ u = u.normalize()
+ θ = θ/180 *np.pi
+ cosθ = np.cos(θ)
+ sinθ = np.sqrt(1-cosθ**2) * np.sign(θ)
+
+ #rotation matrix along u axis
+ M = np.array([
+ [cosθ + u.x*u.x * (1-cosθ), u.x*u.y*(1-cosθ) - u.z*sinθ, u.x*u.z*(1-cosθ) +u.y*sinθ],
+ [u.y*u.x*(1-cosθ) + u.z*sinθ, cosθ + u.y**2 * (1-cosθ), u.y*u.z*(1-cosθ) -u.x*sinθ],
+ [u.z*u.x*(1-cosθ) -u.y*sinθ, u.z*u.y*(1-cosθ) + u.x*sinθ, cosθ + u.z*u.z*(1-cosθ)]
+ ])
+ for c in self.collider_list:
+ c.rotate(M, self.center)
+
diff --git a/Programming/TRAK/sightpy/geometry/sphere.py b/Programming/TRAK/sightpy/geometry/sphere.py
new file mode 100644
index 00000000..1d748a02
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/sphere.py
@@ -0,0 +1,52 @@
+import numpy as np
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+from ..geometry import Primitive, Collider
+
+class Sphere(Primitive):
+ def __init__(self,center, material, radius, max_ray_depth = 5, shadow = True):
+ super().__init__(center, material, max_ray_depth, shadow = shadow)
+ self.collider_list += [Sphere_Collider(assigned_primitive = self, center = center, radius = radius)]
+ self.bounded_sphere_radius = radius
+
+ def get_uv(self, hit):
+ return hit.collider.get_uv(hit)
+
+
+class Sphere_Collider(Collider):
+ def __init__(self, radius, **kwargs):
+ super().__init__(**kwargs)
+ self.radius = radius
+
+
+ def intersect(self, O, D):
+
+ b = 2 * D.dot(O - self.center)
+ c = self.center.square_length() + O.square_length() - 2 * self.center.dot(O) - (self.radius * self.radius)
+ disc = (b ** 2) - (4 * c)
+ sq = np.sqrt(np.maximum(0, disc))
+ h0 = (-b - sq) / 2
+ h1 = (-b + sq) / 2
+ h = np.where((h0 > 0) & (h0 < h1), h0, h1)
+ pred = (disc > 0) & (h > 0)
+ M = (O + D * h)
+ NdotD = ((M - self.center) * (1. / self.radius) ).dot(D)
+
+ pred1 = (disc > 0) & (h > 0) & (NdotD > 0)
+ pred2 = (disc > 0) & (h > 0) & (NdotD < 0)
+ pred3 = True
+
+ #return an array with hit distance and the hit orientation
+ return np.select([pred1,pred2,pred3] , [[h, np.tile(UPDOWN, h.shape)], [h,np.tile(UPWARDS, h.shape)], FARAWAY])
+
+ def get_Normal(self, hit):
+ # M = intersection point
+ return (hit.point - self.center) * (1. / self.radius)
+
+ def get_uv(self, hit):
+ M_C = (hit.point - self.center) / self.radius
+ phi = np.arctan2(M_C.z, M_C.x)
+ theta = np.arcsin(M_C.y)
+ u = (phi + np.pi) / (2*np.pi)
+ v = (theta + np.pi/2) / np.pi
+ return u,v
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/geometry/surface.py b/Programming/TRAK/sightpy/geometry/surface.py
new file mode 100644
index 00000000..600bbb92
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/surface.py
@@ -0,0 +1,30 @@
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+import numpy as np
+
+
+class Surface:
+ def __init__(self, center, material, shadow = True):
+ self.center = center
+ self.material = material
+ self.material.assigned_surface = self
+ self.shadow = shadow
+ self.collider_list = []
+
+
+ def rotate(self, θ, u):
+
+ u = u.normalize()
+ θ = θ/180 *np.pi
+ cosθ = np.cos(θ)
+ sinθ = np.sqrt(1-cosθ**2) * np.sign(θ)
+
+ #rotation matrix along u axis
+ M = np.array([
+ [cosθ + u.x*u.x * (1-cosθ), u.x*u.y*(1-cosθ) - u.z*sinθ, u.x*u.z*(1-cosθ) +u.y*sinθ],
+ [u.y*u.x*(1-cosθ) + u.z*sinθ, cosθ + u.y**2 * (1-cosθ), u.y*u.z*(1-cosθ) -u.x*sinθ],
+ [u.z*u.x*(1-cosθ) -u.y*sinθ, u.z*u.y*(1-cosθ) + u.x*sinθ, cosθ + u.z*u.z*(1-cosθ)]
+ ])
+ for c in self.collider_list:
+ c.rotate(M, self.center)
+
diff --git a/Programming/TRAK/sightpy/geometry/triangle.py b/Programming/TRAK/sightpy/geometry/triangle.py
new file mode 100644
index 00000000..bee1cb87
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/triangle.py
@@ -0,0 +1,77 @@
+import numpy as np
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+from ..geometry.primitive import Primitive
+from ..geometry.collider import Collider
+
+
+class Triangle(Primitive):
+ def __init__(self,center, material, p1 , p2, p3, max_ray_depth,shadow = True):
+ super().__init__(center, material, max_ray_depth, shadow = shadow)
+ self.collider_list += [Triangle_Collider(assigned_primitive = self, p1 =p1, p2 = p2, p3 = p3)]
+
+ def get_uv(self, M, collider):
+ return collider.get_uv(M)
+
+
+class Triangle_Collider(Collider):
+ def __init__(self,assigned_surface, p1, p2, p3):
+
+
+ self.assigned_primitive = assigned_surface
+ self.p1 = p1
+ self.p2 = p2
+ self.p3 = p3
+ self.normal = ((self.p2 - self.p1).cross( self.p3 - self.p1)).normalize()
+
+
+ self.centroid = (self.p1 + self.p2 + self.p3)/3 #one possible definition of center. Used for intersect().
+
+
+ self.n31 = (self.p3 - self.p1).cross(self.normal)
+ self.n12 = (self.p1- self.p2).cross(self.normal)
+ self.n23 = (self.p2 - self.p3).cross(self.normal)
+
+
+
+ def intersect(self, O, D):
+ N = self.normal
+
+ NdotD = N.dot(D)
+ NdotD = np.where(NdotD == 0., NdotD + 0.0001, NdotD) #avoid zero division
+
+ NdotC_O = N.dot(self.centroid - O)
+ d = D * NdotC_O / NdotD
+ M = O + d # intersection point
+ dis = d.length()
+ M_C = M - self.centroid
+ hit_inside = (self.n31.dot(M-self.p1) >= 0) & (self.n12.dot(M-self.p2) >= 0)& (self.n23.dot(M-self.p3) >= 0) & (NdotC_O * NdotD > 0)
+ hit_UPWARDS = (NdotD < 0)
+ hit_UPDOWN = np.logical_not(hit_UPWARDS)
+
+
+ pred1 = hit_inside & hit_UPWARDS
+ pred2 = hit_inside & hit_UPDOWN
+ pred3 = True
+ return np.select([pred1,pred2,pred3] , [[dis, np.tile(UPWARDS, dis.shape) ], [dis,np.tile(UPDOWN, dis.shape)], FARAWAY])
+
+ def rotate(self,M, center):
+ self.p1 = center + (self.p1 -center).matmul(M)
+ self.p2 = center + (self.p2 -center).matmul(M)
+ self.p3 = center + (self.p3 -center).matmul(M)
+
+ self.n31 = self.n31.matmul(M)
+ self.n12 = self.n12.matmul(M)
+ self.n23 = self.n23.matmul(M)
+ self.normal = self.normal.matmul(M)
+ self.centroid = center + (self.centroid-center).matmul(M)
+
+ def get_uv(self, hit):
+ M_C = hit.point - self.center
+ u = ((self.pu.dot(M_C)/self.w + 1 ) /2 + self.uv_shift[0])
+ v = ((self.pv.dot(M_C)/self.h + 1 ) /2 + self.uv_shift[1])
+ return u,v
+
+
+ def get_Normal(self, hit):
+ return self.normal
diff --git a/Programming/TRAK/sightpy/geometry/triangle_mesh.py b/Programming/TRAK/sightpy/geometry/triangle_mesh.py
new file mode 100644
index 00000000..7e95d55a
--- /dev/null
+++ b/Programming/TRAK/sightpy/geometry/triangle_mesh.py
@@ -0,0 +1,39 @@
+import numpy as np
+from ..utils.constants import *
+from ..utils.vector3 import vec3
+from ..geometry import Primitive, Triangle_Collider
+
+
+# WORK IN PROGRESS.
+# We need to implement a bounding volume hierarchy to make TriangleMesh collision efficient.
+# Without a bounding volume hierarchy a model with 200 triangles takes around 3 minutes to be rendered
+
+class TriangleMesh(Primitive):
+ def __init__(self,file_name, center, material, max_ray_depth,shadow = True):
+ super().__init__(center, material,max_ray_depth, shadow = shadow)
+ self.collider_list += []
+ vs = []
+ fs = []
+ with open(file_name, 'r') as f:
+ r = f.read()
+ r = r.split('\n')
+ for i in r:
+ i = i.split()
+ if not i:
+ continue
+ elif i[0] == 'v':
+ x = float(i[1])
+ y = float(i[2])
+ z = float(i[3])
+ vs.append(vec3(x, y, z))
+ elif i[0] == 'f':
+ f1 = int(i[1].split('/')[0]) - 1
+ f2 = int(i[2].split('/')[0]) - 1
+ f3 = int(i[3].split('/')[0]) - 1
+ fs.append([f1, f2, f3])
+ for i in fs:
+ p1 = vs[i[0]] + center
+ p2 = vs[i[1]] + center
+ p3 = vs[i[2]] + center
+ self.collider_list += [colliders.Triangle_Collider(assigned_primitive = self, p1 =p1, p2 = p2, p3 = p3)]
+
diff --git a/Programming/TRAK/sightpy/lights.py b/Programming/TRAK/sightpy/lights.py
new file mode 100644
index 00000000..3335da13
--- /dev/null
+++ b/Programming/TRAK/sightpy/lights.py
@@ -0,0 +1,49 @@
+from .utils.constants import SKYBOX_DISTANCE
+import numpy as np
+from abc import abstractmethod
+
+
+# lights only have effect on Glossy materials
+class Light:
+ def __init__(self, pos, color):
+ self.pos = pos
+ self.color = color
+
+ @abstractmethod
+ def get_L(self):
+ pass
+
+ @abstractmethod
+ def get_irradiance(self, dist_light, NdotL):
+ pass
+
+ @abstractmethod
+ def get_distance(self, M):
+ pass
+
+
+class PointLight(Light):
+ def __init__(self, pos, color):
+ self.pos = pos
+ self.color = color
+ def get_L(self):
+ return (self.pos - M)*(1./(dist_light))
+
+ def get_distance(self, M):
+ return np.sqrt((self.pos - M).dot(self.pos - M))
+
+ def get_irradiance(self,dist_light, NdotL):
+ return self.color * NdotL/(dist_light**2.) * 100
+
+class DirectionalLight(Light):
+ def __init__(self, Ldir, color):
+ self.Ldir = Ldir
+ self.color = color
+ def get_L(self):
+ return self.Ldir
+
+ def get_distance(self, M):
+ return SKYBOX_DISTANCE
+
+ def get_irradiance(self, dist_light, NdotL):
+ return self.color * NdotL
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/materials/__init__.py b/Programming/TRAK/sightpy/materials/__init__.py
new file mode 100644
index 00000000..88b13c0b
--- /dev/null
+++ b/Programming/TRAK/sightpy/materials/__init__.py
@@ -0,0 +1,7 @@
+from .material import Material
+
+from .glossy import Glossy
+from .refractive import Refractive
+from .thin_film_interference import ThinFilmInterference
+from .diffuse import Diffuse
+from .emissive import Emissive
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/materials/diffuse.py b/Programming/TRAK/sightpy/materials/diffuse.py
new file mode 100644
index 00000000..1a11b3bc
--- /dev/null
+++ b/Programming/TRAK/sightpy/materials/diffuse.py
@@ -0,0 +1,99 @@
+from ..utils.constants import *
+from ..utils.vector3 import vec3, rgb, extract
+from ..utils.random import spherical_caps_pdf, cosine_pdf, mixed_pdf
+from functools import reduce as reduce
+from ..ray import Ray, get_raycolor
+from .. import lights
+import numpy as np
+from . import Material
+from ..textures import *
+
+class Diffuse(Material):
+ def __init__(self, diff_color, diffuse_rays = 20, ambient_weight = 0.5, **kwargs):
+ super().__init__(**kwargs)
+
+ if isinstance(diff_color, vec3):
+ self.diff_texture = solid_color(diff_color)
+ elif isinstance(diff_color, texture):
+ self.diff_texture = diff_color
+
+ self.diffuse_rays = diffuse_rays
+ self.max_diffuse_reflections = 2
+ self.ambient_weight = ambient_weight
+
+ def get_color(self, scene, ray, hit):
+
+ hit.point = (ray.origin + ray.dir * hit.distance) # intersection point
+ N = hit.material.get_Normal(hit) # normal
+
+ diff_color = self.diff_texture.get_color(hit)
+
+
+ color = rgb(0.,0.,0.)
+
+ if ray.diffuse_reflections < 1:
+
+
+ nudged = hit.point + N * .000001
+ N_repeated = N.repeat(self.diffuse_rays)
+
+
+ if ray.n.shape() == 1:
+ n_repeated = ray.n
+ else:
+ n_repeated = ray.n.repeat(self.diffuse_rays)
+
+ nudged_repeated = nudged.repeat(self.diffuse_rays)
+ hit_repeated = hit.point.repeat(self.diffuse_rays)
+
+
+ size = N.shape()[0] * self.diffuse_rays
+
+ pdf1 = cosine_pdf(size, N_repeated)
+ pdf2 = spherical_caps_pdf(size, nudged_repeated, scene.importance_sampled_list)
+
+ s_pdf = None
+ if scene.importance_sampled_list == []:
+ s_pdf = cosine_pdf(size, N_repeated)
+ else:
+ s_pdf = mixed_pdf(size, pdf1, pdf2, self.ambient_weight)
+
+ ray_dir = s_pdf.generate()
+ PDF_val = s_pdf.value(ray_dir)
+
+ NdotL = np.clip(ray_dir.dot(N_repeated),0.,1.)
+ color_temp = get_raycolor(Ray(nudged_repeated, ray_dir, ray.depth + 1, n_repeated, ray.reflections + 1, ray.transmissions, ray.diffuse_reflections + 1), scene)
+ color_temp = color_temp * NdotL / PDF_val / (np.pi) # diff_color/np.pi = Lambertian BRDF
+ color += diff_color * color_temp.reshape(N.shape()[0], self.diffuse_rays).mean(axis = 1)
+
+ return color
+
+ elif ray.diffuse_reflections < self.max_diffuse_reflections:
+
+ """
+ when ray.diffuse_reflections > 1 we just call one diffuse ray to solve rendering equation (otherwise is too slow)
+ """
+
+ nudged = hit.point + N * .000001
+ size = N.shape()[0]
+ s_pdf = None
+
+ pdf1 = cosine_pdf(size, N)
+ pdf2 = spherical_caps_pdf(size, nudged, scene.importance_sampled_list)
+
+ if scene.importance_sampled_list == []:
+ s_pdf = cosine_pdf(size, N)
+ else:
+ s_pdf = mixed_pdf(size, pdf1, pdf2, self.ambient_weight)
+
+ ray_dir = s_pdf.generate()
+ PDF_val = s_pdf.value(ray_dir)
+
+ NdotL = np.clip(N.dot(ray_dir),0.,1.)
+ color_temp = diff_color * get_raycolor(Ray(nudged, ray_dir, ray.depth + 1, ray.n, ray.reflections + 1, ray.transmissions, ray.diffuse_reflections + 1), scene)
+ color = color_temp * NdotL / PDF_val / (np.pi)
+
+ return color
+
+ else:
+ return color
diff --git a/Programming/TRAK/sightpy/materials/emissive.py b/Programming/TRAK/sightpy/materials/emissive.py
new file mode 100644
index 00000000..3f375c64
--- /dev/null
+++ b/Programming/TRAK/sightpy/materials/emissive.py
@@ -0,0 +1,23 @@
+from ..utils.constants import *
+from ..utils.vector3 import vec3, rgb, extract
+from functools import reduce as reduce
+from ..ray import Ray, get_raycolor
+from .. import lights
+import numpy as np
+from . import Material
+from ..textures import *
+
+class Emissive(Material):
+ def __init__(self, color, **kwargs):
+
+ if isinstance(color, vec3):
+ self.texture_color = solid_color(color)
+ elif isinstance(color, texture):
+ self.texture_color = color
+
+ super().__init__(**kwargs)
+
+
+ def get_color(self, scene, ray, hit):
+ diff_color = self.texture_color.get_color(hit)
+ return diff_color
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/materials/glossy.py b/Programming/TRAK/sightpy/materials/glossy.py
new file mode 100644
index 00000000..98162167
--- /dev/null
+++ b/Programming/TRAK/sightpy/materials/glossy.py
@@ -0,0 +1,91 @@
+from ..utils.constants import *
+from ..utils.vector3 import vec3, rgb, extract
+from functools import reduce as reduce
+from ..ray import Ray, get_raycolor
+from .. import lights
+import numpy as np
+from . import Material
+from ..textures import *
+
+class Glossy(Material):
+ def __init__(self, diff_color, roughness, spec_coeff, diff_coeff, n, **kwargs):
+ super().__init__(**kwargs)
+
+ if isinstance(diff_color, vec3):
+ self.diff_texture = solid_color(diff_color)
+ elif isinstance(diff_color, texture):
+ self.diff_texture = diff_color
+
+ self.roughness = roughness
+ self.diff_coeff = diff_coeff
+ self.spec_coeff = spec_coeff
+ self.n = n # index of refraction
+
+
+ def get_color(self, scene, ray, hit):
+
+ hit.point = (ray.origin + ray.dir * hit.distance) # intersection point
+ N = hit.material.get_Normal(hit) # normal
+
+ diff_color = self.diff_texture.get_color(hit)* self.diff_coeff
+
+ # Ambient
+ color = scene.ambient_color * diff_color
+ V = ray.dir*-1.
+ nudged = hit.point + N * .000001 # M nudged to avoid itself
+
+ for light in scene.Light_list:
+
+ L = light.get_L() # direction to light
+ dist_light = light.get_distance(hit.point) # distance to light
+ NdotL = np.maximum(N.dot(L), 0.)
+ lv = light.get_irradiance(dist_light, NdotL) # amount of intensity that falls on the surface
+
+
+ # direction to ray origin
+
+ H = (L + V).normalize() # Half-way vector
+
+
+
+
+ # Shadow: find if the point is shadowed or not.
+ # This amounts to finding out if M can see the light
+ # Shoot a ray from M to L and check what object is the nearest
+ if not scene.shadowed_collider_list == []:
+ inters = [s.intersect(nudged, L) for s in scene.shadowed_collider_list]
+ light_distances, light_hit_orientation = zip(*inters)
+ light_nearest = reduce(np.minimum, light_distances)
+ seelight = (light_nearest >= dist_light)
+ else:
+ seelight = 1.
+
+ # Lambert shading (diffuse)
+ color += diff_color * lv * seelight
+
+ if self.roughness != 0.0:
+ #Fresnel Factor for specular highlight (Schlick’s approximation)
+ F0 = np.abs((ray.n - self.n)/(ray.n + self.n))**2
+ cosθ = np.clip(V.dot(H), 0.0, 1.)
+ F = F0 + (1. - F0)*(1.- cosθ)**5
+
+
+ # Phong shading (specular highlight)
+ a = 2./(self.roughness**2.) - 2.
+ Dphong = np.power(np.clip(N.dot(H), 0., 1.), a) * (a + 2.)/(2.*np.pi)
+
+ # Cook-Torrance model
+ color += F * Dphong /(4. * np.clip(N.dot(V) * NdotL, 0.001, 1.) ) * seelight * lv * self.spec_coeff
+
+ # Reflection
+ if ray.depth < hit.surface.max_ray_depth:
+
+ # Fresnel Factor for reflections (Schlick’s approximation)
+
+ F0 = np.abs((scene.n - self.n)/(scene.n + self.n))**2
+ cosθ = np.clip(V.dot(N),0.0,1.)
+ F = F0 + (1. - F0)*(1.- cosθ)**5
+ reflected_ray_dir = (ray.dir - N * 2. * ray.dir.dot(N)).normalize()
+ color += (get_raycolor(Ray(nudged, reflected_ray_dir, ray.depth + 1, ray.n, ray.reflections + 1, ray.transmissions, ray.diffuse_reflections), scene))*F
+
+ return color
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/materials/material.py b/Programming/TRAK/sightpy/materials/material.py
new file mode 100644
index 00000000..ef6b050c
--- /dev/null
+++ b/Programming/TRAK/sightpy/materials/material.py
@@ -0,0 +1,34 @@
+from ..utils.constants import *
+from ..utils.vector3 import vec3, rgb, extract
+from functools import reduce as reduce
+from ..ray import Ray, get_raycolor
+from .. import lights
+from ..utils.image_functions import load_image, load_image_as_linear_sRGB
+import numpy as np
+from abc import abstractmethod
+
+class Material():
+ def __init__(self,normalmap = None):
+
+ if normalmap != None:
+ normalmap = load_image("sightpy/normalmaps/" + normalmap)
+ self.normalmap = normalmap
+
+
+ def get_Normal(self, hit):
+ N_coll = hit.collider.get_Normal(hit)
+ if self.normalmap is not None:
+ u,v = hit.get_uv()
+ im = self.normalmap[-((v * self.normalmap.shape[0]*self.repeat ).astype(int)% self.normalmap.shape[0]) , (u * self.normalmap.shape[1]*self.repeat).astype(int) % self.normalmap.shape[1] ].T
+ N_map = (vec3(im[0] - 0.5,im[1] - 0.5,im[2] - 0.5)) * 2.0
+ return N_map.matmul(hit.collider.inverse_basis_matrix).normalize()*hit.orientation
+ else:
+ return N_coll*hit.orientation
+
+ def set_normalmap(self, normalmap,repeat= 1.0):
+ self.normalmap = load_image("sightpy/normalmaps/" + normalmap)
+ self.repeat = repeat
+
+ @abstractmethod
+ def get_color(self, scene, ray, hit):
+ pass
diff --git a/Programming/TRAK/sightpy/materials/refractive.py b/Programming/TRAK/sightpy/materials/refractive.py
new file mode 100644
index 00000000..c4524258
--- /dev/null
+++ b/Programming/TRAK/sightpy/materials/refractive.py
@@ -0,0 +1,85 @@
+from ..utils.constants import *
+from ..utils.vector3 import vec3, rgb, extract
+from functools import reduce as reduce
+from ..ray import Ray, get_raycolor
+from .. import lights
+import numpy as np
+from . import Material
+
+class Refractive(Material):
+ def __init__(self, n, **kwargs):
+ super().__init__(**kwargs)
+
+ self.n = n # index of refraction
+
+ # Instead of defining a index of refraction (n) for each wavelenght (computationally expensive) we aproximate defining the index of refraction
+ # using a vec3 for red = 630 nm, green 555 nm, blue 475 nm, the most sensitive wavelenghts of human eye.
+
+ # Index a refraction is a complex number.
+ # The real part is involved in how much light is reflected and model refraction direction via Snell Law.
+ # The imaginary part of n is involved in how much light is reflected and absorbed. For non-transparent materials like metals is usually between (0.1j,3j)
+ # and for transparent materials like glass is usually between (0.j , 1e-7j)
+
+
+
+ def get_color(self, scene, ray, hit):
+
+ hit.point = (ray.origin + ray.dir * hit.distance) # intersection point
+ N = hit.material.get_Normal(hit) # normal
+
+ color = rgb(0.,0.,0.)
+
+ V = ray.dir*-1. # direction to ray origin
+ nudged = hit.point + N * .000001 # M nudged to avoid itself
+ # compute reflection and refraction
+ # a paper explaining formulas used:
+ # https://graphics.stanford.edu/courses/cs148-10-summer/docs/2006--degreve--reflection_refraction.pdf
+ # reallistic refraction is expensive. (requires exponential complexity because each ray is divided in two)
+
+ if ray.depth 1.0) by scaling
+ rgb_max = np.amax(rgb, axis=0) + 0.00001 # avoid division by zero
+ intensity_cutoff = 1.0
+ rgb = np.where(rgb_max > intensity_cutoff, rgb * intensity_cutoff / (rgb_max), rgb)
+
+ return rgb
+
+
+def sRGB_to_sRGB_linear(rgb):
+
+ '''sRGB standard for gamma inverse correction.'''
+ rgb_linear = np.where( rgb <= 0.03928, rgb / 12.92, np.power((rgb + 0.055) / 1.055, 2.4))
+
+ return rgb_linear
+
diff --git a/Programming/TRAK/sightpy/utils/constants.py b/Programming/TRAK/sightpy/utils/constants.py
new file mode 100644
index 00000000..0c39f367
--- /dev/null
+++ b/Programming/TRAK/sightpy/utils/constants.py
@@ -0,0 +1,5 @@
+UPWARDS = 1
+UPDOWN = -1
+FARAWAY = 1.0e39
+SKYBOX_DISTANCE = 1.0e6
+
diff --git a/Programming/TRAK/sightpy/utils/image_functions.py b/Programming/TRAK/sightpy/utils/image_functions.py
new file mode 100644
index 00000000..34ea7374
--- /dev/null
+++ b/Programming/TRAK/sightpy/utils/image_functions.py
@@ -0,0 +1,40 @@
+from PIL import Image, ImageFilter
+import numpy as np
+from pathlib import Path
+from .colour_functions import sRGB_to_sRGB_linear
+
+
+def load_image(path):
+ img = Image.open(Path(path))
+ return np.asarray(img)/256.
+
+
+def load_image_with_blur(path, blur = 0.):
+ img = Image.open(Path(path))
+ img = img.filter(ImageFilter.GaussianBlur(radius=blur))
+
+ return np.asarray(img)/256.
+
+
+
+
+def load_image_as_linear_sRGB(path, blur = 0.0):
+
+ path = Path(path)
+ location = str(path.parents[0])
+ name = str(path.name)#be sure that image doesn't lose quality
+
+ print("proccesing " + name)
+ img = Image.open(path)
+
+ if blur != 0.0:
+ img = img.filter(ImageFilter.GaussianBlur(radius=blur))
+
+ img_array = np.asarray(img)/256.
+ img_sRGB_linear_array = sRGB_to_sRGB_linear(img_array)
+ return img_sRGB_linear_array
+
+
+
+
+
diff --git a/Programming/TRAK/sightpy/utils/random.py b/Programming/TRAK/sightpy/utils/random.py
new file mode 100644
index 00000000..0166fc8b
--- /dev/null
+++ b/Programming/TRAK/sightpy/utils/random.py
@@ -0,0 +1,231 @@
+import numpy as np
+from ..utils.vector3 import vec3
+from abc import abstractmethod
+
+def random_in_unit_disk(shape):
+ r = np.sqrt(np.random.rand(shape))
+ phi = np.random.rand(shape)*2*np.pi
+ return r * np.cos(phi), r * np.sin(phi)
+
+def random_in_unit_sphere(shape):
+
+ #https://mathworld.wolfram.com/SpherePointPicking.html
+ phi = np.random.rand(shape)*2*np.pi
+ u = 2.*np.random.rand(shape) - 1.
+ r = np.sqrt(1-u**2)
+ return vec3( r*np.cos(phi), r*np.sin(phi), u)
+
+
+
+class PDF:
+ """Probability density function"""
+ @abstractmethod
+ def value(self,ray_dir):
+ """get probability density function value at direction ray_dir"""
+ pass
+
+ @abstractmethod
+ def generate(self):
+ """generate random ray directions according the probability density function"""
+ pass
+
+
+
+
+class hemisphere_pdf(PDF):
+ """Probability density Function"""
+ def __init__(self,shape, normal):
+ self.shape = shape
+ self.normal = normal
+
+
+ def value(self,ray_dir):
+ return 1./(2.*np.pi)
+
+ def generate(self):
+ r = random_in_unit_sphere(self.shape)
+ return vec3.where( self.normal.dot(r) < 0. , r*-1., r )
+
+
+class cosine_pdf(PDF):
+ """Probability density Function"""
+ def __init__(self,shape, normal):
+ self.shape = shape
+ self.normal = normal
+
+
+
+ def value(self,ray_dir):
+ return np.clip(ray_dir.dot(self.normal),0.,1.)/np.pi
+
+ def generate(self):
+ ax_w = self.normal
+ a = vec3.where( np.abs(ax_w.x) > 0.9 , vec3(0,1,0) , vec3(1,0,0))
+ ax_v = ax_w.cross(a).normalize()
+ ax_u = ax_w.cross(ax_v)
+
+ phi = np.random.rand(self.shape)*2*np.pi
+ r2 = np.random.rand(self.shape)
+
+ z = np.sqrt(1 - r2)
+ x = np.cos(phi) * np.sqrt(r2)
+ y = np.sin(phi) * np.sqrt(r2)
+
+ return ax_u*x + ax_v*y + ax_w*z
+
+
+
+
+class spherical_caps_pdf(PDF):
+ """Probability density Function"""
+ def __init__(self,shape, origin, importance_sampled_list):
+ self.shape = shape
+ self.origin = origin
+ self.importance_sampled_list = importance_sampled_list
+ self.l = len(importance_sampled_list)
+
+ def value(self, ray_dir):
+ PDF_value = 0.
+ for i in range(self.l):
+ PDF_value += np.where( ray_dir.dot(self.ax_w_list[i]) > self.cosθmax_list[i] , 1/((1 - self.cosθmax_list[i])*2*np.pi) , 0. )
+ PDF_value = PDF_value/self.l
+ return PDF_value
+
+
+ def generate(self):
+ shape = self.shape
+ origin = self.origin
+ importance_sampled_list = self.importance_sampled_list
+ l = self.l
+
+ mask = (np.random.rand(shape) * l).astype(int)
+ mask_list = [None]*l
+
+ cosθmax_list = [None]*l
+ ax_u_list = [None]*l
+ ax_v_list = [None]*l
+ ax_w_list = [None]*l
+
+ for i in range(l):
+
+ ax_w_list[i] = (importance_sampled_list[i].center - origin).normalize()
+ a = vec3.where( np.abs(ax_w_list[i].x) > 0.9 , vec3(0,1,0) , vec3(1,0,0))
+ ax_v_list[i] = ax_w_list[i].cross(a).normalize()
+ ax_u_list[i] = ax_w_list[i].cross(ax_v_list[i])
+ mask_list[i] = mask == i
+
+
+ target_distance = np.sqrt((importance_sampled_list[i].center - origin).dot(importance_sampled_list[i].center - origin))
+
+ cosθmax_list[i] = np.sqrt(1 - np.clip(importance_sampled_list[i].bounded_sphere_radius / target_distance, 0., 1.)**2 )
+
+ self.cosθmax_list = cosθmax_list
+ self.ax_w_list = ax_w_list
+
+ phi = np.random.rand(shape)*2*np.pi
+ r2 = np.random.rand(shape)
+
+ cosθmax = np.select(mask_list, cosθmax_list)
+ ax_w = vec3.select(mask_list, ax_w_list)
+ ax_v = vec3.select(mask_list, ax_v_list)
+ ax_u = vec3.select(mask_list, ax_u_list)
+
+ z = 1. + r2 * (cosθmax - 1.)
+ x = np.cos(phi) * np.sqrt(1. - z**2)
+ y = np.sin(phi) * np.sqrt(1. - z**2)
+
+ ray_dir = ax_u*x + ax_v*y + ax_w*z
+ return ray_dir
+
+
+class mixed_pdf(PDF):
+ """Probability density Function"""
+ def __init__(self,shape, pdf1, pdf2, pdf1_weight = 0.5):
+
+ self.pdf1_weight = pdf1_weight
+ self.pdf2_weight = 1. - pdf1_weight
+ self.shape = shape
+ self.pdf1 = pdf1
+ self.pdf2 = pdf2
+
+
+ def value(self,ray_dir):
+ return self.pdf1.value(ray_dir) * self.pdf1_weight + self.pdf2.value(ray_dir) * self.pdf2_weight
+
+ def generate(self):
+ mask = np.random.rand(self.shape)
+ return vec3.where( mask < self.pdf1_weight, self.pdf1.generate(), self.pdf2.generate() )
+
+
+
+
+
+
+
+def random_in_unit_spherical_caps(shape, origin, importance_sampled_list):
+
+ l = len(importance_sampled_list)
+
+
+ mask = (np.random.rand(shape) * l).astype(int)
+ mask_list = [None]*l
+
+ cosθmax_list = [None]*l
+ ax_u_list = [None]*l
+ ax_v_list = [None]*l
+ ax_w_list = [None]*l
+
+ for i in range(l):
+
+ ax_w_list[i] = (importance_sampled_list[i].center - origin).normalize()
+ a = vec3.where( np.abs(ax_w_list[i].x) > 0.9 , vec3(0,1,0) , vec3(1,0,0))
+ ax_v_list[i] = ax_w_list[i].cross(a).normalize()
+ ax_u_list[i] = ax_w_list[i].cross(ax_v_list[i])
+ mask_list[i] = mask == i
+
+
+ target_distance = np.sqrt((importance_sampled_list[i].center - origin).dot(importance_sampled_list[i].center - origin))
+
+ cosθmax_list[i] = np.sqrt(1 - np.clip(importance_sampled_list[i].bounded_sphere_radius / target_distance, 0., 1.)**2 )
+
+
+ phi = np.random.rand(shape)*2*np.pi
+ r2 = np.random.rand(shape)
+
+ cosθmax = np.select(mask_list, cosθmax_list)
+ ax_w = vec3.select(mask_list, ax_w_list)
+ ax_v = vec3.select(mask_list, ax_v_list)
+ ax_u = vec3.select(mask_list, ax_u_list)
+
+ z = 1. + r2 * (cosθmax - 1.)
+ x = np.cos(phi) * np.sqrt(1. - z**2)
+ y = np.sin(phi) * np.sqrt(1. - z**2)
+
+ ray_dir = ax_u*x + ax_v*y + ax_w*z
+
+ PDF = 0.
+ for i in range(l):
+ PDF += np.where( ray_dir.dot(ax_w_list[i]) > cosθmax_list[i] , 1/((1 - cosθmax_list[i])*2*np.pi) , 0. )
+ PDF = PDF/l
+
+ return ray_dir, PDF
+
+def random_in_unit_spherical_cap(shape,cosθmax,normal):
+
+
+ ax_w = normal
+ a = vec3.where( np.abs(ax_w.x) > 0.9 , vec3(0,1,0) , vec3(1,0,0))
+ ax_v = ax_w.cross(a).normalize()
+ ax_u = ax_w.cross(ax_v)
+
+ phi = np.random.rand(shape)*2*np.pi
+ r2 = np.random.rand(shape)
+
+ z = 1. + r2 * (cosθmax - 1.)
+ x = np.cos(phi) * np.sqrt(1. - z**2)
+ y = np.sin(phi) * np.sqrt(1. - z**2)
+
+
+
+
+ return ax_u*x + ax_v*y + ax_w*z
\ No newline at end of file
diff --git a/Programming/TRAK/sightpy/utils/vector3.py b/Programming/TRAK/sightpy/utils/vector3.py
new file mode 100644
index 00000000..43294adb
--- /dev/null
+++ b/Programming/TRAK/sightpy/utils/vector3.py
@@ -0,0 +1,188 @@
+import numpy as np
+import numbers
+
+def extract(cond, x):
+ if isinstance(x, numbers.Number):
+ return x
+ else:
+ return np.extract(cond, x)
+
+class vec3():
+
+ def __init__(self, x, y, z):
+ self.x = x
+ self.y = y
+ self.z = z
+
+ def __str__(self):
+ # Used for debugging. This method is called when you print an instance
+ return "(" + str(self.x) + ", " + str(self.y) + ", " + str(self.z) + ")"
+
+
+ def __add__(self, v):
+ if isinstance(v, vec3):
+ return vec3(self.x + v.x, self.y + v.y, self.z + v.z)
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(self.x + v, self.y + v, self.z + v)
+ def __radd__(self, v):
+ if isinstance(v, vec3):
+ return vec3(self.x + v.x, self.y + v.y, self.z + v.z)
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(self.x + v, self.y + v, self.z + v)
+ def __sub__(self, v):
+ if isinstance(v, vec3):
+ return vec3(self.x - v.x, self.y - v.y, self.z - v.z)
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(self.x - v, self.y - v, self.z - v)
+ def __rsub__(self, v):
+ if isinstance(v, vec3):
+ return vec3(v.x - self.x, v.y - self.y , v.z - self.z)
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(v - self.x, v - self.y , v - self.z)
+
+ def __mul__(self, v):
+ if isinstance(v, vec3):
+ return vec3(self.x * v.x , self.y * v.y , self.z * v.z )
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(self.x * v, self.y * v, self.z * v)
+ def __rmul__(self, v):
+ if isinstance(v, vec3):
+ return vec3(v.x *self.x , v.y * self.y, v.z * self.z )
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(v *self.x , v * self.y, v * self.z )
+ def __truediv__(self, v):
+ if isinstance(v, vec3):
+ return vec3(self.x / v.x , self.y / v.y , self.z / v.z )
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(self.x / v, self.y / v, self.z / v)
+
+
+ def __rtruediv__(self, v):
+ if isinstance(v, vec3):
+ return vec3(v.x / self.x, v.y / self.y, v.z / self.z)
+ elif isinstance(v, numbers.Number) or isinstance(v, np.ndarray):
+ return vec3(v / self.x, v / self.y, v / self.z)
+
+
+
+ def __abs__(self):
+ return vec3(np.abs(self.x), np.abs(self.y), np.abs(self.z))
+
+ def real(v):
+ return vec3(np.real(v.x), np.real(v.y), np.real(v.z))
+
+ def imag(v):
+ return vec3(np.imag(v.x), np.imag(v.y), np.imag(v.z))
+
+ def yzx(self):
+ return vec3(self.y, self.z, self.x)
+ def xyz(self):
+ return vec3(self.x, self.y, self.z)
+ def zxy(self):
+ return vec3(self.z, self.x, self.y)
+ def xyz(self):
+ return vec3(self.x, self.y, self.z)
+
+
+ def average(self):
+ return (self.x + self.y + self.z)/3
+
+ def matmul(self, matrix):
+ if isinstance(self.x, numbers.Number):
+ return array_to_vec3(np.dot(matrix,self.to_array()))
+ elif isinstance(self.x, np.ndarray):
+ return array_to_vec3(np.tensordot(matrix,self.to_array() , axes=([1,0])))
+
+ def change_basis(self, new_basis):
+ return vec3(self.dot(new_basis[0]), self.dot(new_basis[1]), self.dot(new_basis[2]))
+
+ def __pow__(self, a):
+ return vec3(self.x**a, self.y**a, self.z**a)
+
+ def dot(self, v):
+ return self.x*v.x + self.y*v.y + self.z*v.z
+
+ def exp(v):
+ return vec3(np.exp(v.x) , np.exp(v.y) ,np.exp(v.z))
+
+ def sqrt(v):
+ return vec3(np.sqrt(v.x) , np.sqrt(v.y) ,np.sqrt(v.z))
+
+ def to_array(self):
+ return np.array([self.x , self.y , self.z])
+
+ def cross(self, v):
+ return vec3(self.y*v.z - self.z*v.y, -self.x*v.z + self.z*v.x, self.x*v.y - self.y*v.x)
+
+ def length(self):
+ return np.sqrt(self.dot(self))
+
+ def square_length(self):
+ return self.dot(self)
+
+ def normalize(self):
+ mag = self.length()
+ return self * (1.0 / np.where(mag == 0, 1, mag))
+
+
+
+ def components(self):
+ return (self.x, self.y, self.z)
+
+ def extract(self, cond):
+ return vec3(extract(cond, self.x),
+ extract(cond, self.y),
+ extract(cond, self.z))
+
+ def where(cond, out_true, out_false):
+ return vec3(np.where(cond, out_true.x, out_false.x),
+ np.where(cond, out_true.y, out_false.y),
+ np.where(cond, out_true.z, out_false.z))
+
+ def select(mask_list, out_list):
+ out_list_x = [i.x for i in out_list]
+ out_list_y = [i.y for i in out_list]
+ out_list_z = [i.z for i in out_list]
+
+ return vec3(np.select(mask_list, out_list_x),
+ np.select(mask_list, out_list_y),
+ np.select(mask_list, out_list_z))
+
+ def clip(self, min, max):
+ return vec3(np.clip(self.x, min, max),
+ np.clip(self.y, min, max),
+ np.clip(self.z, min, max))
+
+ def place(self, cond):
+ r = vec3(np.zeros(cond.shape), np.zeros(cond.shape), np.zeros(cond.shape))
+ np.place(r.x, cond, self.x)
+ np.place(r.y, cond, self.y)
+ np.place(r.z, cond, self.z)
+ return r
+
+ def repeat(self, n):
+ return vec3(np.repeat(self.x , n), np.repeat(self.y , n), np.repeat(self.z , n))
+
+ def reshape(self, *newshape):
+ return vec3(self.x.reshape(*newshape), self.y.reshape(*newshape), self.z.reshape(*newshape))
+
+ def shape(self, *newshape):
+ if isinstance(self.x, numbers.Number):
+ return 1
+ elif isinstance(self.x, np.ndarray):
+ return self.x.shape
+
+ def mean(self, axis):
+ return vec3(np.mean(self.x,axis = axis), np.mean(self.y,axis = axis), np.mean(self.z,axis = axis))
+
+ def __eq__(self, other):
+ return (self.x == other.x) & (self.y == other.y) & (self.z == other.z)
+
+
+def array_to_vec3(array):
+ return vec3(array[0],array[1],array[2])
+
+
+
+global rgb
+rgb = vec3
\ No newline at end of file
diff --git a/Programming/TRAK/utils.py b/Programming/TRAK/utils.py
new file mode 100644
index 00000000..b211d68c
--- /dev/null
+++ b/Programming/TRAK/utils.py
@@ -0,0 +1,13 @@
+from configparser import ConfigParser
+
+def load_config(config_path):
+ config = ConfigParser()
+ config.read(config_path)
+ return config
+
+def parse_resolution(resolution):
+ try:
+ width, height = map(int, resolution.lower().split('x'))
+ return width, height
+ except ValueError:
+ raise ValueError("Resolution must be in the format WIDTHxHEIGHT, e.g., 1920x1080.")
\ No newline at end of file