Compare commits
4 Commits
e143a4b2ce
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| 47473a3a08 | |||
| fa31d18c12 | |||
| d64085461a | |||
| 79493b8924 |
186
README.md
186
README.md
@@ -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
|

|
||||||
|
|
||||||
## 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()
|

|
||||||
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()
|
// 3‑dimensional vector, used for directions, colors, etc.
|
||||||
Cube()
|
|
||||||
CubeUnit()
|
P(x : float, y : float, z : float) -> Position3
|
||||||
Cone()
|
// 3‑dimensional 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, implementation‑defined).
|
||||||
|
|
||||||
|
Node.scale(x : float, y : float, z : float) -> Node
|
||||||
|
// Non‑uniform 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
|
||||||
|
// Phong‑style 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, implementation‑defined),
|
||||||
|
// `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
|
||||||
|
// Axis‑aligned 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
|
||||||
|
// Cross‑cap surface (Boy's surface variant / projective plane immersion).
|
||||||
|
|
||||||
|
Gnonom() -> Mesh
|
||||||
|
// Gnomon‑like parametric surface (implementation‑defined shape).
|
||||||
|
|
||||||
|
```
|
||||||
|
|||||||
BIN
img/example.png
Normal file
BIN
img/example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 158 KiB |
81
rhai/bvh_test.rhai
Normal file
81
rhai/bvh_test.rhai
Normal 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
|
||||||
123
src/bvh.rs
123
src/bvh.rs
@@ -21,8 +21,8 @@ pub struct AABB {
|
|||||||
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);
|
||||||
let centroid = bln + (trf - bln) / 2.0;
|
let centroid = bln + (trf - bln) / 2.0;
|
||||||
AABB { bln, trf, centroid }
|
AABB { bln, trf, centroid }
|
||||||
}
|
}
|
||||||
@@ -36,64 +36,46 @@ impl AABB {
|
|||||||
}
|
}
|
||||||
//Apply a matrix transformation to a box
|
//Apply a matrix transformation to a box
|
||||||
pub fn transform_mut(&mut self, mat: &Matrix4<f64>) {
|
pub fn transform_mut(&mut self, mat: &Matrix4<f64>) {
|
||||||
let bln = &mut self.bln;
|
let corners = [
|
||||||
let trf = &mut self.trf;
|
Point3::new(self.bln.x, self.bln.y, self.bln.z),
|
||||||
let centroid = &mut self.centroid;
|
Point3::new(self.trf.x, self.bln.y, self.bln.z),
|
||||||
self.bln = mat.transform_point(bln);
|
Point3::new(self.bln.x, self.trf.y, self.bln.z),
|
||||||
self.trf = mat.transform_point(trf);
|
Point3::new(self.trf.x, self.trf.y, self.bln.z),
|
||||||
self.centroid = mat.transform_point(centroid);
|
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_ray(&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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
false
|
|
||||||
}
|
}
|
||||||
// Intersect way with some epsilon term
|
// Intersect with some epsilon tolerance
|
||||||
pub fn intersect_ray_aprox(&self, ray: &Ray) -> bool {
|
pub fn intersect_ray_aprox(&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 - 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> {
|
||||||
@@ -126,6 +108,7 @@ impl AABB {
|
|||||||
self.trf.y.max(other.trf.y),
|
self.trf.y.max(other.trf.y),
|
||||||
self.trf.z.max(other.trf.z),
|
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 {
|
||||||
@@ -154,6 +137,7 @@ impl AABB {
|
|||||||
self.trf.y.max(other.y),
|
self.trf.y.max(other.y),
|
||||||
self.trf.z.max(other.z),
|
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> {
|
||||||
@@ -304,7 +288,7 @@ impl BVH {
|
|||||||
// let mut best_pos = 0.0;
|
// let mut best_pos = 0.0;
|
||||||
// let mut best_cost = 1e30;
|
// let mut best_cost = 1e30;
|
||||||
// let first_prim_idx = self.bvh_nodes[index].first_prim;
|
// let first_prim_idx = self.bvh_nodes[index].first_prim;
|
||||||
// for axis in 0..2 {
|
// for axis in 0..3 {
|
||||||
// for i in 0..self.bvh_nodes[index].prim_count {
|
// for i in 0..self.bvh_nodes[index].prim_count {
|
||||||
// let node = &self.nodes[first_prim_idx + i];
|
// let node = &self.nodes[first_prim_idx + i];
|
||||||
// //Get the centroid of the bounding box
|
// //Get the centroid of the bounding box
|
||||||
@@ -391,20 +375,22 @@ impl BVH {
|
|||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
if bvh_node.prim_count != 0 {
|
if bvh_node.prim_count != 0 {
|
||||||
// Leaf node intersection
|
// Leaf node - check all primitives it contains
|
||||||
let node_idx = bvh_node.first_prim;
|
let mut closest: Option<(&Node, Intersection)> = None;
|
||||||
let node = &self.nodes[node_idx];
|
let mut closest_dist = f64::MAX;
|
||||||
if !node.active {
|
for i in 0..bvh_node.prim_count {
|
||||||
return None;
|
let node = &self.nodes[bvh_node.first_prim + i];
|
||||||
}
|
if !node.active {
|
||||||
if let Some(intersect) = node.intersect_ray(&ray) {
|
continue;
|
||||||
if intersect.distance < EPSILON {
|
}
|
||||||
return None;
|
if let Some(intersect) = node.intersect_ray(&ray) {
|
||||||
} else {
|
if intersect.distance >= EPSILON && intersect.distance < closest_dist {
|
||||||
return Some((node, intersect));
|
closest_dist = intersect.distance;
|
||||||
|
closest = Some((node, intersect));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return None;
|
return closest;
|
||||||
} else {
|
} else {
|
||||||
//Recurse down the BVH
|
//Recurse down the BVH
|
||||||
//Recurse down the BVH right node
|
//Recurse down the BVH right node
|
||||||
@@ -438,17 +424,14 @@ impl BVH {
|
|||||||
let aabb = self.nodes[node.first_prim + i].get_world_aabb();
|
let aabb = self.nodes[node.first_prim + i].get_world_aabb();
|
||||||
if aabb.trf[axis] < pos {
|
if aabb.trf[axis] < pos {
|
||||||
l_count += 1;
|
l_count += 1;
|
||||||
l_aabb.grow_mut(&aabb.trf);
|
l_aabb.join_mut(&aabb);
|
||||||
} else {
|
} else {
|
||||||
r_count += 1;
|
r_count += 1;
|
||||||
r_aabb.grow_mut(&aabb.bln);
|
r_aabb.join_mut(&aabb);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
let cost = l_count as f64 * l_aabb.area() + r_count as f64 * r_aabb.area();
|
let cost = l_count as f64 * l_aabb.area() + r_count as f64 * r_aabb.area();
|
||||||
match cost > 0.0 {
|
if cost > 0.0 { cost } else { 1e30 }
|
||||||
true => 0.0,
|
|
||||||
false => 1e30,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
190
src/gui.rs
190
src/gui.rs
@@ -11,7 +11,7 @@ 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_MIN: f32 = 0.1;
|
const BUFFER_PROPORTION_MIN: f32 = 0.1;
|
||||||
@@ -38,22 +38,16 @@ const MIN_DIFFUSE_COEFFICIENT: f32 = 0.0;
|
|||||||
const MAX_DIFFUSE_COEFFICIENT: f32 = 1.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;
|
||||||
@@ -81,6 +75,9 @@ 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,
|
||||||
@@ -141,6 +138,9 @@ 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(),
|
||||||
@@ -171,6 +171,17 @@ impl Gui {
|
|||||||
gui
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Prepare Dear ImGui.
|
/// Prepare Dear ImGui.
|
||||||
pub fn prepare(
|
pub fn prepare(
|
||||||
&mut self,
|
&mut self,
|
||||||
@@ -216,25 +227,42 @@ impl Gui {
|
|||||||
&mut self.raytracing_option.threads,
|
&mut self.raytracing_option.threads,
|
||||||
);
|
);
|
||||||
// Numbers of rays to render per pass
|
// Numbers of rays to render per pass
|
||||||
ui.slider(
|
Drag::new("Rays Per Pass")
|
||||||
"Rays Per Pass",
|
.range(RAYS_MIN, RAYS_MAX)
|
||||||
RAYS_MIN,
|
.speed(50.0)
|
||||||
RAYS_MAX,
|
.build(ui, &mut self.raytracing_option.pixels_per_thread);
|
||||||
&mut self.raytracing_option.pixels_per_thread,
|
|
||||||
);
|
|
||||||
// Proportion of the window the buffer occupies
|
// Proportion of the window the buffer occupies
|
||||||
ui.slider(
|
Drag::new("% Buffer: ")
|
||||||
"% Buffer: ",
|
.range(BUFFER_PROPORTION_MIN, BUFFER_PROPORTION_MAX)
|
||||||
BUFFER_PROPORTION_MIN,
|
.speed(0.005)
|
||||||
BUFFER_PROPORTION_MAX,
|
.display_format("%.2f")
|
||||||
&mut self.raytracing_option.buffer_proportion,
|
.build(ui, &mut self.raytracing_option.buffer_proportion);
|
||||||
);
|
|
||||||
//Clear colour for scene
|
//Clear colour for scene
|
||||||
ui.slider_config("Clear Colour", 0, 255)
|
let mut clear_f32 = [
|
||||||
.build_array(&mut self.raytracing_option.clear_color);
|
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
|
//Clear colour if no intersect
|
||||||
ui.slider_config("Pixel Clear Colour", 0, 255)
|
let mut pixel_clear_f32 = [
|
||||||
.build_array(&mut self.raytracing_option.pixel_clear);
|
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
|
//Ray depth slider
|
||||||
ui.slider(
|
ui.slider(
|
||||||
"Ray Depth",
|
"Ray Depth",
|
||||||
@@ -250,12 +278,11 @@ impl Gui {
|
|||||||
&mut self.raytracing_option.ray_samples,
|
&mut self.raytracing_option.ray_samples,
|
||||||
);
|
);
|
||||||
//Ray randomness
|
//Ray randomness
|
||||||
ui.slider(
|
Drag::new("Ray Randomness")
|
||||||
"Ray Randomness",
|
.range(MIN_RANDOM, MAX_RANDOM)
|
||||||
MIN_RANDOM,
|
.speed(5.0)
|
||||||
MAX_RANDOM,
|
.display_format("%.1f")
|
||||||
&mut self.raytracing_option.ray_randomness,
|
.build(ui, &mut self.raytracing_option.ray_randomness);
|
||||||
);
|
|
||||||
//Number of diffuse rays
|
//Number of diffuse rays
|
||||||
ui.slider(
|
ui.slider(
|
||||||
"Diffuse Rays",
|
"Diffuse Rays",
|
||||||
@@ -264,12 +291,11 @@ impl Gui {
|
|||||||
&mut self.raytracing_option.diffuse_rays,
|
&mut self.raytracing_option.diffuse_rays,
|
||||||
);
|
);
|
||||||
//Diffuse Coefficient
|
//Diffuse Coefficient
|
||||||
ui.slider(
|
Drag::new("Diffuse Coefficient")
|
||||||
"Diffuse Coefficient",
|
.range(MIN_DIFFUSE_COEFFICIENT, MAX_DIFFUSE_COEFFICIENT)
|
||||||
MIN_DIFFUSE_COEFFICIENT,
|
.speed(0.005)
|
||||||
MAX_DIFFUSE_COEFFICIENT,
|
.display_format("%.3f")
|
||||||
&mut self.raytracing_option.diffuse_coefficient,
|
.build(ui, &mut self.raytracing_option.diffuse_coefficient);
|
||||||
);
|
|
||||||
// Fov of the buffer
|
// Fov of the buffer
|
||||||
ui.slider(
|
ui.slider(
|
||||||
"fov",
|
"fov",
|
||||||
@@ -283,6 +309,15 @@ impl Gui {
|
|||||||
ui.checkbox("Enable Reflections", &mut self.raytracing_option.reflect);
|
ui.checkbox("Enable Reflections", &mut self.raytracing_option.reflect);
|
||||||
ui.checkbox("Enable Specular", &mut self.raytracing_option.specular);
|
ui.checkbox("Enable Specular", &mut self.raytracing_option.specular);
|
||||||
ui.checkbox("Enable Diffuse", &mut self.raytracing_option.diffuse);
|
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::RaytracerOption(self.raytracing_option.clone()));
|
self.event = Some(GuiEvent::RaytracerOption(self.raytracing_option.clone()));
|
||||||
@@ -292,12 +327,21 @@ impl Gui {
|
|||||||
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.event = Some(GuiEvent::CameraUpdate(self.camera.clone()));
|
||||||
@@ -361,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -374,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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -388,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());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -430,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,
|
||||||
|
|||||||
@@ -99,17 +99,19 @@ impl Node {
|
|||||||
// 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);
|
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);
|
self.aabb.transform_mut(&self.model);
|
||||||
}
|
}
|
||||||
// Intersection of a ray, will convert to model coords and check
|
// Intersection of a ray, will convert to model coords and check
|
||||||
pub fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
|
pub fn intersect_ray(&self, ray: &Ray) -> Option<Intersection> {
|
||||||
let ray = ray.transform(&self.inv_model); //Transform from world coordinates
|
let local_ray = ray.transform(&self.inv_model); //Transform from world coordinates
|
||||||
if let Some(mut intersect) = self.primitive.intersect_ray(&ray) {
|
if let Some(mut intersect) = self.primitive.intersect_ray(&local_ray) {
|
||||||
if intersect.distance < EPSILON {
|
if intersect.distance < EPSILON {
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
intersect.transform_mut(&self.model, &self.inv_transpose_model); //Transform to world coords
|
intersect.transform_mut(&self.model, &self.inv_transpose_model); //Transform to world coords
|
||||||
intersect.distance = distance(&intersect.point, &ray.a);
|
intersect.distance = distance(&intersect.point, &ray.a); // use world-space ray origin
|
||||||
return Some(intersect);
|
return Some(intersect);
|
||||||
}
|
}
|
||||||
return None;
|
return None;
|
||||||
|
|||||||
118
src/primitive.rs
118
src/primitive.rs
@@ -47,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 if x1 <= 0.0 {
|
||||||
|
x2
|
||||||
} else {
|
} else {
|
||||||
if x1.abs() < x2.abs() {
|
x1
|
||||||
x1
|
|
||||||
} else {
|
|
||||||
x2
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@@ -124,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
|
||||||
@@ -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 if x1 <= 0.0 {
|
||||||
|
Some(x2)
|
||||||
} else {
|
} else {
|
||||||
if x1.abs() < x2.abs() {
|
Some(x1)
|
||||||
Some(x1)
|
|
||||||
} else {
|
|
||||||
Some(x2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => return None,
|
_ => return None,
|
||||||
@@ -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 if x1 <= 0.0 {
|
||||||
|
Some(x2)
|
||||||
} else {
|
} else {
|
||||||
if x1.abs() < x2.abs() {
|
Some(x1)
|
||||||
Some(x1)
|
|
||||||
} else {
|
|
||||||
Some(x2)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
_ => None,
|
_ => None,
|
||||||
@@ -359,7 +356,15 @@ 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -395,7 +400,7 @@ impl Primitive for RectangleXY {
|
|||||||
let az = ray.a.z;
|
let az = ray.a.z;
|
||||||
let bz = ray.b.z;
|
let bz = ray.b.z;
|
||||||
let t = (z - az) / bz;
|
let t = (z - az) / bz;
|
||||||
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);
|
||||||
@@ -470,21 +475,28 @@ impl Primitive for Cube {
|
|||||||
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,
|
||||||
@@ -645,9 +657,9 @@ 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();
|
||||||
triangles.push(Triangle { u, v, w, normal });
|
triangles.push(Triangle { u, v, w, normal });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -805,9 +817,9 @@ impl Primitive for Torus {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn get_aabb(&self) -> AABB {
|
fn get_aabb(&self) -> AABB {
|
||||||
//TODO!
|
let extent = self.inner_rad + self.outer_rad;
|
||||||
let trf = Point3::new(1.0, 1.0, 1.0);
|
let bln = Point3::new(-extent, -extent, -self.outer_rad);
|
||||||
let bln = Point3::new(-1.0, -1.0, -1.0);
|
let trf = Point3::new(extent, extent, self.outer_rad);
|
||||||
AABB::new(bln, trf)
|
AABB::new(bln, trf)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -846,19 +858,19 @@ impl Gnonom {
|
|||||||
|
|
||||||
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 get_aabb(&self) -> AABB {
|
fn get_aabb(&self) -> AABB {
|
||||||
|
|||||||
68
src/ray.rs
68
src/ray.rs
@@ -3,7 +3,11 @@ use nalgebra::{distance, Matrix3, Matrix4, Point3, Vector3};
|
|||||||
use rand;
|
use rand;
|
||||||
|
|
||||||
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()
|
||||||
@@ -165,9 +169,30 @@ impl Ray {
|
|||||||
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;
|
||||||
@@ -192,27 +217,10 @@ impl Ray {
|
|||||||
|
|
||||||
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();
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//Diffuse component (Lambertian)
|
|
||||||
let mut diffuse = Vector3::zeros();
|
let mut diffuse = Vector3::zeros();
|
||||||
if options.diffuse {
|
if options.diffuse {
|
||||||
diffuse += material.kd * n_dot_l;
|
diffuse += material.kd * n_dot_l;
|
||||||
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) {
|
|
||||||
diffuse += col * options.diffuse_coefficient;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//Specular component
|
//Specular component
|
||||||
@@ -234,10 +242,13 @@ impl Ray {
|
|||||||
+ light.falloff[2] * light_distance * light_distance);
|
+ light.falloff[2] * light_distance * light_distance);
|
||||||
}
|
}
|
||||||
|
|
||||||
let intensity = light.colour.component_mul(&(diffuse + reflect + specular)) * falloff;
|
let intensity = light.colour.component_mul(&(diffuse + specular)) * falloff;
|
||||||
colour += &intensity;
|
colour += &intensity;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Add light-independent terms
|
||||||
|
colour += reflect + indirect;
|
||||||
|
|
||||||
colour
|
colour
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -246,20 +257,9 @@ impl Ray {
|
|||||||
match bvh {
|
match bvh {
|
||||||
Some(bvh) => {
|
Some(bvh) => {
|
||||||
//We have a bvh so use bvh traversal
|
//We have a bvh so use bvh traversal
|
||||||
for (_, node) in &scene.nodes {
|
if let Some((_, intersect)) = bvh.traverse(self, 0) {
|
||||||
if !node.active {
|
return intersect.distance < light_distance;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
match bvh.traverse(self, 0) {
|
|
||||||
Some((_, intersect)) => {
|
|
||||||
if intersect.distance < light_distance {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => continue,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
None => {
|
None => {
|
||||||
for (_, node) in &scene.nodes {
|
for (_, node) in &scene.nodes {
|
||||||
|
|||||||
322
src/state.rs
322
src/state.rs
@@ -5,6 +5,7 @@ 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 std::thread;
|
||||||
|
|
||||||
@@ -13,12 +14,15 @@ 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::Arc;
|
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};
|
||||||
|
|
||||||
@@ -72,6 +76,9 @@ impl RaytracingOption {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const CAMERA_MOVE_SPEED: f64 = 0.15;
|
||||||
|
const CAMERA_ORBIT_SPEED: f64 = 0.005;
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
scene: Arc<Scene>,
|
scene: Arc<Scene>,
|
||||||
bvh: Arc<Option<BVH>>,
|
bvh: Arc<Option<BVH>>,
|
||||||
@@ -85,8 +92,17 @@ pub struct State {
|
|||||||
gui: Gui,
|
gui: Gui,
|
||||||
|
|
||||||
rays: Arc<Vec<Ray>>,
|
rays: Arc<Vec<Ray>>,
|
||||||
ray_queue: Vec<usize>,
|
ray_queue: Arc<Mutex<Vec<usize>>>,
|
||||||
raytracing_options: Arc<RaytracingOption>,
|
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 {
|
||||||
@@ -96,6 +112,7 @@ impl State {
|
|||||||
let pixels = pixels;
|
let pixels = pixels;
|
||||||
let camera = Camera::unit();
|
let camera = Camera::unit();
|
||||||
let rays = Arc::new(Vec::new());
|
let rays = Arc::new(Vec::new());
|
||||||
|
let (_tx, rx) = mpsc::channel();
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
scene,
|
scene,
|
||||||
@@ -107,8 +124,15 @@ impl State {
|
|||||||
pixels,
|
pixels,
|
||||||
gui,
|
gui,
|
||||||
rays,
|
rays,
|
||||||
ray_queue: Vec::new(),
|
ray_queue: Arc::new(Mutex::new(Vec::new())),
|
||||||
raytracing_options: Arc::new(RaytracingOption::default()),
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -192,98 +216,109 @@ 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);
|
||||||
|
}
|
||||||
fn mouse_input(&mut self, _button: &MouseButton) {
|
ElementState::Released => {
|
||||||
// Handle mouse input here
|
self.keys_pressed.remove(&keycode);
|
||||||
}
|
|
||||||
|
|
||||||
fn draw(&mut self) -> Result<(), Box<dyn Error>> {
|
|
||||||
//Draw ray_num in a block
|
|
||||||
let randomness = self.raytracing_options.ray_randomness;
|
|
||||||
let samples = self.raytracing_options.ray_samples;
|
|
||||||
let samples_f32 = samples as f32;
|
|
||||||
|
|
||||||
let num_threads = self.raytracing_options.threads;
|
|
||||||
let pixels_per_thread = self.raytracing_options.pixels_per_thread;
|
|
||||||
|
|
||||||
let mut handles = vec![];
|
|
||||||
|
|
||||||
for _ in 0..num_threads {
|
|
||||||
//Get necessary variables to render
|
|
||||||
let rays = self.rays.clone();
|
|
||||||
let scene = self.scene.clone();
|
|
||||||
let options = self.raytracing_options.clone();
|
|
||||||
let bvh = self.bvh.clone();
|
|
||||||
|
|
||||||
//Get the workload for a thread
|
|
||||||
let mut load = vec![];
|
|
||||||
for _ in 0..pixels_per_thread {
|
|
||||||
match self.ray_queue.pop() {
|
|
||||||
Some(index) => load.push(index),
|
|
||||||
None => break,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
//The finished queue of the thread
|
}
|
||||||
let mut finished = vec![];
|
}
|
||||||
|
|
||||||
//Create a new thread for these pixels
|
fn mouse_input(&mut self, button: &MouseButton, state: &ElementState) {
|
||||||
let handle = thread::spawn({
|
if *button == MouseButton::Right {
|
||||||
move || {
|
self.right_mouse_down = *state == ElementState::Pressed;
|
||||||
for index in &load {
|
if !self.right_mouse_down {
|
||||||
//Shade colour for selected index
|
self.last_mouse_pos = None;
|
||||||
let mut colour: Vector3<f32> = Vector3::zeros();
|
}
|
||||||
let ray = &rays[*index];
|
}
|
||||||
for _ in 0..samples {
|
}
|
||||||
//Generate a ray in a random direction
|
|
||||||
let point = ray.a;
|
|
||||||
let dir = ray.b;
|
|
||||||
let rx = (random::<f64>() - 0.5) / randomness;
|
|
||||||
let ry = (random::<f64>() - 0.5) / randomness;
|
|
||||||
let rz = (random::<f64>() - 0.5) / 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));
|
fn cursor_moved(&mut self, x: f64, y: f64) {
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(ray_colour) = rand_ray.shade_ray(&scene, 0, &options, &bvh)
|
fn process_camera_movement(&mut self) {
|
||||||
{
|
let speed = CAMERA_MOVE_SPEED;
|
||||||
colour += ray_colour;
|
|
||||||
}
|
if self.keys_pressed.contains(&VirtualKeyCode::W) {
|
||||||
}
|
self.camera.move_forward(speed);
|
||||||
colour = (colour / samples_f32) * 255.0;
|
self.camera_dirty = true;
|
||||||
let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff];
|
}
|
||||||
finished.push(rgba);
|
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();
|
||||||
|
for (index, rgba) in results {
|
||||||
|
frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba);
|
||||||
}
|
}
|
||||||
return (load, finished);
|
|
||||||
}
|
}
|
||||||
});
|
Err(mpsc::TryRecvError::Empty) => break,
|
||||||
handles.push(handle);
|
Err(mpsc::TryRecvError::Disconnected) => {
|
||||||
|
// All worker threads have finished
|
||||||
|
self.rendering = false;
|
||||||
|
self.gui.stop_render_timer();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut all_results = vec![];
|
|
||||||
|
|
||||||
for handle in handles.drain(..) {
|
|
||||||
let (load, finished) = handle
|
|
||||||
.join()
|
|
||||||
.map_err(|e| format!("Thread panicked: {:?}", e))?;
|
|
||||||
let thread_results: Vec<_> = load.into_iter().zip(finished.into_iter()).collect();
|
|
||||||
all_results.extend(thread_results);
|
|
||||||
}
|
|
||||||
|
|
||||||
//Now we have two vectors will all the indicies and rgba values, we can upload them to the bufer
|
|
||||||
|
|
||||||
let frame = self.pixels.frame_mut();
|
|
||||||
for result in all_results {
|
|
||||||
let index = result.0;
|
|
||||||
let rgba = result.1;
|
|
||||||
frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba);
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn clear_buffer(&mut self) -> Result<(), Box<dyn Error>> {
|
fn clear_buffer(&mut self) -> Result<(), Box<dyn Error>> {
|
||||||
@@ -295,26 +330,113 @@ impl State {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn reset_queue(&mut self) {
|
fn reset_queue(&mut self) {
|
||||||
|
// Signal any existing workers to stop
|
||||||
|
self.render_active.store(false, Ordering::Relaxed);
|
||||||
|
|
||||||
match self.raytracing_options.bvh_active {
|
match self.raytracing_options.bvh_active {
|
||||||
true => self.bvh = Arc::new(Some(BVH::build(&self.scene.nodes))),
|
true => self.bvh = Arc::new(Some(BVH::build(&self.scene.nodes))),
|
||||||
false => self.bvh = Arc::new(None),
|
false => self.bvh = Arc::new(None),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create new shuffled queue
|
||||||
let size = self.buffer_height as usize * self.buffer_width as usize;
|
let size = self.buffer_height as usize * self.buffer_width as usize;
|
||||||
let mut ray_queue: Vec<usize> = (0..size).collect();
|
let mut ray_queue: Vec<usize> = (0..size).collect();
|
||||||
ray_queue.shuffle(&mut thread_rng());
|
ray_queue.shuffle(&mut thread_rng());
|
||||||
self.ray_queue = ray_queue;
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
batch
|
||||||
|
};
|
||||||
|
|
||||||
|
if load.is_empty() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 dir = ray.b;
|
||||||
|
let rx = (random::<f64>() - 0.5) / randomness;
|
||||||
|
let ry = (random::<f64>() - 0.5) / randomness;
|
||||||
|
let rz = (random::<f64>() - 0.5) / 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 / samples_f32) * 255.0;
|
||||||
|
let rgba = [colour.x as u8, colour.y as u8, colour.z as u8, 0xff];
|
||||||
|
results.push((*index, rgba));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Send results back to main thread
|
||||||
|
if tx.send(results).is_err() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Drop our copy of tx so the channel disconnects when all workers finish
|
||||||
|
drop(tx);
|
||||||
|
|
||||||
|
self.gui.start_render_timer();
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
||||||
match self.draw() {
|
self.draw();
|
||||||
Err(e) => {
|
|
||||||
println!("ERROR: {}", e);
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
}
|
|
||||||
// Render Gui
|
// Render Gui
|
||||||
self.gui
|
self.gui
|
||||||
.prepare(&self.window)
|
.prepare(&self.window)
|
||||||
@@ -355,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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user