Compare commits

..

14 Commits

Author SHA1 Message Date
47473a3a08 Fix raytracer bugs: BVH traversal, AABB transforms, root selection, and shading
- BVH: transform AABB using all 8 corners, fix leaf node traversal to check all primitives
- Node: reset AABB from primitive before transform, compute distance in world space
- Primitives: correct quadratic root selection (pick smallest positive), fix t-guards for Circle/RectangleXY, fix Torus AABB orientation
- Ray: fix random_unit_vec to cover all octants, compute reflection outside light loop, add indirect diffuse GI

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-13 11:45:54 +00:00
fa31d18c12 1. Camera movable with mouse and keyboard 2. GUI runs in separate thread 3. Improvement to GUI widgets 4. Fixes to BVH 2026-03-08 20:13:34 +00:00
d64085461a update readme 2026-02-23 14:11:55 +00:00
79493b8924 update readme 2026-02-23 14:11:29 +00:00
STP
e143a4b2ce Sick ass images 2023-12-04 05:34:00 -05:00
STP
5fe2e4a4e6 Sick ass cornel box 2023-12-04 03:49:20 -05:00
STP
daed0ef0b9 More gui options! 2023-12-03 22:45:49 -05:00
STP
9276088b4b fix 2023-12-03 22:13:26 -05:00
STP
0eff7fc694 propper multithreading 2023-12-03 22:12:46 -05:00
STP
d8488f24f7 BVH and rays complete 2023-12-02 21:52:59 -05:00
STP
d89e7f4951 Bvh added 2023-12-01 16:37:50 -05:00
STP
ba45fcadb7 cleanup 2023-11-30 11:56:08 -05:00
STP
3afe51c4c7 bvh! 2023-11-29 20:46:35 -05:00
STP
f7eaaabe93 Implemented get_aabb for primitive 2023-11-29 11:43:29 -05:00
28 changed files with 2010 additions and 838 deletions

186
README.md
View File

@@ -1,41 +1,155 @@
# Graphics Project # Graphics Project
This is my graphics project that I will be working on for A5 # Introduction
I will use rlua for interacting with lua files
## Installation ![image](img/img2.png)
## Scripting This is a project I undertook at the University of Waterloo, where I first started using rust. Because of my inexperience, the code isn't as organised as it would be if I made it today and represents my first steps in computer graphics and the rust language.
V() My unique aim was to perform ray intersections on special geometric surfaces, such as the CrossCap surface and the Steiner surface, hence those are among my _primitives_.
P()
Scene() # Installation
Scene.addNode()
Scene.addLight() Clone and run with `cargo run`, however much better performance will be granted with `cargo run --release`.
Node()
Node.translate() ![example](img/example.png)
Node.rotate()
Node.scale() # Rhai
Camera()
Light() Rhai is used as an interactive scripting lang for this project. Examples are found in `rhai/`.
Material()
MaterialRed() ## Full List of rhai commands
MaterialBlue()
MaterialGreen() ```
MaterialMagenta() /// Basic math types
MaterialTurquoise()
Sphere() V(x : float, y : float, z : float) -> Vector3
SphereUnit() // 3dimensional vector, used for directions, colors, etc.
Cube()
CubeUnit() P(x : float, y : float, z : float) -> Position3
Cone() // 3dimensional position vector, used for points in space.
ConeUnit()
Cyclinder()
//CylinderUnit() /// Scene and graph
Circle()
CircleUnit() Scene() -> Scene
Rectangle() // Create an empty scene with no nodes, lights, or camera.
RectangleUnit()
Steiner() Scene.addNode(node : Node) -> void
Torus() // Add a node (with geometry or camera) to the scene.
Scene.addLight(light : Light) -> void
// Add a light source to the scene.
Scene.setCamera(camera : Camera) -> void
// Set the active camera for this scene.
/// Nodes and transforms
Node() -> Node
// Create an empty node (no mesh / camera / light by default).
Node.translate(x : float, y : float, z : float) -> Node
// Apply translation by vector V(x, y, z) in local space, returns self for chaining.
Node.rotate(x : float, y : float, z : float) -> Node
// Rotate node by Euler angles (in radians or degrees, implementationdefined).
Node.scale(x : float, y : float, z : float) -> Node
// Nonuniform scale in local space.
Node.setMaterial(material : Material) -> Node
// Set material for this node's mesh (if any).
/// Camera
Camera(position : P, target : P, up : V) -> Camera
// Create a camera located at `position`, looking at `target`, with `up` as the up direction.
/// Lighting
Ambient(color : V) -> AmbientLight
// Ambient light contribution with RGB in [0, 1].
Light(position : P, color : V, falloff : V) -> PointLight
// Point light at `position` with RGB `color` and falloff parameters (constant, linear, quadratic).
/// Materials
Material(kd : V, ks : V, kr : V, shininess : float) -> Material
// Phongstyle material:
// kd: diffuse color
// ks: specular color
// kr: reflection / mirror color
// shininess: specular exponent.
MaterialRed() -> Material
MaterialBlue() -> Material
MaterialGreen() -> Material
MaterialMagenta() -> Material
MaterialTurquoise() -> Material
// Convenience materials with predefined colors.
/// Primitives
Sphere(pos : P, radius : float) -> Mesh
// Sphere centered at `pos` with given radius.
SphereUnit() -> Mesh
// Unit sphere at (0, 0, 0) with radius 1.
Cube(pos : P, radius : float, normal : V) -> Mesh
// Cube centered at `pos`, edge length = 2 * radius (or radius, implementationdefined),
// `normal` can define an orientation axis.
CubeUnit() -> Mesh
// Unit cube at (0, 0, 0).
Cone(radius : float, height : float) -> Mesh
// Cone aligned with +Z (for example), base radius and height.
ConeUnit() -> Mesh
// Cone with radius 1 and height 1 at the origin.
Cylinder(radius : float, height : float) -> Mesh
// Cylinder aligned with +Z, given radius and height.
CylinderUnit() -> Mesh
// Cylinder with radius 1 and height 1 at the origin.
Circle(position : P, radius : float, normal : V) -> Mesh
// Flat disk at `position` with `normal` orientation and given radius.
CircleUnit() -> Mesh
// Unit circle in the XY plane at the origin.
Rectangle(position : P, size : V, normal : V) -> Mesh
// Axisaligned rectangle centered at `position`, width/height from size.x / size.y, oriented by `normal`.
RectangleUnit() -> Mesh
// 1x1 rectangle in the XY plane centered at origin.
/// Special / parametric surfaces
Steiner() -> Mesh
// A Steiner surface with default parameters and resolution.
Torus(radiusMajor : float, radiusMinor : float) -> Mesh
// Torus with major and minor radius, centered at origin.
Roman() -> Mesh
// Roman surface with default scale and resolution.
CrossCap() -> Mesh
// Crosscap surface (Boy's surface variant / projective plane immersion).
Gnonom() -> Mesh
// Gnomonlike parametric surface (implementationdefined shape).
```

BIN
img.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 939 KiB

BIN
img/example.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

BIN
img/img2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
img/img3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

BIN
img/img4.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 836 KiB

BIN
img/img5.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.4 MiB

BIN
img/img6.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

BIN
img/img7.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.9 MiB

BIN
img/img8.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 MiB

BIN
img/img9.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.5 MiB

81
rhai/bvh_test.rhai Normal file
View File

@@ -0,0 +1,81 @@
// BVH Stress Test Scene - Grid of spheres
let scene = Scene();
// Camera
let camera = Camera(P(0.0, 2.0, 5.0), P(0.0, 0.0, 0.0), V(0.0, 1.0, 0.0));
scene.addCamera("main", camera);
// Lights
let light = Light(P(3.0, 5.0, 3.0), V(0.8, 0.8, 0.8), V(0.05, 0.05, 0.05));
light.active(true);
scene.addLight("key", light);
let light2 = Light(P(-3.0, 4.0, -2.0), V(0.4, 0.4, 0.6), V(0.05, 0.05, 0.05));
light2.active(true);
scene.addLight("fill", light2);
let ambient = Ambient(V(0.1, 0.1, 0.1));
ambient.active(true);
scene.addLight("ambient", ambient);
// Materials
let kd = V(0.8, 0.2, 0.2);
let ks = V(0.3, 0.3, 0.3);
let kr = V(0.0, 0.0, 0.0);
let red = Material(kd, ks, kr, 10.0);
scene.addMaterial("red", red);
let kd = V(0.2, 0.2, 0.8);
let ks = V(0.3, 0.3, 0.3);
let kr = V(0.0, 0.0, 0.0);
let blue = Material(kd, ks, kr, 10.0);
scene.addMaterial("blue", blue);
let kd = V(0.2, 0.8, 0.2);
let ks = V(0.3, 0.3, 0.3);
let kr = V(0.0, 0.0, 0.0);
let green = Material(kd, ks, kr, 10.0);
scene.addMaterial("green", green);
let kd = V(0.8, 0.8, 0.2);
let ks = V(0.3, 0.3, 0.3);
let kr = V(0.0, 0.0, 0.0);
let yellow = Material(kd, ks, kr, 10.0);
scene.addMaterial("yellow", yellow);
let kd = V(0.8, 0.4, 0.8);
let ks = V(0.3, 0.3, 0.3);
let kr = V(0.1, 0.1, 0.1);
let purple = Material(kd, ks, kr, 15.0);
scene.addMaterial("purple", purple);
// Floor
let floor = RectangleUnit();
let floor_node = Node(floor, green);
floor_node.rotate(-90.0, 0.0, 0.0);
floor_node.translate(0.0, -1.5, 0.0);
floor_node.scale(5.0, 5.0, 1.0);
scene.addNode("floor", floor_node);
// Grid of spheres: 5 x 4 x 5 = 100 spheres
let materials = [red, blue, green, yellow, purple];
let count = 0;
for x in range(-2, 3) {
for y in range(0, 4) {
for z in range(-2, 3) {
let mat_idx = count % 5;
let mat = materials[mat_idx];
let sphere = Sphere(P(0.0, 0.0, 0.0), 0.15);
let node = Node(sphere, mat);
let px = x.to_float() * 0.7;
let py = y.to_float() * 0.7 - 1.0;
let pz = z.to_float() * 0.7 - 1.0;
node.translate(px, py, pz);
scene.addNode("s" + count, node);
count += 1;
}
}
}
scene

View File

