1. Camera movable with mouse and keyboard 2. GUI runs in separate thread 3. Improvement to GUI widgets 4. Fixes to BVH

This commit is contained in:
2026-03-08 20:13:34 +00:00
parent d64085461a
commit fa31d18c12
8 changed files with 629 additions and 298 deletions

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

@@ -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 }
} }
@@ -50,50 +50,22 @@ impl AABB {
let t1 = (bln - 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 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 ray with some epsilon tolerance
false
}
// Intersect way with some epsilon term
pub fn intersect_ray_aprox(&self, ray: &Ray) -> bool { pub fn intersect_ray_aprox(&self, ray: &Ray) -> bool {
let bln = &self.bln; let bln = &self.bln;
let trf = &self.trf; let trf = &self.trf;
let t1 = (bln - 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 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 > -EPSILON
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 +98,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 +127,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 +278,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 +365,25 @@ impl BVH {
return None; return None;
} }
if bvh_node.prim_count != 0 { if bvh_node.prim_count != 0 {
// Leaf node intersection // Leaf node intersection — test all primitives in the leaf
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;
for i in 0..bvh_node.prim_count {
let node = &self.nodes[bvh_node.first_prim + i];
if !node.active { if !node.active {
return None; continue;
} }
if let Some(intersect) = node.intersect_ray(&ray) { if let Some(intersect) = node.intersect_ray(&ray) {
if intersect.distance < EPSILON { if intersect.distance < EPSILON {
return None; continue;
} else { }
return Some((node, intersect)); if intersect.distance < closest_dist {
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,15 +417,15 @@ 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 { match cost > 0.0 {
true => 0.0, true => cost,
false => 1e30, false => 1e30,
} }
} }

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

@@ -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,

View File

@@ -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);
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 world_origin = ray.a; // Save world-space origin before transform
let ray = ray.transform(&self.inv_model); //Transform from world coordinates let 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(&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, &world_origin);
return Some(intersect); return Some(intersect);
} }
return None; return None;

View File

@@ -47,14 +47,12 @@ 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]) => {
if x1 <= 0.0 && x2 <= 0.0 { if x1 > EPSILON {
return None;
} else {
if x1.abs() < x2.abs() {
x1 x1
} else { } else if x2 > EPSILON {
x2 x2
} } else {
return None;
} }
} }
_ => return None, _ => return None,
@@ -124,7 +122,7 @@ 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 < EPSILON || t > INFINITY {
return None; return None;
}; };
@@ -197,14 +195,12 @@ 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]) => {
if x1 <= 0.0 && x2 <= 0.0 { if x1 > EPSILON {
return None;
} else {
if x1.abs() < x2.abs() {
Some(x1) Some(x1)
} else { } else if x2 > EPSILON {
Some(x2) Some(x2)
} } else {
return None;
} }
} }
_ => return None, _ => return None,
@@ -325,14 +321,12 @@ 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]) => {
if x1 <= 0.0 && x2 <= 0.0 { if x1 > EPSILON {
None
} else {
if x1.abs() < x2.abs() {
Some(x1) Some(x1)
} else { } else if x2 > EPSILON {
Some(x2) Some(x2)
} } else {
None
} }
} }
_ => None, _ => None,
@@ -359,7 +353,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_distance = distance(&ray.a, &cone_intersect.point);
let circle_distance = distance(&ray.a, &circle_intersect.point);
if cone_distance < circle_distance {
Some(cone_intersect)
} else {
Some(circle_intersect)
}
}
} }
} }
@@ -395,7 +397,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 < EPSILON || t > INFINITY {
return None; return None;
} }
let intersect = ray.at_t(t); let intersect = ray.at_t(t);
@@ -470,21 +472,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 +654,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 +814,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, -self.outer_rad, -extent);
let bln = Point3::new(-1.0, -1.0, -1.0); let trf = Point3::new(extent, self.outer_rad, extent);
AABB::new(bln, trf) AABB::new(bln, trf)
} }
} }
@@ -846,19 +855,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 {

View File

@@ -168,6 +168,28 @@ impl Ray {
// Compute the ambient light component and set it as base colour // 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 +214,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 +239,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,18 +254,8 @@ 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; return false;
} }

View File

@@ -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);
}
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 {
fn draw(&mut self) -> Result<(), Box<dyn Error>> { self.last_mouse_pos = None;
//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
let handle = thread::spawn({
move || {
for index in &load {
//Shade colour for selected index
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));
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];
finished.push(rgba);
}
return (load, finished);
}
});
handles.push(handle);
} }
let mut all_results = vec![]; fn cursor_moved(&mut self, x: f64, y: f64) {
if self.right_mouse_down {
for handle in handles.drain(..) { if let Some((last_x, last_y)) = self.last_mouse_pos {
let (load, finished) = handle let dx = x - last_x;
.join() let dy = y - last_y;
.map_err(|e| format!("Thread panicked: {:?}", e))?; self.camera.orbit(
let thread_results: Vec<_> = load.into_iter().zip(finished.into_iter()).collect(); -dx * CAMERA_ORBIT_SPEED,
all_results.extend(thread_results); -dy * CAMERA_ORBIT_SPEED,
);
self.camera_dirty = true;
}
self.last_mouse_pos = Some((x, y));
}
} }
//Now we have two vectors will all the indicies and rgba values, we can upload them to the bufer 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 result in all_results { for (index, rgba) in results {
let index = result.0;
let rgba = result.1;
frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba);
} }
Ok(()) }
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>> { 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;
} }