@@ -1,35 +0,0 @@
let scene = Scene();
let distance = 3.0;
let falloff = V(0.0,0.0,0.1);
let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Z Cam", camera);
let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Y Cam", camera);
let camera = Camera( P(distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+X Cam", camera);
let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Z Cam", camera);
let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Y Cam", camera);
let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-X Cam", camera);
let height = 4.0;
let spacing = 13.0;
let light = Light(P(0.0,height,spacing), V(0.0,0.3,0.3), falloff);
scene.addLight("blue", light);
let light = Light(P(0.0,height,0.0), V(0.0,0.6,0.0), falloff);
scene.addLight("green", light);
let light = Light(P(0.0,height,-spacing), V(0.3,0.0,0.0), falloff);
scene.addLight("red", light);
let material = Material(V(0.2,0.2,0.2), V(0.2, 0.8, 0.8), 10.0);
scene.addMaterial("bluegreen", material);
let mesh = Mesh("obj/cow.obj", material);
let mesh_node = Node(mesh);
scene.addNode("mesh", mesh_node);
scene

View File

@@ -1,66 +0,0 @@
let scene = Scene();
let material = Material(V(0.2,0.2,0.2), V(0.2, 0.2, 0.2), 10.0);
scene.addMaterial("material", material);
let material2 = Material(V(0.2,0.7,0.2), V(0.2, 0.2, 0.2), 10.0);
scene.addMaterial("mat2", material2);
let camera = Camera(P(0.0,0.0,2.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("Cam", camera);
let falloff = V(0.0,0.0,0.0);
let light = Light(P(6.0,6.0,6.0), V(0.4,0.4,0.4), falloff);
light.active(false);
scene.addLight("white", light);
let light = Light(P(2.0,0.0,0.0), V(0.0,1.0,0.0), V(0.1, 0.01, 0.001));
light.active(false);
scene.addLight("green", light);
let light = Light(P(-2.0,0.0,0.0), V(1.0,0.0,0.0), V(0.1, 0.01, 0.001));
light.active(false);
scene.addLight("red", light);
let light = Ambient(V(0.3,0.3,0.3));
scene.addLight("ambient", light);
let tri = TriangleUnit();
let tri_node = Node(tri, material);
tri_node.active(false);
scene.addNode("tri", tri_node);
let circle = CircleUnit();
let circle_node = Node(circle, material);
circle_node.active(false);
scene.addNode("circle", circle_node);
let cone = ConeUnit();
let cone_node = Node(cone, material);
cone_node.active(false);
scene.addNode("cone", cone_node);
let torus = Torus(0.5, 1.5);
let torus_node = Node(torus, material);
torus_node.active(true);
scene.addNode("torus", torus_node);
let sphere = SphereUnit();
let sphere_node = Node(sphere, material);
sphere_node.translate(0.0,0.0,0.0);
sphere_node.active(false);
scene.addNode("sphere", sphere_node);
let ground = SphereUnit();
let ground_node = Node(ground, material2);
let scale = 2.0;
ground_node.translate(0.0,-scale*2.0,0.0);
ground_node.scale(scale,scale,scale);
ground_node.active(false);
scene.addNode("ground", ground_node);
let mesh = Mesh("obj/cat.obj");
let mesh_node = Node(mesh, material);
mesh_node.active(false);
scene.addNode("mesh", mesh_node);
scene

View File

@@ -1,66 +1,174 @@
let scene = Scene(); let scene = Scene();
let distance = 10.0; let distance = 0.99;
let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Z Cam", camera); scene.addCamera("+Z Cam", camera);
let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Y Cam", camera); // scene.addCamera("+Y Cam", camera);
let camera = Camera( P(distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+X Cam", camera); // scene.addCamera("+X Cam", camera);
let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Z Cam", camera); // scene.addCamera("-Z Cam", camera);
let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Y Cam", camera); // scene.addCamera("-Y Cam", camera);
let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); // let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-X Cam", camera); // scene.addCamera("-X Cam", camera);
let material = Material(V(0.2,0.2,0.2), V(0.2, 0.8, 0.8), 10.0); let falloff = V(0.1, 0.1, 0.15);
scene.addMaterial("bluegreen", material);
let light = Light(P(0.0,7.0,0.0), V(0.0,0.0,1.0), V(0.1, 0.01, 0.001)); let colour = V(0.0,0.5,0.5);
light.active(false); let pos = P(-0.5,0.9,0.5);
let light = Light(pos, colour, falloff);
light.active(true);
scene.addLight("blue", light); scene.addLight("blue", light);
let light = Light( P(2.0,7.0,0.0), V(0.0,1.0,0.0), V(0.1, 0.01, 0.001)); let colour = V(0.0,1.0,0.0);
let pos = P(-0.5,0.9,-0.5);
let light = Light(pos, colour, falloff);
light.active(false); light.active(false);
scene.addLight("green", light); scene.addLight("green", light);
let light = Light( P(2.0,7.0,2.0), V(1.0,0.0,0.0), V(0.1, 0.01, 0.001)); let colour = V(1.0,0.0,0.0);
let light = Light(pos, colour, falloff);
light.active(false);
scene.addLight("red", light); scene.addLight("red", light);
let colour = V(0.7,0.7,0.7);
let pos = P(0.0,0.9,0.0);
let light = Light(pos, colour, falloff);
light.active(true);
scene.addLight("white", light);
let light = Ambient(V(0.1,0.1,0.1)); let light = Ambient(V(0.1,0.1,0.1));
light.active(true);
scene.addLight("ambient", light); scene.addLight("ambient", light);
let sphere = Sphere(P(0.0,0.0,0.0), 1.0 );
let sphere_node = Node(sphere, material);
scene.addNode("sphere", sphere_node);
//let mesh = Mesh("obj/cow.obj" ); //let mesh = Mesh("obj/cow.obj" );
//let mesh_node = Node(mesh); //let mesh_node = Node(mesh);
//scene.addNode("mesh", mesh_node); //scene.addNode("mesh", mesh_node);
for i in 0..6 {
let sphere = Sphere(P(0.0,0.0,0.0), 2.0 );
let sphere_node = Node(sphere, material);
sphere_node.translate(4.0*cos(i.to_float()), -4.0, 4.0*sin(i.to_float()));
scene.addNode(i.to_string(), sphere_node);
}
// let child = sphere_node.child(sphere); // let child = sphere_node.child(sphere);
// child.translate(V(1.0,1.0,1.0)); // child.translate(V(1.0,1.0,1.0));
//scene.addNode(child); //scene.addNode(child);
//let sphere2= SphereUnit();
//let sphere2_node = Node( sphere2, material2);
// sphere2_node.rotate(0.1,0.1,0.0);
// sphere2_node.translate(0.0,1.0,0.0);
//scene.addNode("sphere2", sphere2_node);
let kd = V(1.0, 1.0, 1.0); // Diffuse color (white)
let ks = V(0.0,0.0,0.0); // Specular color (no specular reflection)
let kr = V(0.0,0.0,0.0); // Reflection color (no reflection)
let white_wall = Material(kd, ks, kr, 10.0);
scene.addMaterial("white_wall", white_wall);
let kd = V(1.0, 0.0, 0.0); // Diffuse color (white)
let ks = V(0.0,0.0,0.0); // Specular color (no specular reflection)
let kr = V(0.0,0.0,0.0); // Reflection color (no reflection)
let red_wall = Material(kd, ks, kr, 10.0);
scene.addMaterial("red_wall", red_wall);
let kd = V(0.0, 1.0, 0.0); // Diffuse color (white)
let ks = V(0.0,0.0,0.0); // Specular color (no specular reflection)
let kr = V(0.0,0.0,0.0); // Reflection color (no reflection)
let green_wall = Material(kd, ks, kr, 10.0);
scene.addMaterial("green_wall", green_wall);
let kd = V(0.0, 0.0, 1.0); // Diffuse color (white)
let ks = V(0.0,0.0,0.0); // Specular color (no specular reflection)
let kr = V(0.0,0.0,0.0); // Reflection color (no reflection)
let blue_wall = Material(kd, ks, kr, 10.0);
scene.addMaterial("blue_wall", blue_wall);
//Rear wall
let rectangle1 = RectangleUnit();
let rectangle_node1 = Node(rectangle1, white_wall);
rectangle_node1.rotate(0.0, 0.0, 0.0);
rectangle_node1.translate(0.0, 0.0, -1.0);
rectangle_node1.active(true);
scene.addNode("rectangle1", rectangle_node1);
//Behind wall
// let rectangle6 = RectangleUnit();
// let rectangle_node6 = Node(rectangle6, white_wall);
// rectangle_node6.rotate(0.0, 180.0, 0.0);
// rectangle_node6.translate(0.0, 0.0, 1.0);
// rectangle_node6.active(true);
// scene.addNode("rectangle6", rectangle_node6);
//Right wall
let rectangle2 = RectangleUnit();
let rectangle_node2 = Node(rectangle2, green_wall);
rectangle_node2.rotate(0.0, -90.0, 0.0);
rectangle_node2.translate(1.0, 0.0, 0.0);
rectangle_node2.active(true);
scene.addNode("rectangle2", rectangle_node2);
//Floor
let rectangle3 = RectangleUnit();
let rectangle_node3 = Node(rectangle3, red_wall);
rectangle_node3.rotate(0.0, 90.0, 0.0);
rectangle_node3.translate(-1.0, 0.0, 0.0);
rectangle_node3.active(true);
scene.addNode("rectangle3", rectangle_node3);
//Left wall
let rectangle4 = RectangleUnit();
let rectangle_node4 = Node(rectangle4, white_wall);
rectangle_node4.rotate(90.0, 0.0, 0.0);
rectangle_node4.translate(0.0, 1.0, 0.0);
rectangle_node4.active(true);
scene.addNode("rectangle4", rectangle_node4);
//Ceiling
let rectangle5 = RectangleUnit();
let rectangle_node5 = Node(rectangle5, white_wall);
rectangle_node5.rotate(-90.0, 0.0, 0.0);
rectangle_node5.translate(0.0, -1.0, 0.0);
rectangle_node5.active(true);
scene.addNode("rectangle5", rectangle_node5);
let kd = V(0.0, 0.0, 0.0); // Diffuse color (white)
let ks = V(0.0,0.0,0.0); // Specular color (no specular reflection)
let kr = V(1.0,1.0,1.0); // Reflection color (no reflection)
let reflective = Material(kd, ks, kr, 10.0);
scene.addMaterial("reflective", reflective);
let sphere = Sphere(P(0.0,0.0,0.0), 0.4 );
let sphere_node = Node( sphere, reflective);
sphere_node.translate(0.4, -0.6, 0.0);
scene.addNode("sphere",sphere_node);
let kd = V(0.3, 0.3, 0.3); // Diffuse color (white)
let ks = V(0.3,0.3,0.0); // Specular color (no specular reflection)
let kr = V(0.0,0.0,1.0); // Reflection color (no reflection)
let shiny = Material(kd, ks, kr, 2.0);
scene.addMaterial("shiny", shiny);
let cube = CubeUnit(); let cube = CubeUnit();
let cube_node = Node(cube, material); let cube_node = Node( cube, shiny);
scene.addNode("cube", cube_node); cube_node.translate(-0.5,-0.6,0.0);
cube_node.scale(0.3,0.2,0.2);
cube_node.rotate(0.0,45.0,30.0);
scene.addNode("cube",cube_node);
//let gnonom = Gnonom(); let gnonom = Gnonom();
//let gnonom_node = Node(gnonom); let gnonom_node = Node(gnonom, shiny);
//scene.addNode("gnonom", gnonom_node); gnonom_node.scale(0.2,0.2,0.2);
gnonom_node.translate(0.0, 0.-0.7, 0.8);
gnonom_node.rotate(0.0, 45.0, 0.0);
gnonom_node.active(false);
scene.addNode("gnonom", gnonom_node);
//let cylinder = Cylinder(2.0,1.0 ); // let cylinder = Cylinder(2.0, 1.0);
//let cylinder_node = Node(cylinder); // let cylinder_node = Node(cylinder, material);
//cylinder_node.scale(1.0,1.0,1.0); // cylinder_node.scale(1.0, 1.0, 1.0);
//scene.addNode("cylinder",cylinder_node); // scene.addNode("cylinder", cylinder_node);
//let cone
scene scene

View File

@@ -2,7 +2,6 @@
let scene = Scene(); let scene = Scene();
let distance = 3.0; let distance = 3.0;
let falloff = V(0.0,0.0,0.1);
let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Z Cam", camera); scene.addCamera("+Z Cam", camera);
let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
@@ -16,8 +15,29 @@ scene.addCamera("-Y Cam", camera);
let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0)); let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-X Cam", camera); scene.addCamera("-X Cam", camera);
let kd = V(0.6, 0.3, 0.9); // Diffuse color (white)
let ks = V(0.0,0.0,0.0); // Specular color (no specular reflection)
let kr = V(1.0,1.0,1.0); // Reflection color (no reflection)
let material = Material(kd, ks, kr, 10.0);
scene.addMaterial("mattee", material);
let kd = V(0.3, 0.3, 0.3); // Diffuse color (white)
let ks = V(0.7,0.7,0.7); // Specular color (no specular reflection)
let kr = V(1.0,1.0,1.0); // Reflection color (no reflection)
let material1 = Material(kd, ks, kr, 10.0);
scene.addMaterial("mattee", material1);
let kd = V(0.4, 0.0, 0.8); // Diffuse color (white)
let ks = V(0.0,0.7,0.7); // Specular color (no specular reflection)
let kr = V(1.0,1.0,1.0); // Reflection color (no reflection)
let material2 = Material(kd, ks, kr, 10.0);
scene.addMaterial("reflect", material2);
let height = 4.0; let height = 4.0;
let spacing = 4.0; let spacing = 4.0;
let falloff = V(0.0,0.0,0.01);
let blue = V(0.0,0.0,0.6); let blue = V(0.0,0.0,0.6);
let light = Light(P(0.0,height,spacing), blue, falloff); let light = Light(P(0.0,height,spacing), blue, falloff);
scene.addLight("blue", light); scene.addLight("blue", light);
@@ -28,36 +48,43 @@ let red = V(0.6,0.0,0.0);
let light = Light(P(0.0,height,-spacing), red, falloff); let light = Light(P(0.0,height,-spacing), red, falloff);
scene.addLight("red", light); scene.addLight("red", light);
let material = Material(V(0.2,0.2,0.2), V(0.2, 0.8, 0.8), 10.0);
scene.addMaterial("bluegreen", material);
let steiner = Steiner(); let steiner = Steiner();
let steiner_node = Node(steiner, material); let steiner_node = Node(steiner, material2);
steiner_node.rotate(90.0,0.0,0.0);
steiner_node.translate(0.0,0.0,1.0);
scene.addNode("steiner", steiner_node); scene.addNode("steiner", steiner_node);
let steiner2 = Steiner2(); let steiner2 = Steiner2();
let steiner2_node = Node(steiner2, material2); let steiner2_node = Node(steiner2, material2);
steiner2_node.active(false);
scene.addNode("steiner2", steiner2_node); scene.addNode("steiner2", steiner2_node);
let crosscap = CrossCap(); let crosscap = CrossCap();
let crosscap_node = Node(crosscap, material); let crosscap_node = Node(crosscap, material);
crosscap_node.active(false);
scene.addNode("crosscap", crosscap_node); scene.addNode("crosscap", crosscap_node);
let p = 1.0; let p = 0.9;
let q = 1.0; let q = 0.1;
let crosscap2 = CrossCap2(p, q); let crosscap2 = CrossCap2(p, q);
let crosscap2_node = Node(crosscap2, material); let crosscap2_node = Node(crosscap2, material);
crosscap2_node.active(true);
crosscap2_node.translate(0.0,0.0,-1.5);
crosscap2_node.rotate(140.0,0.0,90.0);
scene.addNode("crosscap2", crosscap2_node); scene.addNode("crosscap2", crosscap2_node);
let k = 0.5; let k = 2.0;
let roman = Roman(k ); let roman = Roman(k );
let roman_node = Node(roman, material); let roman_node = Node(roman, material);
roman_node.active(false);
scene.addNode("roman", roman_node); scene.addNode("roman", roman_node);
let inner_rad = 1.0; let inner_rad = 1.0;
let outer_rad = 0.5; let outer_rad = 1.2;
let torus = Torus(inner_rad, outer_rad ); let torus = Torus(inner_rad, outer_rad );
let torus_node = Node(torus, material); let torus_node = Node(torus, material);
torus_node.scale(0.2,0.2,0.2);
torus_node.rotate(0.0,70.0,0.0);
scene.addNode("torus", torus_node); scene.addNode("torus", torus_node);
scene scene

34
rhai/space.rhai Normal file
View File

@@ -0,0 +1,34 @@
let scene = Scene();
let falloff = V(0.0,0.0,0.0);
//CAMERAS
// let camera = Camera( P(100.0,100.0,100.0), P(500.0,500.0,500.0), V(0.0,1.0,0.0));
// scene.addCamera("Main Camera", camera);
// //Light for the sun
// let light = Light(P(800.0, 800.0, 250.0), V(1.0, 1.0, 0.929), falloff);
// scene.addLight("Sun", light);
// //Ball for the sun
// let material = Material(V(1.0, 1.0, 0.9), V(0.9, 0.9, 0.9), 10.0);
// scene.addMaterial("material_sun", material);
// let sphere = Sphere(P(800.0, 800.0, 200.0), 50.0);
// let sphere_node = Node(sphere, material);
// scene.addNode("sphere", sphere_node);
// //Ball for the planet
// let material = Material(V(0.2,0.8,0.2), V(0.2, 0.8, 0.8), 10.0);
// scene.addMaterial("material_planet", material);
// let sphere = Sphere(P(500.0, 500.0, 500.0), 50.0);
// let sphere_node = Node(sphere, material);
// scene.addNode("sphere", sphere_node);
let material =
let steiner = Steiner();
let steiner_node = Node(steiner, material);
scene.addNode("sphere", steiner);
scene

View File

@@ -1,34 +0,0 @@
let scene = Scene();
let distance = 3.0;
let falloff = V(0.0,0.0,0.1);
let camera = Camera( P(0.0,0.0,distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Z Cam", camera);
let camera = Camera( P(0.0,distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+Y Cam", camera);
let camera = Camera( P(distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("+X Cam", camera);
let camera = Camera( P(0.0,0.0,-distance), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Z Cam", camera);
let camera = Camera( P(0.0,-distance,0.1), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-Y Cam", camera);
let camera = Camera( P(-distance,0.0,0.0), P(0.0,0.0,0.0), V(0.0,1.0,0.0));
scene.addCamera("-X Cam", camera);
let height = 4.0;
let spacing = 13.0;
let light = Light(P(0.0,height,spacing), V(0.0,0.3,0.3), falloff);
scene.addLight("blue", light);
let light = Light(P(0.0,height,0.0), V(0.0,0.6,0.0), falloff);
scene.addLight("green", light);
let light = Light(P(0.0,height,-spacing), V(0.3,0.0,0.0), falloff);
scene.addLight("red", light);
let material = Material(V(0.2,0.2,0.2), V(0.2, 0.8, 0.8), 10.0);
scene.addMaterial("bluegreen", material);
let sphere = Steiner( material);
let sphere_node = Node(sphere);
scene.addNode("sphere", sphere_node);
scene

View File

@@ -1,75 +1,85 @@
use crate::{ray::*, EPSILON}; use crate::{node::Node, ray::*, EPSILON};
use nalgebra::{Point3, Vector3}; use nalgebra::{distance, Matrix4, Point3, Vector3};
use std::collections::HashMap;
use std::fmt;
// Debuging statics
static mut STATIC0: i32 = 0;
static mut STATIC1: i32 = 0;
static mut STATIC2: i32 = 0;
static mut STATIC3: i32 = 0;
static mut STATIC4: i32 = 0;
// BOUNDING BOX ----------------------------------------------------------------- // BOUNDING BOX -----------------------------------------------------------------
#[derive(Clone)] #[derive(Clone)]
pub struct AABB { pub struct AABB {
pub bln: Point3<f64>, pub bln: Point3<f64>,
pub trf: Point3<f64>, pub trf: Point3<f64>,
pub centroid: Point3<f64>,
} }
impl AABB { impl AABB {
// New box with respective coordinates // New box with respective coordinates
pub fn new(bln: Point3<f64>, trf: Point3<f64>) -> AABB { pub fn new(bln: Point3<f64>, trf: Point3<f64>) -> AABB {
let bln = bln + Vector3::new(EPSILON, EPSILON, EPSILON); let bln = bln - Vector3::new(EPSILON, EPSILON, EPSILON);
let trf = trf - Vector3::new(EPSILON, EPSILON, EPSILON); let trf = trf + Vector3::new(EPSILON, EPSILON, EPSILON);
AABB { bln, trf } let centroid = bln + (trf - bln) / 2.0;
AABB { bln, trf, centroid }
}
//Empty box
pub fn empty() -> AABB {
AABB {
bln: Point3::new(f64::MAX, f64::MAX, f64::MAX),
trf: Point3::new(f64::MIN, f64::MIN, f64::MIN),
centroid: Point3::new(0.0, 0.0, 0.0),
}
}
//Apply a matrix transformation to a box
pub fn transform_mut(&mut self, mat: &Matrix4<f64>) {
let corners = [
Point3::new(self.bln.x, self.bln.y, self.bln.z),
Point3::new(self.trf.x, self.bln.y, self.bln.z),
Point3::new(self.bln.x, self.trf.y, self.bln.z),
Point3::new(self.trf.x, self.trf.y, self.bln.z),
Point3::new(self.bln.x, self.bln.y, self.trf.z),
Point3::new(self.trf.x, self.bln.y, self.trf.z),
Point3::new(self.bln.x, self.trf.y, self.trf.z),
Point3::new(self.trf.x, self.trf.y, self.trf.z),
];
let mut new_bln = Point3::new(f64::MAX, f64::MAX, f64::MAX);
let mut new_trf = Point3::new(f64::MIN, f64::MIN, f64::MIN);
for corner in &corners {
let t = mat.transform_point(corner);
new_bln = new_bln.inf(&t);
new_trf = new_trf.sup(&t);
}
self.bln = new_bln;
self.trf = new_trf;
self.centroid = self.bln + (self.trf - self.bln) / 2.0;
} }
// Intersect bounding box exactly // Intersect bounding box exactly
pub fn intersect_bounding_box(&self, ray: &Ray) -> bool { pub fn intersect_ray(&self, ray: &Ray) -> bool {
let bln = &self.bln; let t1 = (self.bln - ray.a).component_div(&ray.b);
let trf = &self.trf; let t2 = (self.trf - ray.a).component_div(&ray.b);
let t1 = (bln - ray.a).component_div(&ray.b);
let t2 = (trf - ray.a).component_div(&ray.b);
let tmin = t1.inf(&t2).min(); let tmin = t1.inf(&t2).max();
let tmax = t1.sup(&t2).max(); let tmax = t1.sup(&t2).min();
if tmax >= tmin { tmax >= tmin && tmax > 0.0
let intersect = ray.at_t(tmin);
// Check if the intersection is inside the box
if intersect.x > bln.x
|| intersect.x < trf.x
|| intersect.y > bln.y
|| intersect.y < trf.y
|| intersect.z > bln.z
|| intersect.z < trf.z
{
return true; // Intersection is outside the box
} }
} // Intersect with some epsilon tolerance
false pub fn intersect_ray_aprox(&self, ray: &Ray) -> bool {
} let t1 = (self.bln - ray.a).component_div(&ray.b);
// Intersect way with some epsilon term let t2 = (self.trf - ray.a).component_div(&ray.b);
pub fn intersect_bounding_box_aprox(&self, ray: &Ray) -> bool {
let bln = &self.bln;
let trf = &self.trf;
let t1 = (bln - ray.a).component_div(&ray.b);
let t2 = (trf - ray.a).component_div(&ray.b);
let tmin = t1.inf(&t2).min(); let tmin = t1.inf(&t2).max();
let tmax = t1.sup(&t2).max(); let tmax = t1.sup(&t2).min();
if tmax >= tmin { tmax >= tmin - EPSILON && tmax > 0.0
let intersect = ray.at_t(tmin);
// Check if the intersection is inside the box
if intersect.x > bln.x - EPSILON
|| intersect.x < trf.x + EPSILON
|| intersect.y > bln.y - EPSILON
|| intersect.y < trf.y + EPSILON
|| intersect.z > bln.z - EPSILON
|| intersect.z < trf.z + EPSILON
{
return true; // Intersection is outside the box
}
}
false
} }
// Get the center of this bounding box // Get the center of this bounding box
fn get_centroid(&self) -> Point3<f64> { fn get_centroid(&self) -> Point3<f64> {
self.bln + (self.trf - self.bln) / 2.0 self.centroid
} }
// Make a new AABB that contains both // Make a new AABB that contains both
pub fn join(&self, other: &AABB) -> AABB { pub fn join(&self, other: &AABB) -> AABB {
@@ -86,6 +96,20 @@ impl AABB {
), ),
) )
} }
//Join mutably
pub fn join_mut(&mut self, other: &AABB) {
self.bln = Point3::new(
self.bln.x.min(other.bln.x),
self.bln.y.min(other.bln.y),
self.bln.z.min(other.bln.z),
);
self.trf = Point3::new(
self.trf.x.max(other.trf.x),
self.trf.y.max(other.trf.y),
self.trf.z.max(other.trf.z),
);
self.centroid = self.bln + (self.trf - self.bln) / 2.0;
}
//Grow the AABB to contain the cover the point //Grow the AABB to contain the cover the point
pub fn grow(&self, other: &Point3<f64>) -> AABB { pub fn grow(&self, other: &Point3<f64>) -> AABB {
AABB::new( AABB::new(
@@ -101,34 +125,322 @@ impl AABB {
), ),
) )
} }
//Grow mutably
pub fn grow_mut(&mut self, other: &Point3<f64>) {
self.bln = Point3::new(
self.bln.x.min(other.x),
self.bln.y.min(other.y),
self.bln.z.min(other.z),
);
self.trf = Point3::new(
self.trf.x.max(other.x),
self.trf.y.max(other.y),
self.trf.z.max(other.z),
);
self.centroid = self.bln + (self.trf - self.bln) / 2.0;
}
// Size of AABB // Size of AABB
pub fn size(&self) -> Vector3<f64> { pub fn size(&self) -> Vector3<f64> {
self.trf - self.bln self.trf - self.bln
} } //Surface area of AABB
//Surface area of AABB
pub fn surface_area(&self) -> f64 { pub fn surface_area(&self) -> f64 {
let size = self.size(); let size = self.size();
2.0 * (size.x * size.y + size.x * size.z + size.y * size.z) 2.0 * (size.x * size.y + size.x * size.z + size.y * size.z)
} }
pub fn area(&self) -> f64 {
let extent = self.trf - self.bln;
return extent.x * extent.y + extent.y * extent.z + extent.z * extent.x;
}
// Volume of the AABB // Volume of the AABB
pub fn volume(&self) -> f64 { pub fn volume(&self) -> f64 {
let size = self.size(); let size = self.size();
size.x * size.y * size.z size.x * size.y * size.z
} }
} }
impl fmt::Display for AABB {
pub enum BVHNode<'a> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
Leaf { if self.bln[0] == f64::MAX || self.trf[0] == f64::MIN {
parent: &'a BVHNode<'a>, writeln!(f, "Empty aabb")
bounding_box: AABB, } else {
depth: u32, writeln!(f, "bln: {}\ntrf: {}", self.bln, self.trf)
}, }
Node { }
parent: Option<&'a BVHNode<'a>>, }
child_l: &'a BVHNode<'a>, #[derive(Clone)]
child_r: &'a BVHNode<'a>, pub struct BVHNode {
depth: u32, aabb: AABB, //The nodes bounding box
}, l_idx: usize, //Child node l, the right node is alway l_idx + 1
first_prim: usize, //First primitive that the node encapsulates
prim_count: usize, //Number of primitives the node encapsulates
} }
impl<'a> BVHNode<'a> {} impl BVHNode {
pub fn default() -> BVHNode {
BVHNode {
aabb: AABB::empty(),
l_idx: 0,
first_prim: 0,
prim_count: 0,
}
}
}
impl fmt::Display for BVHNode {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "l_idx: {}", self.l_idx)?;
writeln!(f, "First Prim: {}", self.first_prim)?;
writeln!(f, "Prim Count: {}", self.prim_count)?;
writeln!(f, "aabb: {}", self.aabb)
}
}
pub struct BVH {
bvh_nodes: Vec<BVHNode>, //BVH nodes with AABBs
nodes: Vec<Node>, //Nodes with primitives
nodes_used: usize,
}
impl BVH {
//Build a bvh by subdividing recursively
pub fn build(in_nodes: &HashMap<String, Node>) -> BVH {
/*
Make our own vec of nodes so that we can refer to it by index
This might be expensive so another method is preferred
*/
let mut nodes = vec![];
for (_, node) in in_nodes {
nodes.push(node.clone());
}
//A BVH tree will be maximum size of 2*n + 1
//Initialise an empty BVHNode with empty AABB
let n = nodes.len();
let bvh_nodes: Vec<BVHNode> = vec![BVHNode::default(); 2 * n + 1];
//Begin constructing our BVH tree
//One node used to begin with (The root node)
let nodes_used = 1;
let mut tree = BVH {
nodes,
bvh_nodes,
nodes_used,
};
// Get the root node at index 0
let root = &mut tree.bvh_nodes[0];
root.l_idx = 0; //Root node has no left or right child to begin
(root.first_prim, root.prim_count) = (0, n); //Make root include all n nodes
tree.update_bvh_node_aabb(0); //Create the root nodes AABB on the n primitives
tree.subdivide(0); //Sub divide the root node
tree
}
// Will update the node's AABB at bvh_nodes[index]
fn update_bvh_node_aabb(&mut self, index: usize) {
// We will make his node bound all its primitives
let bvh_node = &mut self.bvh_nodes[index]; // Current BVHNode
let bvh_node_aabb = &mut bvh_node.aabb; //Current node AABB
let first_prim = bvh_node.first_prim; //Start index of prim
let prim_count = bvh_node.prim_count; //Number of primitives within the nodes aabb
for i in 0..prim_count {
let node = &self.nodes[first_prim + i]; //Get the node from the Vec<Node>
bvh_node_aabb.join_mut(&node.aabb); //Join it with the BVH node's AABB
}
// unsafe {
// println!("UPDATE TO AABB ---- {STATIC0}");
// STATIC0 += 1;
// let bvh_node = &mut self.bvh_nodes[index]; //Get the BVHNode we are working on
// println!("{bvh_node}");
// }
}
// Subdivision, will subdivide a split
fn subdivide(&mut self, index: usize) {
//Get the bvh_node we will be altering
// Determine the axis and position of the split plane
// Split the group of primitives in two halves using the split plane
// Create child nodes for each half
// Recurse into each of the child nodes.
//Leaf node case, we cannot sub-divide any more
if self.bvh_nodes[index].prim_count == 1 {
return;
};
/* ------------ SUBDIVIDE BY LONGEST AXIS ------------ */
//Get information about the node we want to subdivide
let (bln, trf) = (
self.bvh_nodes[index].aabb.bln,
self.bvh_nodes[index].aabb.trf,
);
let extent = trf - bln;
let mut axis = 0; // Assume that x is longest
if extent.y > extent.x {
axis = 1; // Split y if longest
};
if extent.z > extent[axis] {
axis = 2; // Split z if longest
};
let split_pos = bln[axis] + extent[axis] * 0.5; // Final split down the middle of AABB
/* --------- SUBDIVIDE BY Surface Area Heuristic ---------*/
// let mut best_axis: Option<usize> = None;
// let mut best_pos = 0.0;
// let mut best_cost = 1e30;
// let first_prim_idx = self.bvh_nodes[index].first_prim;
// for axis in 0..3 {
// for i in 0..self.bvh_nodes[index].prim_count {
// let node = &self.nodes[first_prim_idx + i];
// //Get the centroid of the bounding box
// let centroid = node.aabb.get_centroid();
// //Get the candidate position
// let candidate_pos = world_centroid[axis];
// let cost = self.evaluate_sah(&self.bvh_nodes[index], axis, candidate_pos);
// if cost < best_cost {
// best_pos = candidate_pos;
// best_axis = Some(axis);
// best_cost = cost;
// }
// }
// }
// let axis = match best_axis {
// Some(axis) => axis,
// None => 0,
// };
// let split_pos = best_pos;
let left_count;
let right_count;
let mut i;
let mut j;
{
let bvh_node = &mut self.bvh_nodes[index];
i = bvh_node.first_prim; //Start of array
j = i + bvh_node.prim_count - 1; //End of array
while i <= j {
//Perform a quicksort dependent on location
let node = &self.nodes[i]; // Node we would like to sort
let centroid = node.aabb.get_centroid(); //Centroid of node we would like to sort
if centroid[axis] < split_pos {
i += 1; // On Left-Hand-Side
} else {
self.nodes.swap(i, j);
j -= 1; // On Right-Hand-Side
}
}
//Now we have two splits
//The lhs of the array is in the left split 0..left_count
//The rhs of the array is on the right split left_count + 1..n
left_count = i - bvh_node.first_prim; //Number of prims on lhs
right_count = bvh_node.prim_count - left_count;
//println!("SPLIT INTO: {left_count} {right_count}");
if left_count == 0 || left_count == bvh_node.prim_count {
//Split did nothing
return;
}
}
// unsafe {
// println!("SUBDIVIDE: {STATIC1}");
// println!("SPLIT INTO: {left_count} ");
// STATIC1 += 1;
// }
let l_idx = self.nodes_used; //Left child
self.bvh_nodes[index].l_idx = l_idx;
self.nodes_used = self.nodes_used + 2;
//Set left node information
self.bvh_nodes[l_idx].first_prim = self.bvh_nodes[index].first_prim; //Left split begins at parent split
self.bvh_nodes[l_idx].prim_count = left_count; // Left prims
//Set right node information
self.bvh_nodes[l_idx + 1].first_prim = i; // Right split start index
self.bvh_nodes[l_idx + 1].prim_count = right_count;
//Current node is not a leaf node
self.bvh_nodes[index].prim_count = 0;
self.update_bvh_node_aabb(l_idx); //Update AABB for left of split
self.update_bvh_node_aabb(l_idx + 1); //Update AABB for right of split
//Recurse
self.subdivide(l_idx); // Subdivide left index
self.subdivide(l_idx + 1); // SUbdivide right index
}
// Traverse the BVH, 0 will be needed to start at root node
pub fn traverse(&self, ray: &Ray, idx: usize) -> Option<(&Node, Intersection)> {
let bvh_node = &self.bvh_nodes[idx];
if !bvh_node.aabb.intersect_ray(&ray) {
// No intersection with BVH in world coordinates
return None;
}
if bvh_node.prim_count != 0 {
// Leaf node - check all primitives it contains
let mut closest: Option<(&Node, Intersection)> = None;
let mut closest_dist = f64::MAX;
for i in 0..bvh_node.prim_count {
let node = &self.nodes[bvh_node.first_prim + i];
if !node.active {
continue;
}
if let Some(intersect) = node.intersect_ray(&ray) {
if intersect.distance >= EPSILON && intersect.distance < closest_dist {
closest_dist = intersect.distance;
closest = Some((node, intersect));
}
}
}
return closest;
} else {
//Recurse down the BVH
//Recurse down the BVH right node
let intersect_l = self.traverse(ray, bvh_node.l_idx);
let intersect_r = self.traverse(ray, bvh_node.l_idx + 1);
match (intersect_l, intersect_r) {
(None, None) => return None,
(Some(intersect), None) => return Some(intersect),
(None, Some(intersect)) => return Some(intersect),
(Some((node_l, inter_l)), Some((node_r, inter_r))) => {
//Compare intersect distance
let dist_l = distance(&ray.a, &inter_l.point);
let dist_r = distance(&ray.a, &inter_r.point);
if dist_l < dist_r {
return Some((node_l, inter_l));
} else {
return Some((node_r, inter_r));
}
}
}
}
}
fn evaluate_sah(&self, node: &BVHNode, axis: usize, pos: f64) -> f64 {
// determine triangle counts and bounds for this split candidate
let mut l_aabb = AABB::empty();
let mut r_aabb = AABB::empty();
let mut l_count = 0;
let mut r_count = 0;
for i in 0..node.prim_count {
let aabb = self.nodes[node.first_prim + i].get_world_aabb();
if aabb.trf[axis] < pos {
l_count += 1;
l_aabb.join_mut(&aabb);
} else {
r_count += 1;
r_aabb.join_mut(&aabb);
}
}
let cost = l_count as f64 * l_aabb.area() + r_count as f64 * r_aabb.area();
if cost > 0.0 { cost } else { 1e30 }
}
}
impl fmt::Display for BVH {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
for (i, node) in self.bvh_nodes.iter().enumerate() {
writeln!(f, "Node: {i}")?;
writeln!(f, "{node}")?;
}
write!(f, "")
}
}

View File

@@ -51,6 +51,66 @@ impl Camera {
self.recalculate_matrix(); self.recalculate_matrix();
} }
/// Get the forward direction vector (from eye toward target)
pub fn forward(&self) -> Vector3<f64> {
(self.target - self.eye).normalize()
}
/// Get the right direction vector
pub fn right(&self) -> Vector3<f64> {
self.forward().cross(&self.up).normalize()
}
/// Move the camera forward/backward along its view direction (moves both eye and target)
pub fn move_forward(&mut self, amount: f64) {
let dir = self.forward() * amount;
self.eye += dir;
self.target += dir;
self.recalculate_matrix();
}
/// Strafe the camera left/right (moves both eye and target)
pub fn move_right(&mut self, amount: f64) {
let dir = self.right() * amount;
self.eye += dir;
self.target += dir;
self.recalculate_matrix();
}
/// Move the camera up/down along the up vector (moves both eye and target)
pub fn move_up(&mut self, amount: f64) {
let dir = self.up.normalize() * amount;
self.eye += dir;
self.target += dir;
self.recalculate_matrix();
}
/// Orbit the camera around the target point by yaw (horizontal) and pitch (vertical) angles in radians
pub fn orbit(&mut self, yaw: f64, pitch: f64) {
let offset = self.eye - self.target;
let radius = offset.norm();
// Current spherical angles
let current_pitch = (offset.y / radius).asin();
let current_yaw = offset.z.atan2(offset.x);
let new_yaw = current_yaw + yaw;
let new_pitch = (current_pitch + pitch).clamp(
-std::f64::consts::FRAC_PI_2 + 0.01,
std::f64::consts::FRAC_PI_2 - 0.01,
);
// Convert back to cartesian
let new_offset = Vector3::new(
radius * new_pitch.cos() * new_yaw.cos(),
radius * new_pitch.sin(),
radius * new_pitch.cos() * new_yaw.sin(),
);
self.eye = self.target + new_offset;
self.recalculate_matrix();
}
/// Recalculate the view and inverse view matrices based on the current eye, target, and up vectors /// Recalculate the view and inverse view matrices based on the current eye, target, and up vectors
fn recalculate_matrix(&mut self) { fn recalculate_matrix(&mut self) {
self._view = Matrix4::look_at_lh(&self.eye, &self.target, &self.up); self._view = Matrix4::look_at_lh(&self.eye, &self.target, &self.up);

View File

@@ -5,41 +5,49 @@ use crate::{
node::*, node::*,
primitive::*, primitive::*,
scene::*, scene::*,
state::{INIT_FILE, SAVE_FILE}, state::{RaytracingOption, INIT_FILE, SAVE_FILE},
}; };
use imgui::*; use imgui::*;
use nalgebra::{Point3, Vector3}; use nalgebra::{Point3, Vector3};
use pixels::{wgpu, PixelsContext}; use pixels::{wgpu, PixelsContext};
use rhai::Engine; use rhai::Engine;
use std::time::Instant; use std::time::{Duration, Instant};
//BUFFER CONSTANTS //BUFFER CONSTANTS
const BUFFER_PROPORTION_INIT: f32 = 0.2;
const BUFFER_PROPORTION_MIN: f32 = 0.1; const BUFFER_PROPORTION_MIN: f32 = 0.1;
const BUFFER_PROPORTION_MAX: f32 = 1.0; const BUFFER_PROPORTION_MAX: f32 = 1.0;
//RAY CONSTANTS //RAY CONSTANTS
const RAYS_INIT: i32 = 100; const MIN_THREADS: u32 = 1;
const RAYS_MIN: i32 = 100; const MAX_THREADS: u32 = 12;
const RAYS_MAX: i32 = 10000; const RAYS_MIN: u32 = 100;
const RAYS_MAX: u32 = 10000;
const MIN_DEPTH: u8 = 1;
const MAX_DEPTH: u8 = 10;
const MIN_SAMPLES: u32 = 1;
const MAX_SAMPLES: u32 = 10;
const MIN_RANDOM: f64 = 100.0;
const MAX_RANDOM: f64 = 1000.0;
const MIN_EPSILON: f64 = 1e-11;
const MAX_EPSILON: f64 = 1.0;
//DIFFUSE CONSTANTS
const MIN_DIFFUSE_RAYS: u8 = 1;
const MAX_DIFFUSE_RAYS: u8 = 10;
const MIN_DIFFUSE_COEFFICIENT: f32 = 0.0;
const MAX_DIFFUSE_COEFFICIENT: f32 = 1.0;
//MATERIAL CONSTANTS //MATERIAL CONSTANTS
const MIN_D: f32 = 0.0;
const MIN_S: f32 = 0.0;
const MIN_SHINE: f32 = 0.0; const MIN_SHINE: f32 = 0.0;
const MAX_D: f32 = 1.0;
const MAX_S: f32 = 1.0;
const MAX_SHINE: f32 = 50.0; const MAX_SHINE: f32 = 50.0;
//TRANSFORMATION CONSTANTS //TRANSFORMATION CONSTANTS
const MIN_COLOUR: f32 = 0.0;
const MIN_FALLOFF: f32 = 0.0; const MIN_FALLOFF: f32 = 0.0;
const MIN_SCALE: f64 = 0.0; const MIN_SCALE: f64 = 0.0;
//const MIN_POSITION: f64 = -10.0; //const MIN_POSITION: f64 = -10.0;
const MIN_ROTATION: f64 = -180.0; const MIN_ROTATION: f64 = -180.0;
const MIN_TRANSLATE: f64 = -10.0; const MIN_TRANSLATE: f64 = -10.0;
//-- //--
const MAX_COLOUR: f32 = 1.0;
const MAX_FALLOFF: f32 = 1.0; const MAX_FALLOFF: f32 = 1.0;
const MAX_SCALE: f64 = 3.0; const MAX_SCALE: f64 = 3.0;
//const MAX_POSITION: f64 = 10.0; //const MAX_POSITION: f64 = 10.0;
@@ -47,14 +55,14 @@ const MAX_ROTATION: f64 = 180.0;
const MAX_TRANSLATE: f64 = 10.0; const MAX_TRANSLATE: f64 = 10.0;
// CAMERA CONSTANTS // CAMERA CONSTANTS
const MIN_FOV: f32 = 10.0; const MIN_FOV: f64 = 10.0;
const MAX_FOV: f32 = 160.0; const MAX_FOV: f64 = 160.0;
//const CAMERA_INIT: f32 = 5.0; //const CAMERA_INIT: f32 = 5.0;
/// Manages all state required for rendering Dear ImGui over `Pixels`test. /// Manages all state required for rendering Dear ImGui over `Pixels`test.
pub enum GuiEvent { pub enum GuiEvent {
BufferResize(f32, f32), RaytracerOption(RaytracingOption),
CameraUpdate(Camera, f32), CameraUpdate(Camera),
SceneLoad(Scene), SceneLoad(Scene),
SaveImage(String), SaveImage(String),
} }
@@ -67,17 +75,17 @@ pub struct Gui {
pub event: Option<GuiEvent>, pub event: Option<GuiEvent>,
render_start: Option<Instant>,
render_elapsed: Option<Duration>,
script_filename: String, script_filename: String,
script: String, script: String,
engine: Engine, engine: Engine,
scene: Scene, scene: Scene,
pub ray_num: i32, raytracing_option: RaytracingOption,
buffer_proportion: f32,
camera: Camera, camera: Camera,
camera_fov: f32,
image_filename: String, image_filename: String,
} }
@@ -122,7 +130,7 @@ impl Gui {
let renderer = imgui_wgpu::Renderer::new(&mut imgui, device, queue, config); let renderer = imgui_wgpu::Renderer::new(&mut imgui, device, queue, config);
// Return GUI context // Return GUI context
Self { let mut gui = Self {
imgui, imgui,
platform, platform,
renderer, renderer,
@@ -130,18 +138,47 @@ impl Gui {
last_cursor: None, last_cursor: None,
event: None, event: None,
render_start: None,
render_elapsed: None,
script_filename: String::from(INIT_FILE), script_filename: String::from(INIT_FILE),
script: String::new(), script: String::new(),
engine: init_engine(), engine: init_engine(),
scene: Scene::empty(), scene: Scene::empty(),
ray_num: RAYS_INIT, raytracing_option: RaytracingOption::default(),
buffer_proportion: BUFFER_PROPORTION_INIT,
camera: Camera::unit(), camera: Camera::unit(),
camera_fov: 110.0,
image_filename: String::from(SAVE_FILE), image_filename: String::from(SAVE_FILE),
};
// ------------ TESTING CODE (LOAD SCENE ON START) -----------------
match std::fs::read_to_string(&mut gui.script_filename) {
Ok(script) => {
gui.script = script;
}
Err(e) => println!("{}", e),
}
match gui.engine.eval(&gui.script) {
Ok(scene) => {
gui.scene = scene;
gui.event = Some(GuiEvent::SceneLoad(gui.scene.clone()));
}
Err(e) => println!("{e}"),
}
// ------------ TESTING CODE (LOAD SCENE ON START) -----------------
gui
}
pub fn start_render_timer(&mut self) {
self.render_start = Some(Instant::now());
self.render_elapsed = None;
}
pub fn stop_render_timer(&mut self) {
if let Some(start) = self.render_start.take() {
self.render_elapsed = Some(start.elapsed());
} }
} }
@@ -183,38 +220,131 @@ impl Gui {
//Raytracing options ------------------------------------------- //Raytracing options -------------------------------------------
if CollapsingHeader::new("Raytracer").build(ui) { if CollapsingHeader::new("Raytracer").build(ui) {
// Numbers of rays to render
ui.slider("# Rays: ", RAYS_MIN, RAYS_MAX, &mut self.ray_num);
// Proportion of the window the buffer occupies
ui.slider( ui.slider(
"% Buffer: ", "Threads",
BUFFER_PROPORTION_MIN, MIN_THREADS,
BUFFER_PROPORTION_MAX, MAX_THREADS,
&mut self.buffer_proportion, &mut self.raytracing_option.threads,
); );
// Numbers of rays to render per pass
Drag::new("Rays Per Pass")
.range(RAYS_MIN, RAYS_MAX)
.speed(50.0)
.build(ui, &mut self.raytracing_option.pixels_per_thread);
// Proportion of the window the buffer occupies
Drag::new("% Buffer: ")
.range(BUFFER_PROPORTION_MIN, BUFFER_PROPORTION_MAX)
.speed(0.005)
.display_format("%.2f")
.build(ui, &mut self.raytracing_option.buffer_proportion);
//Clear colour for scene
let mut clear_f32 = [
self.raytracing_option.clear_color[0] as f32 / 255.0,
self.raytracing_option.clear_color[1] as f32 / 255.0,
self.raytracing_option.clear_color[2] as f32 / 255.0,
self.raytracing_option.clear_color[3] as f32 / 255.0,
];
if ui.color_edit4_config("Clear Colour", &mut clear_f32).alpha_bar(true).build() {
self.raytracing_option.clear_color = [
(clear_f32[0] * 255.0) as u8, (clear_f32[1] * 255.0) as u8,
(clear_f32[2] * 255.0) as u8, (clear_f32[3] * 255.0) as u8,
];
}
//Clear colour if no intersect
let mut pixel_clear_f32 = [
self.raytracing_option.pixel_clear[0] as f32 / 255.0,
self.raytracing_option.pixel_clear[1] as f32 / 255.0,
self.raytracing_option.pixel_clear[2] as f32 / 255.0,
self.raytracing_option.pixel_clear[3] as f32 / 255.0,
];
if ui.color_edit4_config("Pixel Clear Colour", &mut pixel_clear_f32).alpha_bar(true).build() {
self.raytracing_option.pixel_clear = [
(pixel_clear_f32[0] * 255.0) as u8, (pixel_clear_f32[1] * 255.0) as u8,
(pixel_clear_f32[2] * 255.0) as u8, (pixel_clear_f32[3] * 255.0) as u8,
];
}
//Ray depth slider
ui.slider(
"Ray Depth",
MIN_DEPTH,
MAX_DEPTH,
&mut self.raytracing_option.ray_depth,
);
//Ray samples slider
ui.slider(
"Ray Samples",
MIN_SAMPLES,
MAX_SAMPLES,
&mut self.raytracing_option.ray_samples,
);
//Ray randomness
Drag::new("Ray Randomness")
.range(MIN_RANDOM, MAX_RANDOM)
.speed(5.0)
.display_format("%.1f")
.build(ui, &mut self.raytracing_option.ray_randomness);
//Number of diffuse rays
ui.slider(
"Diffuse Rays",
MIN_DIFFUSE_RAYS,
MAX_DIFFUSE_RAYS,
&mut self.raytracing_option.diffuse_rays,
);
//Diffuse Coefficient
Drag::new("Diffuse Coefficient")
.range(MIN_DIFFUSE_COEFFICIENT, MAX_DIFFUSE_COEFFICIENT)
.speed(0.005)
.display_format("%.3f")
.build(ui, &mut self.raytracing_option.diffuse_coefficient);
// Fov of the buffer // Fov of the buffer
ui.slider("fov", MIN_FOV, MAX_FOV, &mut self.camera_fov); ui.slider(
"fov",
MIN_FOV,
MAX_FOV,
&mut self.raytracing_option.buffer_fov,
);
// Enable BVH
ui.checkbox("Enable BVH", &mut self.raytracing_option.bvh_active);
ui.checkbox("Enable Shadows", &mut self.raytracing_option.shadows);
ui.checkbox("Enable Reflections", &mut self.raytracing_option.reflect);
ui.checkbox("Enable Specular", &mut self.raytracing_option.specular);
ui.checkbox("Enable Diffuse", &mut self.raytracing_option.diffuse);
// Render timer display
ui.separator();
if let Some(start) = &self.render_start {
let elapsed = start.elapsed().as_secs_f64();
ui.text(format!("Rendering: {:.2}s", elapsed));
} else if let Some(elapsed) = &self.render_elapsed {
ui.text(format!("Render time: {:.2}s", elapsed.as_secs_f64()));
}
ui.separator();
// Apply stored changes // Apply stored changes
if ui.button("Apply") { if ui.button("Apply") {
self.event = Some(GuiEvent::BufferResize( self.event = Some(GuiEvent::RaytracerOption(self.raytracing_option.clone()));
self.buffer_proportion,
self.camera_fov,
));
}; };
} }
// CAMERA OPTIONS ---------------------------------------- // CAMERA OPTIONS ----------------------------------------
if CollapsingHeader::new("Camera").build(ui) { if CollapsingHeader::new("Camera").build(ui) {
// Eye, target and up vector inputs // Eye, target and up vector inputs
ui.text("Camera options:"); ui.text("Camera options:");
ui.slider_config("Eye", MIN_TRANSLATE, MAX_TRANSLATE) Drag::new("Eye")
.build_array(self.camera.eye.coords.as_mut_slice()); .range(MIN_TRANSLATE, MAX_TRANSLATE)
ui.slider_config("Target", MIN_TRANSLATE, MAX_TRANSLATE) .speed(0.05)
.build_array(self.camera.target.coords.as_mut_slice()); .display_format("%.2f")
ui.slider_config("Up", 0.0, 1.0) .build_array(ui, self.camera.eye.coords.as_mut_slice());
.build_array(self.camera.up.as_mut_slice()); Drag::new("Target")
.range(MIN_TRANSLATE, MAX_TRANSLATE)
.speed(0.05)
.display_format("%.2f")
.build_array(ui, self.camera.target.coords.as_mut_slice());
Drag::new("Up")
.range(0.0, 1.0)
.speed(0.005)
.display_format("%.3f")
.build_array(ui, self.camera.up.as_mut_slice());
if ui.button("Apply Camera") { if ui.button("Apply Camera") {
println!("Camera changed"); println!("Camera changed");
self.event = Some(GuiEvent::CameraUpdate(self.camera.clone(), self.camera_fov)); self.event = Some(GuiEvent::CameraUpdate(self.camera.clone()));
} }
} }
// SCRIPTING -------------------------------------------- // SCRIPTING --------------------------------------------
@@ -266,9 +396,7 @@ impl Gui {
// SCENE -------------------------------------------- // SCENE --------------------------------------------
if CollapsingHeader::new("Scene").build(ui) { if CollapsingHeader::new("Scene").build(ui) {
if ui.button("Update Scene") { if ui.button("Update Scene") {
for (_, node) in &mut self.scene.nodes { self.scene.compute();
node.compute();
}
self.event = Some(GuiEvent::SceneLoad(self.scene.clone())); self.event = Some(GuiEvent::SceneLoad(self.scene.clone()));
} }
// Edit transformation of nodes // Edit transformation of nodes
@@ -277,12 +405,21 @@ impl Gui {
ui.checkbox(format!("##active{label}"), &mut node.active); ui.checkbox(format!("##active{label}"), &mut node.active);
ui.same_line(); ui.same_line();
if let Some(_t) = ui.tree_node(label) { if let Some(_t) = ui.tree_node(label) {
ui.slider_config("Translation", MIN_TRANSLATE, MAX_TRANSLATE) Drag::new("Translation")
.build_array(&mut node.translation); .range(MIN_TRANSLATE, MAX_TRANSLATE)
ui.slider_config("Rotation", MIN_ROTATION, MAX_ROTATION) .speed(0.05)
.build_array(&mut node.rotation); .display_format("%.2f")
ui.slider_config("Scale", MIN_SCALE, MAX_SCALE) .build_array(ui, &mut node.translation);
.build_array(&mut node.scale); Drag::new("Rotation")
.range(MIN_ROTATION, MAX_ROTATION)
.speed(1.0)
.display_format("%.1f")
.build_array(ui, &mut node.rotation);
Drag::new("Scale")
.range(MIN_SCALE, MAX_SCALE)
.speed(0.01)
.display_format("%.3f")
.build_array(ui, &mut node.scale);
} }
} }
} }
@@ -290,11 +427,19 @@ impl Gui {
if let Some(_t) = ui.tree_node("Materials") { if let Some(_t) = ui.tree_node("Materials") {
for (label, material) in &mut self.scene.materials { for (label, material) in &mut self.scene.materials {
if let Some(_t) = ui.tree_node(label) { if let Some(_t) = ui.tree_node(label) {
ui.slider_config("ks", MIN_D, MAX_D) let mut ks_arr: [f32; 3] = material.ks.into();
.build_array(material.ks.as_mut_slice()); if ui.color_edit3("ks", &mut ks_arr) {
ui.slider_config("kd", MIN_S, MAX_S) material.ks = Vector3::from(ks_arr);
.build_array(material.kd.as_mut_slice()); }
ui.slider("shine", MIN_SHINE, MAX_SHINE, &mut material.shininess); let mut kd_arr: [f32; 3] = material.kd.into();
if ui.color_edit3("kd", &mut kd_arr) {
material.kd = Vector3::from(kd_arr);
}
Drag::new("shine")
.range(MIN_SHINE, MAX_SHINE)
.speed(0.5)
.display_format("%.1f")
.build(ui, &mut material.shininess);
} }
} }
} }
@@ -304,12 +449,20 @@ impl Gui {
ui.checkbox(format!("##activelight{label}"), &mut light.active); ui.checkbox(format!("##activelight{label}"), &mut light.active);
ui.same_line(); ui.same_line();
if let Some(_t) = ui.tree_node(label) { if let Some(_t) = ui.tree_node(label) {
ui.slider_config("Colour", MIN_COLOUR, MAX_COLOUR) let mut colour_arr: [f32; 3] = light.colour.into();
.build_array(light.colour.as_mut_slice()); if ui.color_edit3("Colour", &mut colour_arr) {
ui.slider_config("Position", MIN_TRANSLATE, MAX_TRANSLATE) light.colour = Vector3::from(colour_arr);
.build_array(light.position.coords.as_mut_slice()); }
ui.slider_config("Falloff", MIN_FALLOFF, MAX_FALLOFF) Drag::new("Position")
.build_array(light.falloff.as_mut_slice()); .range(MIN_TRANSLATE, MAX_TRANSLATE)
.speed(0.05)
.display_format("%.2f")
.build_array(ui, light.position.coords.as_mut_slice());
Drag::new("Falloff")
.range(MIN_FALLOFF, MAX_FALLOFF)
.speed(0.005)
.display_format("%.3f")
.build_array(ui, light.falloff.as_mut_slice());
} }
} }
} }
@@ -318,7 +471,7 @@ impl Gui {
for (label, camera) in &self.scene.cameras { for (label, camera) in &self.scene.cameras {
if ui.button(label) { if ui.button(label) {
self.camera = camera.clone(); self.camera = camera.clone();
self.event = Some(GuiEvent::CameraUpdate(camera.clone(), self.camera_fov)); self.event = Some(GuiEvent::CameraUpdate(camera.clone()));
} }
} }
} }
@@ -346,6 +499,11 @@ impl Gui {
) )
} }
/// Update the GUI's camera to reflect external changes (e.g. from keyboard/mouse movement)
pub fn update_camera(&mut self, camera: &Camera) {
self.camera = camera.clone();
}
/// Handle any outstanding events. /// Handle any outstanding events.
pub fn handle_event( pub fn handle_event(
&mut self, &mut self,
@@ -450,8 +608,8 @@ pub fn init_engine() -> Engine {
.register_type::<Mesh>() .register_type::<Mesh>()
.register_fn("Mesh", Mesh::from_file); .register_fn("Mesh", Mesh::from_file);
engine engine
.register_type::<Rectangle>() .register_type::<RectangleXY>()
.register_fn("Rectange", Rectangle::new) .register_fn("Rectange", RectangleXY::new)
.register_fn("RectangleUnit", Rectangle::unit); .register_fn("RectangleUnit", RectangleXY::unit);
engine engine
} }

View File

@@ -1,10 +1,11 @@
use crate::state::run; use crate::state::run;
use error_iter::ErrorIter; use error_iter::ErrorIter;
const EPSILON: f64 = 1e-8; const EPSILON: f64 = 1e-7;
const INFINITY: f64 = 1e10; const INFINITY: f64 = 1e10;
use log::error; use log::error;
//use nalgebra::{Matrix4, RowVector4, Vector3, Vector4};
use std::env; use std::env;
use std::error::Error; use std::error::Error;
@@ -21,13 +22,159 @@ mod state;
fn main() { fn main() {
env_logger::init(); env_logger::init();
env::set_var("RUST_BACKTRACE", "1"); env::set_var("RUST_BACKTRACE", "1");
//let args: Vec<String> = env::args().collect();
// let vec = Vector3::new(1.0, 1.0, 1.0);
// let translation = Vector3::new(1.0, 1.0, 1.0);
// let translation_matrix = Matrix4::new_translation(&translation);
// println!(
// "{}, {}",
// translation_matrix,
// translation_matrix.transform_vector(&vec)
// );
// let mut translation_matrix = translation_matrix.transpose();
// translation_matrix.set_row(3, &RowVector4::new(0.0, 0.0, 0.0, 0.0));
// println!(
// "{}, {}", // translation_matrix, // translation_matrix.transform_vector(&vec)
// );
if let Err(e) = run() { if let Err(e) = run() {
println!("Error at runtime: {}", e); println!("Error at runtime: {}", e);
}; };
// if args.len() == 6 {
// let width: usize = args[1].parse().unwrap();
// let height: usize = args[2].parse().unwrap();
// let fovy = args[3].parse::<f64>().unwrap();
// let filename = &args[4];
// let savefile = &args[5];
// headless(
// width,
// height,
// fovy,
// filename.to_string(),
// savefile.to_string(),
// );
// } else {
//}
} }
// fn headless(width: usize, height: usize, fovy: f64, filename: String, savefile: String) {
// let options = Arc::new(RaytracingOption {
// threads: 12,
// ray_samples: 1,
// ray_randomness: 100.0,
// clear_color: [0x22, 0x00, 0x11, 0x55],
// pixel_clear: [0x55, 0x00, 0x22, 0x55],
// pixels_per_thread: 200,
// buffer_proportion: 1.0,
// buffer_fov: 110.0,
// ray_depth: 5,
// diffuse_rays: 3,
// diffuse_coefficient: 0.8,
// bvh_active: false,
// });
// //Read script from file
// let script = match std::fs::read_to_string(&filename) {
// Ok(in_script) => in_script,
// Err(e) => {
// println!("{}", e);
// return;
// }
// };
// //Evaluate scene in file
// let engine = init_engine();
// let scene: Arc<Scene> = match engine.eval(&script) {
// Ok(in_scene) => Arc::new(in_scene),
// Err(e) => {
// println!("{e}");
// return;
// }
// };
// //Set the camera
// let mut camera = Camera::unit();
// for (_, in_camera) in &scene.cameras {
// camera = in_camera.clone();
// }
// //Cast the rays
// let rays = Arc::new(Ray::cast_rays(
// &camera.eye,
// &camera.target,
// &camera.up,
// fovy,
// width as u32,
// height as u32,
// ));
// //Enable bounding volume heirarchy
// let bvh;
// match options.bvh_active {
// true => bvh = Arc::new(Some(BVH::build(&scene.nodes))),
// false => bvh = Arc::new(None),
// }
// //Create our frame and indexer
// let size = width * height;
// let frame_mutex = Arc::new(Mutex::new(vec![0; size * 4]));
// //Multithreading
// let mut handles = vec![];
// for index in 0..size {
// for _ in 0..options.threads {
// //Get random index from queue
// //Create a nre thread for this pixel
// let handle = thread::spawn({
// let rays = rays.clone();
// let scene = scene.clone();
// let options = options.clone();
// let bvh = bvh.clone();
// let rays = rays.clone();
// let frame_mutex = frame_mutex.clone();
// move || {
// //Shade colour for selected ray
// let mut colour: Vector3<f32> = Vector3::zeros();
// //Get the ray we want to make
// let shot_ray = &rays[index];
// //Send out ray_samples rays
// for _ in 0..options.ray_samples {
// let point = shot_ray.a;
// let dir = shot_ray.b;
// //Generate a random ray
// let rx = (random::<f64>() - 0.5) / options.ray_randomness;
// let ry = (random::<f64>() - 0.5) / options.ray_randomness;
// let rz = (random::<f64>() - 0.5) / options.ray_randomness;
// let nx = dir.x + rx;
// let ny = dir.y + ry;
// let nz = dir.z + rz;
// let rand_ray = Ray::new(point, Vector3::new(nx, ny, nz));
// if let Some(ray_colour) = rand_ray.shade_ray(&scene, 0, &options, &bvh) {
// colour += ray_colour;
// }
// }
// colour = (colour / options.ray_samples as f32) * 255.0;
// let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff];
// {
// let frame = &mut frame_mutex.lock().unwrap();
// frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba);
// }
// }
// });
// handles.push(handle);
// }
// for handle in handles.drain(..) {
// handle.join().unwrap();
// }
// }
// use std::path::Path;
// image::save_buffer(
// Path::new(&savefile),
// &frame_mutex.lock().unwrap(),
// width as u32,
// height as u32,
// image::ColorType::Rgba8,
// )
// .unwrap();
// }
fn log_error<E: Error + 'static>(method_name: &str, err: E) { fn log_error<E: Error + 'static>(method_name: &str, err: E) {
error!("{method_name}() failed: {err}"); error!("{method_name}() failed: {err}");
for source in err.sources().skip(1) { for source in err.sources().skip(1) {

View File

@@ -10,10 +10,10 @@ pub struct Material {
} }
impl Material { impl Material {
pub fn new(kd: Vector3<f64>, ks: Vector3<f64>, shininess: f64) -> Material { pub fn new(kd: Vector3<f64>, ks: Vector3<f64>, kr: Vector3<f64>, shininess: f64) -> Material {
let kd = kd.cast(); let kd = kd.cast();
let ks = ks.cast(); let ks = ks.cast();
let kr = ks.cast(); let kr = kr.cast();
let shininess = shininess as f32; let shininess = shininess as f32;
Material { Material {
kd, kd,

View File

@@ -1,12 +1,19 @@
use crate::{material::Material, primitive::*}; use crate::{
use nalgebra::{Matrix4, Vector3}; bvh::AABB,
use std::rc::Rc; material::Material,
primitive::*,
ray::{Intersection, Ray},
EPSILON,
};
use nalgebra::{distance, Matrix3, Matrix4, Vector3};
use std::sync::Arc;
#[derive(Clone)] #[derive(Clone)]
pub struct Node { pub struct Node {
//Primitive //Primitive
pub primitive: Rc<dyn Primitive>, pub primitive: Arc<dyn Primitive>,
pub material: Material, pub material: Material,
pub aabb: AABB,
//Transformations //Transformations
pub rotation: [f64; 3], pub rotation: [f64; 3],
pub scale: [f64; 3], pub scale: [f64; 3],
@@ -14,27 +21,30 @@ pub struct Node {
//Model matricies //Model matricies
pub model: Matrix4<f64>, pub model: Matrix4<f64>,
pub inv_model: Matrix4<f64>, pub inv_model: Matrix4<f64>,
pub inv_transpose_model: Matrix3<f64>,
//If the node is active
pub active: bool, pub active: bool,
} }
impl Node { impl Node {
//New node with no transformations //New node with no transformations
pub fn new(primitive: Rc<dyn Primitive>, material: Material) -> Node { pub fn new(primitive: Arc<dyn Primitive>, material: Material) -> Node {
let aabb = primitive.get_aabb();
Node { Node {
primitive, primitive,
material, material,
aabb,
rotation: [0.0, 0.0, 0.0], rotation: [0.0, 0.0, 0.0],
scale: [1.0, 1.0, 1.0], scale: [1.0, 1.0, 1.0],
translation: [0.0, 0.0, 0.0], translation: [0.0, 0.0, 0.0],
model: Matrix4::identity(), model: Matrix4::identity(),
inv_model: Matrix4::identity(), inv_model: Matrix4::identity(),
inv_transpose_model: Matrix3::identity(),
active: true, active: true,
} }
} }
//New node with parent transformations //New node with parent transformations
pub fn child(self, primitive: Rc<dyn Primitive>) -> Node { pub fn child(self, primitive: Arc<dyn Primitive>) -> Node {
let mut child = self.clone(); let mut child = self.clone();
child.primitive = primitive; child.primitive = primitive;
child child
@@ -46,12 +56,6 @@ impl Node {
//Rotate a mesh by adding to its rotation //Rotate a mesh by adding to its rotation
pub fn rotate(&mut self, roll: f64, pitch: f64, yaw: f64) { pub fn rotate(&mut self, roll: f64, pitch: f64, yaw: f64) {
//Convert to radians
let roll = roll.to_radians();
// Convert pitch and yaw to radians
let pitch = pitch.to_radians();
let yaw = yaw.to_radians();
// Add the roll, pitch, and yaw to the current rotation // Add the roll, pitch, and yaw to the current rotation
self.rotation[0] += roll; self.rotation[0] += roll;
self.rotation[1] += pitch; self.rotation[1] += pitch;
@@ -71,9 +75,9 @@ impl Node {
} }
// Scale a mesh by adding to its current scale // Scale a mesh by adding to its current scale
pub fn scale(&mut self, x: f64, y: f64, z: f64) { pub fn scale(&mut self, x: f64, y: f64, z: f64) {
self.scale[0] += x; self.scale[0] = x;
self.scale[1] += y; self.scale[1] = y;
self.scale[2] += z; self.scale[2] = z;
// Recompute the model and inverse model matrices // Recompute the model and inverse model matrices
self.compute(); self.compute();
@@ -88,10 +92,32 @@ impl Node {
let scale_matrix = Matrix4::new_nonuniform_scaling(&scale); let scale_matrix = Matrix4::new_nonuniform_scaling(&scale);
// Rotation matrix // Rotation matrix
let (roll, pitch, yaw) = (self.rotation[0], self.rotation[1], self.rotation[2]); let (roll, pitch, yaw) = (self.rotation[0], self.rotation[1], self.rotation[2]);
let rotation_matrix = Matrix4::from_euler_angles(roll, pitch, yaw); let rotation_matrix =
Matrix4::from_euler_angles(roll.to_radians(), pitch.to_radians(), yaw.to_radians());
// Compute the model matrix by combining the translation, rotation, and scale matrices // Compute the model matrix by combining the translation, rotation, and scale matrices
self.model = (translation_matrix * rotation_matrix * scale_matrix).cast(); self.model = (translation_matrix * rotation_matrix * scale_matrix).cast();
// Compute the inverse model matrix by inverting the model matrix // Compute the inverse model matrix by inverting the model matrix
self.inv_model = self.model.try_inverse().unwrap(); self.inv_model = self.model.try_inverse().unwrap();
self.inv_transpose_model = self.inv_model.transpose().remove_row(3).remove_column(3);
// Reset AABB from primitive local space before transforming to world space
self.aabb = self.primitive.get_aabb();
self.aabb.transform_mut(&self.model);
}
// Intersection of a ray, will convert to model coords and check
pub fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
let local_ray = ray.transform(&self.inv_model); //Transform from world coordinates
if let Some(mut intersect) = self.primitive.intersect_ray(&local_ray) {
if intersect.distance < EPSILON {
return None;
}
intersect.transform_mut(&self.model, &self.inv_transpose_model); //Transform to world coords
intersect.distance = distance(&intersect.point, &ray.a); // use world-space ray origin
return Some(intersect);
}
return None;
}
//Gets the bounding box in world coords
pub fn get_world_aabb(&self) -> AABB {
return self.aabb.clone();
} }
} }

View File

@@ -9,11 +9,11 @@ use nalgebra::{distance, Point3, Vector3};
use roots::{find_roots_quadratic, find_roots_quartic, Roots}; use roots::{find_roots_quadratic, find_roots_quartic, Roots};
use std::fs::File; use std::fs::File;
use std::io::{BufRead, BufReader}; use std::io::{BufRead, BufReader};
use std::rc::Rc; use std::sync::Arc;
// PRIMITIVE TRAIT ----------------------------------------------------------------- // PRIMITIVE TRAIT -----------------------------------------------------------------
pub trait Primitive { pub trait Primitive: Send + Sync {
fn intersect_ray(&self, ray: &Ray) -> Option<Intersection>; fn intersect_ray(&self, ray: &Ray) -> Option<Intersection>;
fn intersect_bounding_box(&self, ray: &Ray) -> bool; fn get_aabb(&self) -> AABB;
} }
// SPHERE ----------------------------------------------------------------- // SPHERE -----------------------------------------------------------------
@@ -21,23 +21,14 @@ pub trait Primitive {
pub struct Sphere { pub struct Sphere {
position: Point3<f64>, position: Point3<f64>,
radius: f64, radius: f64,
bounding_box: AABB,
} }
impl Sphere { impl Sphere {
pub fn new(position: Point3<f64>, radius: f64) -> Rc<dyn Primitive> { pub fn new(position: Point3<f64>, radius: f64) -> Arc<dyn Primitive> {
let radius_vec = Vector3::new(radius, radius, radius); Arc::new(Sphere { position, radius })
let bln = position - radius_vec;
let trf = position + radius_vec;
let bounding_box = AABB::new(bln, trf);
Rc::new(Sphere {
position,
radius,
bounding_box,
})
} }
pub fn unit() -> Rc<dyn Primitive> { pub fn unit() -> Arc<dyn Primitive> {
Sphere::new(Point3::new(0.0, 0.0, 0.0), 1.0) Sphere::new(Point3::new(0.0, 0.0, 0.0), 1.0)
} }
} }
@@ -56,14 +47,13 @@ impl Primitive for Sphere {
Roots::No(_) => return None, Roots::No(_) => return None,
Roots::One([x1]) => x1, Roots::One([x1]) => x1,
Roots::Two([x1, x2]) => { Roots::Two([x1, x2]) => {
// roots are returned in ascending order: x1 <= x2
if x1 <= 0.0 && x2 <= 0.0 { if x1 <= 0.0 && x2 <= 0.0 {
return None; return None;
} else { } else if x1 <= 0.0 {
if x1.abs() < x2.abs() {
x1
} else {
x2 x2
} } else {
x1
} }
} }
_ => return None, _ => return None,
@@ -78,8 +68,12 @@ impl Primitive for Sphere {
}) })
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
return self.bounding_box.intersect_bounding_box(ray); let radius = self.radius;
let radius_vec = Vector3::new(radius, radius, radius);
let bln = self.position - radius_vec;
let trf = self.position + radius_vec;
AABB::new(bln, trf)
} }
} }
@@ -90,28 +84,32 @@ pub struct Circle {
radius: f64, radius: f64,
normal: Vector3<f64>, normal: Vector3<f64>,
constant: f64, constant: f64,
bounding_box: AABB,
} }
impl Circle { impl Circle {
pub fn new(position: Point3<f64>, radius: f64, normal: Vector3<f64>) -> Rc<dyn Primitive> { pub fn new(position: Point3<f64>, radius: f64, normal: Vector3<f64>) -> Arc<dyn Primitive> {
let radius_vec = Vector3::new(radius, radius, radius);
let bln = position - radius_vec;
let trf = position + radius_vec;
let bounding_box = AABB::new(bln, trf);
let normal = normal.normalize(); let normal = normal.normalize();
let constant = normal.dot(&position.coords); let constant = normal.dot(&position.coords);
Rc::new(Circle { Arc::new(Circle {
position, position,
radius, radius,
normal, normal,
constant, constant,
bounding_box,
}) })
} }
pub fn unit() -> Rc<dyn Primitive> { pub fn new_unboxed(position: Point3<f64>, radius: f64, normal: Vector3<f64>) -> Circle {
let normal = normal.normalize();
let constant = normal.dot(&position.coords);
Circle {
position,
radius,
normal,
constant,
}
}
pub fn unit() -> Arc<dyn Primitive> {
let position = Point3::new(0.0, 0.0, 0.0); let position = Point3::new(0.0, 0.0, 0.0);
let normal = Vector3::new(0.0, 0.0, -1.0); let normal = Vector3::new(0.0, 0.0, -1.0);
let radius = 1.0; let radius = 1.0;
@@ -125,9 +123,9 @@ impl Primitive for Circle {
let n_dot_b = ray.b.dot(&self.normal); let n_dot_b = ray.b.dot(&self.normal);
let t = (self.constant - n_dot_a) / n_dot_b; let t = (self.constant - n_dot_a) / n_dot_b;
if t > INFINITY { if t <= 0.0 || t > INFINITY {
return None; return None;
}; }
let intersect = ray.at_t(t); let intersect = ray.at_t(t);
//Distance to center of circle //Distance to center of circle
@@ -144,8 +142,13 @@ impl Primitive for Circle {
} }
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let radius = self.radius;
let position = self.position;
let radius_vec = Vector3::new(radius, radius, radius);
let bln = position - radius_vec;
let trf = position + radius_vec;
AABB::new(bln, trf)
} }
} }
@@ -154,31 +157,27 @@ impl Primitive for Circle {
pub struct Cylinder { pub struct Cylinder {
radius: f64, radius: f64,
height: f64, height: f64,
base_circle: Rc<dyn Primitive>, base_circle: Circle,
top_circle: Rc<dyn Primitive>, top_circle: Circle,
bounding_box: AABB,
} }
impl Cylinder { impl Cylinder {
pub fn new(radius: f64, height: f64) -> Rc<dyn Primitive> { pub fn new(radius: f64, height: f64) -> Arc<dyn Primitive> {
let base_circle = Circle::new( let base_circle = Circle::new_unboxed(
Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 0.0),
radius, radius,
Vector3::new(0.0, -1.0, 0.0), Vector3::new(0.0, -1.0, 0.0),
); );
let top_circle = Circle::new( let top_circle = Circle::new_unboxed(
Point3::new(0.0, height, 0.0), Point3::new(0.0, height, 0.0),
radius, radius,
Vector3::new(0.0, 1.0, 0.0), Vector3::new(0.0, 1.0, 0.0),
); );
let bln = Point3::new(-radius, 0.0, -radius); Arc::new(Cylinder {
let trf = Point3::new(radius, height, radius);
Rc::new(Cylinder {
radius, radius,
height, height,
base_circle, base_circle,
top_circle, top_circle,
bounding_box: AABB { bln, trf },
}) })
} }
} }
@@ -197,14 +196,13 @@ impl Primitive for Cylinder {
Roots::No(_) => return None, Roots::No(_) => return None,
Roots::One([x1]) => Some(x1), Roots::One([x1]) => Some(x1),
Roots::Two([x1, x2]) => { Roots::Two([x1, x2]) => {
// roots are returned in ascending order: x1 <= x2
if x1 <= 0.0 && x2 <= 0.0 { if x1 <= 0.0 && x2 <= 0.0 {
return None; return None;
} else { } else if x1 <= 0.0 {
if x1.abs() < x2.abs() {
Some(x1)
} else {
Some(x2) Some(x2)
} } else {
Some(x1)
} }
} }
_ => return None, _ => return None,
@@ -261,8 +259,12 @@ impl Primitive for Cylinder {
} }
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let radius = self.radius;
let height = self.height;
let bln = Point3::new(-radius, 0.0, -radius);
let trf = Point3::new(radius, height, radius);
AABB::new(bln, trf)
} }
} }
@@ -271,28 +273,24 @@ impl Primitive for Cylinder {
pub struct Cone { pub struct Cone {
height: f64, height: f64,
constant: f64, constant: f64,
circle: Rc<dyn Primitive>, circle: Circle,
bounding_box: AABB,
} }
impl Cone { impl Cone {
pub fn new(radius: f64, height: f64) -> Rc<dyn Primitive> { pub fn new(radius: f64, height: f64) -> Arc<dyn Primitive> {
let circle = Circle::new( let circle = Circle::new_unboxed(
Point3::new(0.0, 0.0, 0.0), Point3::new(0.0, 0.0, 0.0),
radius, radius,
Vector3::new(0.0, -1.0, 0.0), Vector3::new(0.0, -1.0, 0.0),
); );
let bln = Point3::new(-radius, 0.0, -radius);
let trf = Point3::new(radius, height, radius);
let constant = radius * radius / (height * height); let constant = radius * radius / (height * height);
Rc::new(Cone { Arc::new(Cone {
height, height,
constant, constant,
circle, circle,
bounding_box: AABB { bln, trf },
}) })
} }
pub fn unit() -> Rc<dyn Primitive> { pub fn unit() -> Arc<dyn Primitive> {
Cone::new(0.5, 1.0) Cone::new(0.5, 1.0)
} }
@@ -325,14 +323,13 @@ impl Primitive for Cone {
Roots::No(_) => None, Roots::No(_) => None,
Roots::One([x1]) => Some(x1), Roots::One([x1]) => Some(x1),
Roots::Two([x1, x2]) => { Roots::Two([x1, x2]) => {
// roots are returned in ascending order: x1 <= x2
if x1 <= 0.0 && x2 <= 0.0 { if x1 <= 0.0 && x2 <= 0.0 {
None None
} else { } else if x1 <= 0.0 {
if x1.abs() < x2.abs() {
Some(x1)
} else {
Some(x2) Some(x2)
} } else {
Some(x1)
} }
} }
_ => None, _ => None,
@@ -359,90 +356,71 @@ impl Primitive for Cone {
(None, None) => None, (None, None) => None,
(Some(cone_intersect), None) => Some(cone_intersect), (Some(cone_intersect), None) => Some(cone_intersect),
(None, Some(circle_intersect)) => Some(circle_intersect), (None, Some(circle_intersect)) => Some(circle_intersect),
(Some(cone_intersect), Some(_)) => Some(cone_intersect), (Some(cone_intersect), Some(circle_intersect)) => {
let cone_dist = distance(&ray.a, &cone_intersect.point);
let circle_dist = distance(&ray.a, &circle_intersect.point);
if cone_dist < circle_dist {
Some(cone_intersect)
} else {
Some(circle_intersect)
}
}
} }
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let height = self.height;
let radius = (self.constant * height * height).sqrt();
let bln = Point3::new(-radius, 0.0, -radius);
let trf = Point3::new(radius, height, radius);
AABB::new(bln, trf)
} }
} }
// RECTANGLE ----------------------------------------------------------------- // RECTANGLE -----------------------------------------------------------------
// Normal is (0.0, 0.0, 1.0) always facing towards camera at positive z axis
#[derive(Clone)] #[derive(Clone)]
pub struct Rectangle { pub struct RectangleXY {
position: Point3<f64>, bl: Point3<f64>,
normal: Vector3<f64>, tr: Point3<f64>,
width_direction: Vector3<f64>,
width: f64,
height: f64,
bounding_box: AABB,
} }
impl Rectangle { impl RectangleXY {
pub fn new( pub fn new(bl: Point3<f64>, tr: Point3<f64>) -> Arc<dyn Primitive> {
position: Point3<f64>, Arc::new(RectangleXY { bl, tr })
normal: Vector3<f64>,
width_direction: Vector3<f64>,
width: f64,
height: f64,
) -> Rc<dyn Primitive> {
let normal = normal.normalize();
let width_direction = width_direction.normalize();
let height_direction = width_direction.cross(&normal);
let bln = position - width / 2.0 * width_direction - height / 2.0 * height_direction;
let trf = position + width / 2.0 * width_direction + height / 2.0 * height_direction;
Rc::new(Rectangle {
position,
normal: normal.normalize(),
width_direction: width_direction.normalize(),
width,
height,
bounding_box: AABB { bln, trf },
})
} }
pub fn unit() -> Rc<dyn Primitive> { pub fn unit() -> Arc<dyn Primitive> {
Rectangle::new( RectangleXY::new(Point3::new(-1.0, -1.0, 0.0), Point3::new(1.0, 1.0, 0.0))
Point3::new(0.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0),
Vector3::new(1.0, 0.0, 0.0),
2.0,
2.0,
)
} }
} }
impl Primitive for Rectangle { impl Primitive for RectangleXY {
fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> { fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
let constant = self.position.coords.dot(&self.normal); let z = self.bl.z;
let denominator = ray.b.dot(&self.normal); let az = ray.a.z;
let t = (constant - ray.a.coords.dot(&self.normal)) / denominator; let bz = ray.b.z;
let t = (z - az) / bz;
if t <= 0.0 || t > INFINITY {
return None;
}
let intersect = ray.at_t(t);
let (ix, iy) = (intersect.x, intersect.y);
if t > INFINITY { if (ix < self.bl.x) || (ix > self.tr.x) || (iy < self.bl.y) || (iy > self.tr.y) {
return None; return None;
} }
let intersect = ray.at_t(t); Some(Intersection {
let height_direction = self.width_direction.cross(&self.normal);
let (w2, h2) = (self.width / 2.0, self.height / 2.0);
let r1 = w2 * self.width_direction;
let r2 = h2 * height_direction;
let pi = intersect - self.position;
let pi_dot_r1 = pi.dot(&r1);
let pi_dot_r2 = pi.dot(&r2);
if pi_dot_r1 >= -w2 && pi_dot_r1 <= w2 && pi_dot_r2 >= -h2 && pi_dot_r2 <= h2 {
return Some(Intersection {
point: intersect, point: intersect,
normal: self.normal, normal: Vector3::new(0.0, 0.0, 1.0),
distance: t, distance: t,
}); })
}
None
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let bl = self.bl + Vector3::new(0.0, 0.0, -0.1);
let tr = self.tr + Vector3::new(0.0, 0.0, 0.1);
AABB::new(bl, tr)
} }
} }
@@ -451,19 +429,18 @@ impl Primitive for Rectangle {
pub struct Cube { pub struct Cube {
bln: Point3<f64>, bln: Point3<f64>,
trf: Point3<f64>, trf: Point3<f64>,
bounding_box: AABB,
} }
impl Cube { impl Cube {
pub fn new(bln: Point3<f64>, trf: Point3<f64>) -> Rc<dyn Primitive> { pub fn new(bln: Point3<f64>, trf: Point3<f64>) -> Arc<dyn Primitive> {
Rc::new(Cube { Arc::new(Cube { bln, trf })
bln,
trf,
bounding_box: AABB { bln, trf },
})
} }
pub fn unit() -> Rc<dyn Primitive> { pub fn new_unboxed(bln: Point3<f64>, trf: Point3<f64>) -> Cube {
Cube { bln, trf }
}
pub fn unit() -> Arc<dyn Primitive> {
let bln = Point3::new(-1.0, -1.0, -1.0); let bln = Point3::new(-1.0, -1.0, -1.0);
let trf = Point3::new(1.0, 1.0, 1.0); let trf = Point3::new(1.0, 1.0, 1.0);
Cube::new(bln, trf) Cube::new(bln, trf)
@@ -488,35 +465,42 @@ impl Primitive for Cube {
let intersect = ray.at_t(tmin); let intersect = ray.at_t(tmin);
// Check if the intersection is outside the box // Check if the intersection is outside the box
if intersect.x < bln.x if intersect.x < bln.x - EPSILON
|| intersect.x > trf.x || intersect.x > trf.x + EPSILON
|| intersect.y < bln.y || intersect.y < bln.y - EPSILON
|| intersect.y > trf.y || intersect.y > trf.y + EPSILON
|| intersect.z < bln.z || intersect.z < bln.z - EPSILON
|| intersect.z > trf.z || intersect.z > trf.z + EPSILON
{ {
return None; // Intersection is outside the box return None; // Intersection is outside the box
} }
//Get normal of intersection point // Determine which face was hit by finding the t-value closest to tmin
//t1 is bln t2 is trf let diffs = [
let normal = if tmin == t1.x { (t1.x - tmin).abs(),
Vector3::new(-1.0, 0.0, 0.0) (t1.y - tmin).abs(),
} else if tmin == t1.y { (t1.z - tmin).abs(),
Vector3::new(0.0, -1.0, 0.0) (t2.x - tmin).abs(),
} else if tmin == t1.z { (t2.y - tmin).abs(),
Vector3::new(0.0, 0.0, -1.0) (t2.z - tmin).abs(),
} else if tmin == t2.x { ];
Vector3::new(1.0, 0.0, 0.0) let normals = [
} else if tmin == t2.y { Vector3::new(-1.0, 0.0, 0.0),
Vector3::new(0.0, 1.0, 0.0) Vector3::new(0.0, -1.0, 0.0),
} else { Vector3::new(0.0, 0.0, -1.0),
Vector3::new(0.0, 0.0, 1.0) Vector3::new(1.0, 0.0, 0.0),
}; Vector3::new(0.0, 1.0, 0.0),
Vector3::new(0.0, 0.0, 1.0),
];
let min_idx = diffs.iter()
.enumerate()
.min_by(|(_, a), (_, b)| a.partial_cmp(b).unwrap())
.unwrap().0;
let normal = normals[min_idx];
Some(Intersection { Some(Intersection {
point: intersect, point: intersect,
normal: normal, normal,
distance: tmin, distance: tmin,
}) })
} else { } else {
@@ -524,8 +508,8 @@ impl Primitive for Cube {
} }
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) AABB::new(self.bln, self.trf)
} }
} }
@@ -539,27 +523,17 @@ pub struct Triangle {
v: Point3<f64>, v: Point3<f64>,
w: Point3<f64>, w: Point3<f64>,
normal: Vector3<f64>, normal: Vector3<f64>,
bounding_box: AABB,
} }
impl Triangle { impl Triangle {
pub fn new(u: Point3<f64>, v: Point3<f64>, w: Point3<f64>) -> Rc<dyn Primitive> { pub fn new(u: Point3<f64>, v: Point3<f64>, w: Point3<f64>) -> Arc<dyn Primitive> {
let uv = v - u; let uv = v - u;
let uw = w - u; let uw = w - u;
let normal = uw.cross(&uv).normalize(); let normal = uw.cross(&uv).normalize();
let bln = u.inf(&v).inf(&w); Arc::new(Triangle { u, v, w, normal })
let trf = u.sup(&v).sup(&w);
let bounding_box = AABB { bln, trf };
Rc::new(Triangle {
u,
v,
w,
normal,
bounding_box,
})
} }
#[allow(dead_code)] #[allow(dead_code)]
pub fn unit() -> Rc<dyn Primitive> { pub fn unit() -> Arc<dyn Primitive> {
let u = Point3::new(-1.0, -1.0, 0.0); let u = Point3::new(-1.0, -1.0, 0.0);
let v = Point3::new(0.0, 1.0, 0.0); let v = Point3::new(0.0, 1.0, 0.0);
let w = Point3::new(1.0, -1.0, 0.0); let w = Point3::new(1.0, -1.0, 0.0);
@@ -608,8 +582,13 @@ impl Primitive for Triangle {
None None
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let u = self.u;
let v = self.v;
let w = self.w;
let bln = u.inf(&v).inf(&w);
let trf = u.sup(&v).sup(&w);
AABB::new(bln, trf)
} }
} }
@@ -617,17 +596,13 @@ impl Primitive for Triangle {
#[derive(Clone)] #[derive(Clone)]
pub struct Mesh { pub struct Mesh {
triangles: Vec<Triangle>, triangles: Vec<Triangle>,
bounding_box: AABB,
} }
impl Mesh { impl Mesh {
pub fn new(triangles: Vec<Triangle>) -> Rc<dyn Primitive> { pub fn new(triangles: Vec<Triangle>) -> Arc<dyn Primitive> {
// Calculate the bounding box for the entire mesh based on the bounding boxes of individual triangles // Calculate the bounding box for the entire mesh based on the bounding boxes of individual triangles
let bounding_box = Mesh::compute_bounding_box(&triangles); let _bounding_box = Mesh::compute_bounding_box(&triangles);
Rc::new(Mesh { Arc::new(Mesh { triangles })
triangles,
bounding_box,
})
} }
fn compute_bounding_box(triangles: &Vec<Triangle>) -> AABB { fn compute_bounding_box(triangles: &Vec<Triangle>) -> AABB {
@@ -641,10 +616,10 @@ impl Mesh {
trf = trf.sup(&triangle.v); trf = trf.sup(&triangle.v);
trf = trf.sup(&triangle.w); trf = trf.sup(&triangle.w);
} }
AABB { bln, trf } AABB::new(bln, trf)
} }
pub fn from_file(filename: &str) -> Rc<dyn Primitive> { pub fn from_file(filename: &str) -> Arc<dyn Primitive> {
let mut triangles: Vec<Triangle> = Vec::new(); let mut triangles: Vec<Triangle> = Vec::new();
let mut vertices: Vec<Point3<f64>> = Vec::new(); let mut vertices: Vec<Point3<f64>> = Vec::new();
@@ -682,19 +657,10 @@ impl Mesh {
let u = vertices[v1 - 1]; let u = vertices[v1 - 1];
let v = vertices[v2 - 1]; let v = vertices[v2 - 1];
let w = vertices[v3 - 1]; let w = vertices[v3 - 1];
let uv = u - v; let uv = v - u;
let uw = w - v; let uw = w - u;
let normal = uv.cross(&uw).normalize(); let normal = uw.cross(&uv).normalize();
let bln = u.inf(&v).inf(&w); triangles.push(Triangle { u, v, w, normal });
let trf = u.sup(&v).sup(&w);
let bounding_box = AABB { bln, trf };
triangles.push(Triangle {
u,
v,
w,
normal,
bounding_box,
});
} }
} }
_ => {} _ => {}
@@ -727,8 +693,8 @@ impl Primitive for Mesh {
closest_intersect closest_intersect
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) Mesh::compute_bounding_box(&self.triangles)
} }
} }
@@ -737,18 +703,14 @@ impl Primitive for Mesh {
pub struct Torus { pub struct Torus {
inner_rad: f64, inner_rad: f64,
outer_rad: f64, outer_rad: f64,
bounding_box: AABB,
} }
impl Torus { impl Torus {
pub fn new(inner_rad: f64, outer_rad: f64) -> Rc<dyn Primitive> { pub fn new(inner_rad: f64, outer_rad: f64) -> Arc<dyn Primitive> {
// I need to find the bounding box for this shape // I need to find the bounding box for this shape
let trf = Point3::new(1.0, 1.0, 1.0); Arc::new(Torus {
let bln = Point3::new(-1.0, -1.0, -1.0);
Rc::new(Torus {
inner_rad, inner_rad,
outer_rad, outer_rad,
bounding_box: AABB { bln, trf },
}) })
} }
} }
@@ -854,93 +816,87 @@ impl Primitive for Torus {
}) })
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let extent = self.inner_rad + self.outer_rad;
let bln = Point3::new(-extent, -extent, -self.outer_rad);
let trf = Point3::new(extent, extent, self.outer_rad);
AABB::new(bln, trf)
} }
} }
// GNOMON ----------------------------------------------------------------- // GNOMON -----------------------------------------------------------------
#[derive(Clone)] #[derive(Clone)]
pub struct Gnonom { pub struct Gnonom {
x_cube: Rc<dyn Primitive>, x_cube: Cube,
y_cube: Rc<dyn Primitive>, y_cube: Cube,
z_cube: Rc<dyn Primitive>, z_cube: Cube,
bounding_box: AABB,
} }
impl Gnonom { impl Gnonom {
const GNONOM_WIDTH: f64 = 0.1; const GNONOM_WIDTH: f64 = 0.1;
const GNONOM_LENGTH: f64 = 2.0; const GNONOM_LENGTH: f64 = 2.0;
pub fn new() -> Rc<dyn Primitive> { pub fn new() -> Arc<dyn Primitive> {
let x_cube = Cube::new( let x_cube = Cube::new_unboxed(
Point3::new(0.0, -Self::GNONOM_WIDTH, -Self::GNONOM_WIDTH), Point3::new(0.0, -Self::GNONOM_WIDTH, -Self::GNONOM_WIDTH),
Point3::new(Self::GNONOM_LENGTH, Self::GNONOM_WIDTH, Self::GNONOM_WIDTH), Point3::new(Self::GNONOM_LENGTH, Self::GNONOM_WIDTH, Self::GNONOM_WIDTH),
); );
let y_cube = Cube::new( let y_cube = Cube::new_unboxed(
Point3::new(-Self::GNONOM_WIDTH, 0.0, -Self::GNONOM_WIDTH), Point3::new(-Self::GNONOM_WIDTH, 0.0, -Self::GNONOM_WIDTH),
Point3::new(Self::GNONOM_WIDTH, Self::GNONOM_LENGTH, Self::GNONOM_WIDTH), Point3::new(Self::GNONOM_WIDTH, Self::GNONOM_LENGTH, Self::GNONOM_WIDTH),
); );
let z_cube = Cube::new( let z_cube = Cube::new_unboxed(
Point3::new(-Self::GNONOM_WIDTH, -Self::GNONOM_WIDTH, 0.0), Point3::new(-Self::GNONOM_WIDTH, -Self::GNONOM_WIDTH, 0.0),
Point3::new(Self::GNONOM_WIDTH, Self::GNONOM_WIDTH, Self::GNONOM_LENGTH), Point3::new(Self::GNONOM_WIDTH, Self::GNONOM_WIDTH, Self::GNONOM_LENGTH),
); );
let bounding_box = AABB { Arc::new(Gnonom {
bln: Point3::new(
-Self::GNONOM_WIDTH,
-Self::GNONOM_WIDTH,
-Self::GNONOM_WIDTH,
),
trf: Point3::new(
Self::GNONOM_LENGTH,
Self::GNONOM_LENGTH,
Self::GNONOM_LENGTH,
),
};
Rc::new(Gnonom {
x_cube, x_cube,
y_cube, y_cube,
z_cube, z_cube,
bounding_box,
}) })
} }
} }
impl Primitive for Gnonom { impl Primitive for Gnonom {
fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> { fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
match self.x_cube.intersect_ray(ray) { let mut closest: Option<Intersection> = None;
Some(intersect) => return Some(intersect), let mut closest_dist = f64::MAX;
None => (),
}; for cube in [&self.x_cube, &self.y_cube, &self.z_cube] {
match self.y_cube.intersect_ray(ray) { if let Some(intersect) = cube.intersect_ray(ray) {
Some(intersect) => return Some(intersect), let dist = distance(&ray.a, &intersect.point);
None => (), if dist < closest_dist {
}; closest_dist = dist;
match self.z_cube.intersect_ray(ray) { closest = Some(intersect);
Some(intersect) => return Some(intersect), }
None => (), }
}; }
None closest
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) AABB::new(
Point3::new(
-Self::GNONOM_WIDTH,
-Self::GNONOM_WIDTH,
-Self::GNONOM_WIDTH,
),
Point3::new(
Self::GNONOM_LENGTH,
Self::GNONOM_LENGTH,
Self::GNONOM_LENGTH,
),
)
} }
} }
// CROSS CAP --------- // CROSS CAP ---------
#[derive(Clone)] #[derive(Clone)]
pub struct CrossCap { pub struct CrossCap {}
bounding_box: AABB,
}
impl CrossCap { impl CrossCap {
pub fn new() -> Rc<dyn Primitive> { pub fn new() -> Arc<dyn Primitive> {
// I need to find the bounding box for this shape // I need to find the bounding box for this shape
let trf = Point3::new(1.0, 1.0, 1.0); Arc::new(CrossCap {})
let bln = Point3::new(-1.0, -1.0, -1.0);
Rc::new(CrossCap {
bounding_box: AABB { bln, trf },
})
} }
} }
@@ -1014,8 +970,10 @@ impl Primitive for CrossCap {
}) })
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let trf = Point3::new(1.0, 1.0, 1.0);
let bln = Point3::new(-1.0, -1.0, -1.0);
AABB::new(bln, trf)
} }
} }
@@ -1024,19 +982,12 @@ impl Primitive for CrossCap {
pub struct CrossCap2 { pub struct CrossCap2 {
p: f64, p: f64,
q: f64, q: f64,
bounding_box: AABB,
} }
impl CrossCap2 { impl CrossCap2 {
pub fn new(p: f64, q: f64) -> Rc<dyn Primitive> { pub fn new(p: f64, q: f64) -> Arc<dyn Primitive> {
// I need to find the bounding box for this shape // I need to find the bounding box for this shape
let trf = Point3::new(1.0, 1.0, 1.0); Arc::new(CrossCap2 { p, q })
let bln = Point3::new(-1.0, -1.0, -1.0);
Rc::new(CrossCap2 {
p,
q,
bounding_box: AABB { bln, trf },
})
} }
} }
@@ -1135,25 +1086,21 @@ impl Primitive for CrossCap2 {
}) })
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let trf = Point3::new(1.0, 1.0, 1.0);
let bln = Point3::new(-1.0, -1.0, -1.0);
AABB::new(bln, trf)
} }
} }
// Steiner --------- // Steiner ---------
#[derive(Clone)] #[derive(Clone)]
pub struct Steiner { pub struct Steiner {}
bounding_box: AABB,
}
impl Steiner { impl Steiner {
pub fn new() -> Rc<dyn Primitive> { pub fn new() -> Arc<dyn Primitive> {
// I need to find the bounding box for this shape // I need to find the bounding box for this shape
let trf = Point3::new(1.0, 1.0, 1.0); Arc::new(Steiner {})
let bln = Point3::new(-1.0, -1.0, -1.0);
Rc::new(Steiner {
bounding_box: AABB { bln, trf },
})
} }
} }
@@ -1216,25 +1163,21 @@ impl Primitive for Steiner {
}) })
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let trf = Point3::new(1.0, 1.0, 1.0);
let bln = Point3::new(-1.0, -1.0, -1.0);
AABB::new(bln, trf)
} }
} }
// Steiner 2 --------- // Steiner 2 ---------
#[derive(Clone)] #[derive(Clone)]
pub struct Steiner2 { pub struct Steiner2 {}
bounding_box: AABB,
}
impl Steiner2 { impl Steiner2 {
pub fn new() -> Rc<dyn Primitive> { pub fn new() -> Arc<dyn Primitive> {
// I need to find the bounding box for this shape // I need to find the bounding box for this shape
let trf = Point3::new(1.0, 1.0, 1.0); Arc::new(Steiner2 {})
let bln = Point3::new(-1.0, -1.0, -1.0);
Rc::new(Steiner2 {
bounding_box: AABB { bln, trf },
})
} }
} }
@@ -1308,8 +1251,10 @@ impl Primitive for Steiner2 {
}) })
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let trf = Point3::new(1.0, 1.0, 1.0);
let bln = Point3::new(-1.0, -1.0, -1.0);
AABB::new(bln, trf)
} }
} }
@@ -1317,18 +1262,12 @@ impl Primitive for Steiner2 {
#[derive(Clone)] #[derive(Clone)]
pub struct Roman { pub struct Roman {
k: f64, k: f64,
bounding_box: AABB,
} }
impl Roman { impl Roman {
pub fn new(k: f64) -> Rc<dyn Primitive> { pub fn new(k: f64) -> Arc<dyn Primitive> {
// I need to find the bounding box for this shape // I need to find the bounding box for this shape
let trf = Point3::new(1.0, 1.0, 1.0); Arc::new(Roman { k })
let bln = Point3::new(-1.0, -1.0, -1.0);
Rc::new(Roman {
k,
bounding_box: AABB { bln, trf },
})
} }
} }
@@ -1419,8 +1358,10 @@ impl Primitive for Roman {
}) })
} }
fn intersect_bounding_box(&self, ray: &Ray) -> bool { fn get_aabb(&self) -> AABB {
self.bounding_box.intersect_bounding_box(ray) let trf = Point3::new(1.0, 1.0, 1.0);
let bln = Point3::new(-1.0, -1.0, -1.0);
AABB::new(bln, trf)
} }
} }

View File

@@ -1,13 +1,13 @@
use crate::{node::Node, scene::Scene, EPSILON}; use crate::{bvh::BVH, light::Light, node::Node, scene::Scene, state::RaytracingOption, EPSILON};
use nalgebra::{distance, Matrix4, Point3, Vector3}; use nalgebra::{distance, Matrix3, Matrix4, Point3, Vector3};
use rand; use rand;
const MAX_DEPTH: u8 = 5;
const DIFFUSE_RAYS: i8 = 5;
const DIFFUSE_COEFFICIENT: f32 = 0.5;
fn random_vec() -> Vector3<f64> { fn random_vec() -> Vector3<f64> {
Vector3::new(rand::random(), rand::random(), rand::random()) Vector3::new(
rand::random::<f64>() * 2.0 - 1.0,
rand::random::<f64>() * 2.0 - 1.0,
rand::random::<f64>() * 2.0 - 1.0,
)
} }
fn random_unit_vec() -> Vector3<f64> { fn random_unit_vec() -> Vector3<f64> {
random_vec().normalize() random_vec().normalize()
@@ -22,15 +22,17 @@ pub struct Intersection {
} }
//Intersection point including point and normal //Intersection point including point and normal
impl Intersection { impl Intersection {
pub fn transform(&self, trans: &Matrix4<f64>, inv_trans: &Matrix4<f64>) -> Intersection { pub fn transform(&mut self, trans: &Matrix4<f64>, inv_trans: &Matrix4<f64>) -> Intersection {
let point = trans.transform_point(&self.point);
let normal = inv_trans.transpose().transform_vector(&self.normal);
Intersection { Intersection {
point, point: trans.transform_point(&self.point),
normal, normal: inv_trans.transpose().transform_vector(&self.normal),
distance: self.distance, distance: self.distance,
} }
} }
pub fn transform_mut(&mut self, trans: &Matrix4<f64>, inv_transpose: &Matrix3<f64>) {
self.point = trans.transform_point(&self.point);
self.normal = inv_transpose * self.normal;
}
} }
// Ray struct represents a ray in 3D space with a starting point 'a' and a direction 'b' // Ray struct represents a ray in 3D space with a starting point 'a' and a direction 'b'
@@ -66,45 +68,45 @@ impl Ray {
b: trans.transform_vector(&self.b), b: trans.transform_vector(&self.b),
} }
} }
//Transform mutably
pub fn transform_mut(&mut self, trans: &Matrix4<f64>) {
self.a = trans.transform_point(&self.a);
self.b = trans.transform_vector(&self.b);
}
//This function will determine if the ray hits an object in the scene //This function will determine if the ray hits an object in the scene
pub fn hit_scene(&self, scene: &Scene) -> bool { //This is not optimised as it does not include bounding boxes
pub fn hit_scene(ray: &Ray, scene: &Scene) -> bool {
for (_, node) in &scene.nodes { for (_, node) in &scene.nodes {
if !node.active { if !node.active {
continue; continue;
} }
// Transform ray into local model cordinates // Transform ray into local model cordinates
let ray = self.transform(&node.inv_model); if node.intersect_ray(&ray).is_some() {
// Check bounding box intersection
if node.primitive.intersect_bounding_box(&ray) {
// Check primitive intersection
if node.primitive.intersect_ray(&ray).is_some() {
return true; return true;
} }
} }
}
false false
} }
//This function find the closest intersection point of a ray with an object in the scene //This function find the closest intersection point of a ray with an object in the scene
pub fn closest_intersect<'a>(&'a self, scene: &'a Scene) -> Option<(&Node, Intersection)> { //Also not optimised, as it does not include bounding boxes
pub fn closest_intersect<'a>(
ray: &'a Ray,
scene: &'a Scene,
) -> Option<(&'a Node, Intersection)> {
let mut closest_distance = f64::MAX; let mut closest_distance = f64::MAX;
let mut closest_intersect: Option<(&Node, Intersection)> = None; let mut closest_intersect: Option<(&Node, Intersection)> = None;
let ray_a = ray.a;
for (_, node) in &scene.nodes { for (_, node) in &scene.nodes {
//position of ray in world coords
if !node.active { if !node.active {
continue; continue;
} }
// Transform ray into local model cordinates
let ray = self.transform(&node.inv_model); if node.aabb.intersect_ray(&ray) {
// Check bounding box intersection //Check node intersection
if node.primitive.intersect_bounding_box(&ray) { if let Some(intersect) = node.intersect_ray(&ray) {
// Check primitive intersection
if let Some(intersect) = node.primitive.intersect_ray(&ray) {
// Dont intersect with itself
if intersect.distance < EPSILON {
continue;
}
// Check for closest distance by converting to world coords // Check for closest distance by converting to world coords
let intersect = intersect.transform(&node.model, &node.inv_model); let distance = distance(&ray_a, &intersect.point);
let distance = distance(&ray.a, &intersect.point);
if distance < closest_distance { if distance < closest_distance {
closest_distance = distance; closest_distance = distance;
closest_intersect = Some((node, intersect)); closest_intersect = Some((node, intersect));
@@ -115,19 +117,42 @@ impl Ray {
closest_intersect closest_intersect
} }
// This function takes a scene and returns the color of the point where the ray intersects the scene // This function takes a scene and returns the color of the point where the ray intersects the scene
pub fn shade_ray(&self, scene: &Scene, depth: u8) -> Option<Vector3<f32>> { pub fn shade_ray(
if depth == MAX_DEPTH { &self,
scene: &Scene,
depth: u8,
options: &RaytracingOption,
sbvh: &Option<BVH>,
) -> Option<Vector3<f32>> {
//If we have exceeded depth then return
if depth == options.ray_depth {
return None; return None;
} }
match self.closest_intersect(scene) { match sbvh {
//We have a bvh so use bvh traversal
Some(bvh) => {
//Intersect the scene with the bvh
if let Some((node, intersect)) = bvh.traverse(self, 0) {
return Some(Ray::phong_shade_point(
&scene, &self, &node, &intersect, depth, options, sbvh,
));
}
return None;
}
//We dont have a bvh so use generic algorithm
None => {
//No BVH given so intersect normally
match Ray::closest_intersect(self, scene) {
Some((node, intersect)) => { Some((node, intersect)) => {
Some(Ray::phong_shade_point( Some(Ray::phong_shade_point(
&scene, &self, &node, &intersect, depth, &scene, &self, &node, &intersect, depth, options, sbvh,
)) // If there is an intersection, shade it )) // If there is an intersection, shade it
} }
None => None, // If there is no intersection, return None None => None, // If there is no intersection, return None
} }
} }
}
}
// Function to shade a point in the scene using Phong shading model // Function to shade a point in the scene using Phong shading model
pub fn phong_shade_point( pub fn phong_shade_point(
@@ -136,16 +161,38 @@ impl Ray {
node: &Node, node: &Node,
intersect: &Intersection, intersect: &Intersection,
depth: u8, depth: u8,
options: &RaytracingOption,
bvh: &Option<BVH>,
) -> Vector3<f32> { ) -> Vector3<f32> {
let normal = &intersect.normal; let normal = &intersect.normal;
let point = intersect.point; let point = &intersect.point;
let incidence = &ray.b; let incidence = &ray.b;
let material = &node.material; let material = &node.material;
// Compute the ambient light component and set it as base colour
let mut colour = Vector3::zeros(); let mut colour = Vector3::zeros();
// Reflection is view-dependent, not light-dependent — compute once
let mut reflect = Vector3::zeros();
if options.reflect {
let reflect_dir = incidence - 2.0 * incidence.dot(&normal) * normal;
let reflect_ray = Ray::new(*point, reflect_dir);
if let Some(col) = reflect_ray.shade_ray(scene, depth + 1, options, bvh) {
reflect += col.component_mul(&material.kr)
}
}
// Indirect diffuse (global illumination samples) — compute once
let mut indirect = Vector3::zeros();
if options.diffuse {
for _ in 0..options.diffuse_rays {
let diffuse_dir = random_unit_vec();
let diffuse_ray = Ray::new(point.clone(), diffuse_dir + normal);
if let Some(col) = diffuse_ray.shade_ray(scene, depth + 1, options, bvh) {
indirect += col * options.diffuse_coefficient;
}
}
}
for (_, light) in &scene.lights { for (_, light) in &scene.lights {
if !light.active { if !light.active {
continue; continue;
@@ -161,67 +208,78 @@ impl Ray {
let to_light = to_light.normalize(); let to_light = to_light.normalize();
//Niave Shadows //Niave Shadows
let to_light_ray = Ray::new(point, to_light); if options.shadows {
if to_light_ray.light_blocked(scene, node) { let to_light_ray = Ray::new(*point, to_light);
if to_light_ray.light_blocked(scene, light, bvh) {
continue; continue;
} }
}
let n_dot_l = normal.dot(&to_light).max(0.0) as f32; let n_dot_l = normal.dot(&to_light).max(0.0) as f32;
//Reflected component //Direct diffuse component (Lambertian)
let mut reflect = Vector3::zeros();
let reflect_dir = incidence - 2.0 * incidence.dot(&normal) * normal;
let reflect_ray = Ray::new(point, reflect_dir);
if let Some(col) = reflect_ray.shade_ray(scene, depth + 1) {
reflect += col.component_mul(&material.kr)
}
//Diffuse component (Lambertian)
let mut diffuse = Vector3::zeros(); let mut diffuse = Vector3::zeros();
if options.diffuse {
diffuse += material.kd * n_dot_l; diffuse += material.kd * n_dot_l;
for _ in 0..DIFFUSE_RAYS {
let diffuse_dir = random_unit_vec();
let diffuse_ray = Ray::new(point.clone(), diffuse_dir + normal);
if let Some(col) = diffuse_ray.shade_ray(scene, depth + 1) {
diffuse += col * DIFFUSE_COEFFICIENT;
}
} }
//Specular component //Specular component
let mut specular = Vector3::zeros(); let mut specular = Vector3::zeros();
if n_dot_l < 0.0 { if options.specular {
if n_dot_l > 0.0 {
let h = (to_light - incidence).normalize(); let h = (to_light - incidence).normalize();
let n_dot_h = normal.dot(&h).max(0.0) as f32; let n_dot_h = normal.dot(&h).max(0.0) as f32;
specular = material.ks * n_dot_h.powf(material.shininess); specular = material.ks * n_dot_h.powf(material.shininess);
} }
}
//Falloff //Falloff
// let falloff = 1.0 let mut falloff = 1.0;
// / (1.0 if options.falloff {
// + light.falloff[0] falloff = 1.0
// + light.falloff[1] * light_distance / ((1.0 + light.falloff[0])
// + light.falloff[2] * light_distance * light_distance); + light.falloff[1] * light_distance
+ light.falloff[2] * light_distance * light_distance);
}
let intensity = light.colour.component_mul(&(diffuse + reflect + specular)); let intensity = light.colour.component_mul(&(diffuse + specular)) * falloff;
colour += &intensity; colour += &intensity;
} }
// Add light-independent terms
colour += reflect + indirect;
colour colour
} }
pub fn light_blocked(&self, scene: &Scene, _node: &Node) -> bool { pub fn light_blocked(&self, scene: &Scene, light: &Light, bvh: &Option<BVH>) -> bool {
let light_distance = distance(&self.a, &light.position);
match bvh {
Some(bvh) => {
//We have a bvh so use bvh traversal
if let Some((_, intersect)) = bvh.traverse(self, 0) {
return intersect.distance < light_distance;
}
}
None => {
for (_, node) in &scene.nodes { for (_, node) in &scene.nodes {
if !node.active { if !node.active {
continue; continue;
} }
let ray = self.transform(&node.inv_model); if node.aabb.intersect_ray(self) {
if node.primitive.intersect_bounding_box(&ray) { match node.intersect_ray(self) {
if node.primitive.intersect_ray(&ray).is_some() { Some(intersect) => {
if intersect.distance < light_distance {
return true; return true;
} }
} }
None => continue,
} }
false }
}
}
}
return false;
} }
//Cast a set of rays //Cast a set of rays
pub fn cast_rays( pub fn cast_rays(

View File

@@ -35,4 +35,10 @@ impl Scene {
pub fn add_camera(&mut self, label: String, camera: Camera) { pub fn add_camera(&mut self, label: String, camera: Camera) {
self.cameras.insert(label, camera); self.cameras.insert(label, camera);
} }
// Compute all matricies for nodes
pub fn compute(&mut self) {
for (_, node) in &mut self.nodes {
node.compute();
}
}
} }

View File

@@ -1,36 +1,87 @@
//Use linear algebra module //Use linear algebra module
use crate::bvh::BVH;
use crate::camera::Camera; use crate::camera::Camera;
use crate::ray::Ray; use crate::ray::Ray;
use crate::{gui::Gui, scene::Scene}; use crate::{gui::Gui, scene::Scene};
use crate::{gui::GuiEvent, log_error}; use crate::{gui::GuiEvent, log_error};
use std::collections::HashSet;
use std::path::Path; use std::path::Path;
use std::thread;
use nalgebra::Vector3; use nalgebra::Vector3;
use rand::seq::SliceRandom; use rand::seq::SliceRandom;
use rand::{random, thread_rng}; use rand::{random, thread_rng};
use std::error::Error; use std::error::Error;
use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::{mpsc, Arc, Mutex};
use anyhow::Result; use anyhow::Result;
use pixels::{Pixels, SurfaceTexture}; use pixels::{Pixels, SurfaceTexture};
use winit::dpi::{LogicalSize, PhysicalSize}; use winit::dpi::{LogicalSize, PhysicalSize};
use winit::event::{Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent}; use winit::event::{
ElementState, Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent,
};
use winit::event_loop::{ControlFlow, EventLoop}; use winit::event_loop::{ControlFlow, EventLoop};
use winit::window::{Window, WindowBuilder}; use winit::window::{Window, WindowBuilder};
const START_WIDTH: i32 = 1200; const START_WIDTH: i32 = 1200;
const START_HEIGHT: i32 = 700; const START_HEIGHT: i32 = 700;
const RAY_SAMPLES: i8 = 5;
const RAY_RANDOMNESS: f64 = 100.0;
const COLOUR_CLEAR: [u8; 4] = [0x22, 0x00, 0x11, 0x55];
const PIXEL_CLEAR: [u8; 4] = [0x55, 0x00, 0x22, 0x55];
pub const INIT_FILE: &str = "rhai/scene.rhai"; pub const INIT_FILE: &str = "rhai/scene.rhai";
pub const SAVE_FILE: &str = "img.png"; pub const SAVE_FILE: &str = "img.png";
#[derive(Clone)]
pub struct RaytracingOption {
pub threads: u32,
pub ray_samples: u32,
pub ray_randomness: f64,
pub clear_color: [u8; 4],
pub pixel_clear: [u8; 4],
pub pixels_per_thread: u32,
pub buffer_proportion: f32,
pub buffer_fov: f64,
pub ray_depth: u8,
pub diffuse_rays: u8,
pub diffuse_coefficient: f32,
pub bvh_active: bool,
pub shadows: bool,
pub diffuse: bool,
pub reflect: bool,
pub specular: bool,
pub falloff: bool,
}
impl RaytracingOption {
pub fn default() -> RaytracingOption {
RaytracingOption {
threads: 12,
ray_samples: 1,
ray_randomness: 700.0,
clear_color: [0x22, 0x00, 0x11, 0x55],
pixel_clear: [0x11, 0x00, 0x22, 0x55],
pixels_per_thread: 100,
buffer_proportion: 1.0,
buffer_fov: 70.0,
ray_depth: 1,
diffuse_rays: 3,
diffuse_coefficient: 0.1,
bvh_active: false,
shadows: true,
diffuse: true,
reflect: true,
specular: true,
falloff: true,
}
}
}
const CAMERA_MOVE_SPEED: f64 = 0.15;
const CAMERA_ORBIT_SPEED: f64 = 0.005;
pub struct State { pub struct State {
scene: Scene, scene: Arc<Scene>,
bvh: Arc<Option<BVH>>,
camera: Camera, camera: Camera,
window: Window, window: Window,
@@ -40,52 +91,78 @@ pub struct State {
pixels: Pixels, pixels: Pixels,
gui: Gui, gui: Gui,
rays: Vec<Ray>, rays: Arc<Vec<Ray>>,
ray_queue: Vec<usize>, ray_queue: Arc<Mutex<Vec<usize>>>,
raytracing_options: Arc<RaytracingOption>,
result_rx: mpsc::Receiver<Vec<(usize, [u8; 4])>>,
render_active: Arc<AtomicBool>,
rendering: bool,
keys_pressed: HashSet<VirtualKeyCode>,
right_mouse_down: bool,
last_mouse_pos: Option<(f64, f64)>,
camera_dirty: bool,
} }
impl State { impl State {
pub fn new(window: Window, pixels: Pixels, gui: Gui) -> Self { pub fn new(window: Window, pixels: Pixels, gui: Gui) -> Self {
let scene = Scene::empty(); let scene = Arc::new(Scene::empty());
let window_size = window.inner_size(); let window_size = window.inner_size();
let pixels = pixels;
let camera = Camera::unit(); let camera = Camera::unit();
let rays = Vec::new(); let rays = Arc::new(Vec::new());
let (_tx, rx) = mpsc::channel();
Self { Self {
scene, scene,
bvh: Arc::new(None),
camera, camera,
window, window,
buffer_width: window_size.width as u32, buffer_width: window_size.width as u32,
buffer_height: window_size.height as u32, buffer_height: window_size.height as u32,
pixels: pixels, pixels,
gui, gui,
rays, rays,
ray_queue: Vec::new(), ray_queue: Arc::new(Mutex::new(Vec::new())),
raytracing_options: Arc::new(RaytracingOption::default()),
result_rx: rx,
render_active: Arc::new(AtomicBool::new(false)),
rendering: false,
keys_pressed: HashSet::new(),
right_mouse_down: false,
last_mouse_pos: None,
camera_dirty: false,
} }
} }
fn update(&mut self) -> Result<(), Box<dyn Error>> { fn update(&mut self) -> Result<(), Box<dyn Error>> {
if let Some(event) = self.gui.event.take() { if let Some(event) = self.gui.event.take() {
match event { match event {
GuiEvent::BufferResize(proportion, fov) => { GuiEvent::RaytracerOption(options) => {
self.resize_buffer(proportion, fov as f64)? self.raytracing_options = Arc::new(options);
match self.raytracing_options.bvh_active {
true => self.bvh = Arc::new(Some(BVH::build(&self.scene.nodes))),
false => self.bvh = Arc::new(None),
} }
GuiEvent::CameraUpdate(camera, fovy) => { self.resize_buffer()?
self.rays = Ray::cast_rays( }
GuiEvent::CameraUpdate(camera) => {
self.rays = Arc::new(Ray::cast_rays(
&camera.eye, &camera.eye,
&camera.target, &camera.target,
&camera.up, &camera.up,
fovy as f64, self.raytracing_options.buffer_fov,
self.buffer_width, self.buffer_width,
self.buffer_height, self.buffer_height,
); ));
self.camera = camera; self.camera = camera;
self.clear()?; self.clear_buffer()?;
self.reset_queue(); self.reset_queue();
} }
GuiEvent::SceneLoad(scene) => { GuiEvent::SceneLoad(scene) => {
self.scene = scene; self.scene = Arc::new(scene);
self.clear()?; self.clear_buffer()?;
self.reset_queue(); self.reset_queue();
} }
GuiEvent::SaveImage(filename) => { GuiEvent::SaveImage(filename) => {
@@ -103,30 +180,32 @@ impl State {
Ok(()) Ok(())
} }
fn resize_buffer(&mut self, proportion: f32, fovy: f64) -> Result<(), Box<dyn Error>> { fn resize_buffer(&mut self) -> Result<(), Box<dyn Error>> {
// Calculate new buffer dimensions based on proportion // Calculate new buffer dimensions based on proportion
let size = self.window.inner_size(); let size = self.window.inner_size();
let proportion = &self.raytracing_options.buffer_proportion;
let fovy = self.raytracing_options.buffer_fov;
self.buffer_width = (size.width as f32 * proportion) as u32; self.buffer_width = (size.width as f32 * proportion) as u32;
self.buffer_height = (size.height as f32 * proportion) as u32; self.buffer_height = (size.height as f32 * proportion) as u32;
// Clear the buffer and reset the ray queue // Clear the buffer and reset the ray queue
self.clear()?; self.clear_buffer()?;
self.reset_queue(); self.reset_queue();
// Recalculate rays with new buffer dimensions // Recalculate rays with new buffer dimensions
self.rays = Ray::cast_rays( self.rays = Arc::new(Ray::cast_rays(
&self.camera.eye, &self.camera.eye,
&self.camera.target, &self.camera.target,
&self.camera.up, &self.camera.up,
fovy, fovy,
self.buffer_width, self.buffer_width,
self.buffer_height, self.buffer_height,
); ));
// Resize buffer and surface // Resize buffer and surface
let pixels = &mut self.pixels; self.pixels.resize_surface(size.width, size.height)?;
pixels.resize_surface(size.width, size.height)?; self.pixels
pixels.resize_buffer(self.buffer_width, self.buffer_height)?; .resize_buffer(self.buffer_width, self.buffer_height)?;
Ok(()) Ok(())
} }
@@ -137,77 +216,227 @@ impl State {
} }
fn keyboard_input(&mut self, key: &KeyboardInput) { fn keyboard_input(&mut self, key: &KeyboardInput) {
if let Some(VirtualKeyCode::A) = key.virtual_keycode { if let Some(keycode) = key.virtual_keycode {
// Handle 'A' key event here match key.state {
ElementState::Pressed => {
self.keys_pressed.insert(keycode);
}
ElementState::Released => {
self.keys_pressed.remove(&keycode);
}
}
} }
} }
fn mouse_input(&mut self, _button: &MouseButton) { fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) {
// Handle mouse input here if *button == MouseButton::Right {
self.right_mouse_down = *state == ElementState::Pressed;
if !self.right_mouse_down {
self.last_mouse_pos = None;
}
}
} }
fn draw(&mut self) -> Result<(), Box<dyn Error>> { fn cursor_moved(&mut self, x: f64, y: f64) {
//Draw ray_num in a block if self.right_mouse_down {
if let Some((last_x, last_y)) = self.last_mouse_pos {
let dx = x - last_x;
let dy = y - last_y;
self.camera.orbit(
-dx * CAMERA_ORBIT_SPEED,
-dy * CAMERA_ORBIT_SPEED,
);
self.camera_dirty = true;
}
self.last_mouse_pos = Some((x, y));
}
}
fn process_camera_movement(&mut self) {
let speed = CAMERA_MOVE_SPEED;
if self.keys_pressed.contains(&VirtualKeyCode::W) {
self.camera.move_forward(speed);
self.camera_dirty = true;
}
if self.keys_pressed.contains(&VirtualKeyCode::S) {
self.camera.move_forward(-speed);
self.camera_dirty = true;
}
if self.keys_pressed.contains(&VirtualKeyCode::A) {
self.camera.move_right(-speed);
self.camera_dirty = true;
}
if self.keys_pressed.contains(&VirtualKeyCode::D) {
self.camera.move_right(speed);
self.camera_dirty = true;
}
if self.keys_pressed.contains(&VirtualKeyCode::Q) {
self.camera.move_up(-speed);
self.camera_dirty = true;
}
if self.keys_pressed.contains(&VirtualKeyCode::E) {
self.camera.move_up(speed);
self.camera_dirty = true;
}
if self.camera_dirty {
self.camera_dirty = false;
self.rays = Arc::new(Ray::cast_rays(
&self.camera.eye,
&self.camera.target,
&self.camera.up,
self.raytracing_options.buffer_fov,
self.buffer_width,
self.buffer_height,
));
self.gui.update_camera(&self.camera);
let _ = self.clear_buffer();
self.reset_queue();
}
}
fn draw(&mut self) {
if !self.rendering {
return;
}
// Drain completed results from background workers
loop {
match self.result_rx.try_recv() {
Ok(results) => {
let frame = self.pixels.frame_mut(); let frame = self.pixels.frame_mut();
for _ in 0..self.gui.ray_num { for (index, rgba) in results {
//Get random index from queue frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba);
let index = match self.ray_queue.pop() { }
Some(index) => index, }
Err(mpsc::TryRecvError::Empty) => break,
Err(mpsc::TryRecvError::Disconnected) => {
// All worker threads have finished
self.rendering = false;
self.gui.stop_render_timer();
break;
}
}
}
}
fn clear_buffer(&mut self) -> Result<(), Box<dyn Error>> {
let frame = self.pixels.frame_mut();
for pixel in frame.chunks_exact_mut(4) {
pixel.copy_from_slice(&self.raytracing_options.pixel_clear);
}
Ok(())
}
fn reset_queue(&mut self) {
// Signal any existing workers to stop
self.render_active.store(false, Ordering::Relaxed);
match self.raytracing_options.bvh_active {
true => self.bvh = Arc::new(Some(BVH::build(&self.scene.nodes))),
false => self.bvh = Arc::new(None),
}
// Create new shuffled queue
let size = self.buffer_height as usize * self.buffer_width as usize;
let mut ray_queue: Vec<usize> = (0..size).collect();
ray_queue.shuffle(&mut thread_rng());
self.ray_queue = Arc::new(Mutex::new(ray_queue));
// Create new channel and active flag
let (tx, rx) = mpsc::channel();
self.result_rx = rx;
let render_active = Arc::new(AtomicBool::new(true));
self.render_active = render_active.clone();
self.rendering = true;
// Spawn persistent worker threads
let num_threads = self.raytracing_options.threads;
let pixels_per_thread = self.raytracing_options.pixels_per_thread;
for _ in 0..num_threads {
let rays = self.rays.clone();
let scene = self.scene.clone();
let options = self.raytracing_options.clone();
let bvh = self.bvh.clone();
let queue = self.ray_queue.clone();
let tx = tx.clone();
let active = render_active.clone();
thread::spawn(move || {
let randomness = options.ray_randomness;
let samples = options.ray_samples;
let samples_f32 = samples as f32;
loop {
if !active.load(Ordering::Relaxed) {
break;
}
// Pop a batch from the shared queue
let load: Vec<usize> = {
let mut q = queue.lock().unwrap();
let mut batch = Vec::with_capacity(pixels_per_thread as usize);
for _ in 0..pixels_per_thread {
match q.pop() {
Some(index) => batch.push(index),
None => break, None => break,
}
}
batch
}; };
//Shade colour for selected ray
let mut colour = Vector3::zeros(); if load.is_empty() {
for _ in 0..RAY_SAMPLES { break;
let ray = &self.rays[index]; }
// Process the batch
let mut results = Vec::with_capacity(load.len());
for index in &load {
let mut colour: Vector3<f32> = Vector3::zeros();
let ray = &rays[*index];
for _ in 0..samples {
let point = ray.a; let point = ray.a;
let dir = ray.b; let dir = ray.b;
let rx = (random::<f64>() - 0.5) / RAY_RANDOMNESS; let rx = (random::<f64>() - 0.5) / randomness;
let ry = (random::<f64>() - 0.5) / RAY_RANDOMNESS; let ry = (random::<f64>() - 0.5) / randomness;
let rz = (random::<f64>() - 0.5) / RAY_RANDOMNESS; let rz = (random::<f64>() - 0.5) / randomness;
let nx = dir.x + rx; let nx = dir.x + rx;
let ny = dir.y + ry; let ny = dir.y + ry;
let nz = dir.z + rz; let nz = dir.z + rz;
let rand_ray = Ray::new(point, Vector3::new(nx, ny, nz)); let rand_ray = Ray::new(point, Vector3::new(nx, ny, nz));
if let Some(ray_colour) = rand_ray.shade_ray(&self.scene, 0) { if let Some(ray_colour) =
rand_ray.shade_ray(&scene, 0, &options, &bvh)
{
colour += ray_colour; colour += ray_colour;
};
} }
colour = (colour / RAY_SAMPLES as f32) * 255.0; }
colour = (colour / samples_f32) * 255.0;
let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff]; let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff];
frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); results.push((*index, rgba));
}
Ok(())
} }
fn clear(&mut self) -> Result<(), Box<dyn Error>> { // Send results back to main thread
let frame = self.pixels.frame_mut(); if tx.send(results).is_err() {
for pixel in frame.chunks_exact_mut(4) { break;
pixel.copy_from_slice(&COLOUR_CLEAR);
} }
Ok(())
} }
});
}
// Drop our copy of tx so the channel disconnects when all workers finish
drop(tx);
fn reset_queue(&mut self) { self.gui.start_render_timer();
let size = self.buffer_height as usize * self.buffer_width as usize;
let mut ray_queue: Vec<usize> = (0..size).collect();
ray_queue.shuffle(&mut thread_rng());
self.ray_queue = ray_queue;
} }
fn render(&mut self) -> Result<(), Box<dyn Error>> { fn render(&mut self) -> Result<(), Box<dyn Error>> {
// Update state // Update state
self.update()?; self.update()?;
// Draw rays if we have remaining rays in queue // Collect completed rays from background workers
if !self.ray_queue.is_empty() { self.draw();
match self.draw() {
Err(e) => {
println!("ERROR: {}", e);
}
_ => {}
}
}
// Render Gui // Render Gui
self.gui self.gui
.prepare(&self.window) .prepare(&self.window)
@@ -234,7 +463,7 @@ pub fn run() -> Result<(), Box<dyn Error>> {
let gui = Gui::new(&window, &pixels); let gui = Gui::new(&window, &pixels);
let mut state = State::new(window, pixels, gui); let mut state = State::new(window, pixels, gui);
state.resize_buffer(1.0, 90.0)?; state.resize_buffer()?;
event_loop.run(move |event, _, control_flow| { event_loop.run(move |event, _, control_flow| {
state.gui.handle_event(&state.window, &event); state.gui.handle_event(&state.window, &event);
@@ -248,11 +477,17 @@ pub fn run() -> Result<(), Box<dyn Error>> {
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit, WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
WindowEvent::Resized(size) => state.resize(&size).expect("Window Resize Error"), WindowEvent::Resized(size) => state.resize(&size).expect("Window Resize Error"),
WindowEvent::KeyboardInput { input, .. } => state.keyboard_input(&input), WindowEvent::KeyboardInput { input, .. } => state.keyboard_input(&input),
WindowEvent::MouseInput { button, .. } => state.mouse_input(&button), WindowEvent::MouseInput { button, state: elem_state, .. } => {
state.mouse_input(&button, &elem_state)
}
WindowEvent::CursorMoved { position, .. } => {
state.cursor_moved(position.x, position.y)
}
_ => {} _ => {}
}, },
Event::RedrawRequested(_) => { Event::RedrawRequested(_) => {
state.process_camera_movement();
if let Err(_e) = state.render() { if let Err(_e) = state.render() {
*control_flow = ControlFlow::Exit; *control_flow = ControlFlow::Exit;
} }