diff --git a/img.png b/img.png new file mode 100644 index 0000000..e2786ea Binary files /dev/null and b/img.png differ diff --git a/scene.rhai b/scene.rhai index 088555c..cacc15e 100644 --- a/scene.rhai +++ b/scene.rhai @@ -19,12 +19,12 @@ let sphere = Sphere(P(0.0,0.0,0.0), 1.0, material); let sphere_node = Node(sphere); scene.addNode(sphere_node); -for i in 0..6 { - let sphere = Sphere(P(0.0,0.0,0.0), 2.0, material); - let sphere_node = Node(sphere); - sphere_node.translate(V(2.0*cos(i.to_float()), -4.0, 2.0*sin(i.to_float()))); - scene.addNode(sphere_node); -} +// for i in 0..6 { +// let sphere = Sphere(P(0.0,0.0,0.0), 2.0, material); +// let sphere_node = Node(sphere); +// sphere_node.translate(2.0*cos(i.to_float()), -4.0, 2.0*sin(i.to_float())); +// scene.addNode(sphere_node); +// } // let child = sphere_node.child(sphere); // child.translate(V(1.0,1.0,1.0)); //scene.addNode(child); diff --git a/src/camera.rs b/src/camera.rs index 066321f..74b1017 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,17 +1,18 @@ use nalgebra::{Matrix4, Point3, Vector3}; -#[allow(dead_code)] +/// Annotate the Camera struct #[derive(Clone)] pub struct Camera { - eye: Point3, - target: Point3, - up: Vector3, - pub view: Matrix4, - pub inv_view: Matrix4, + pub eye: Point3, + pub target: Point3, + pub up: Vector3, + pub _view: Matrix4, + pub _inv_view: Matrix4, } #[allow(dead_code)] impl Camera { + /// Create a new camera with the given eye, target, and up vectors pub fn new(eye: Point3, target: Point3, up: Vector3) -> Self { let view = Matrix4::look_at_lh(&eye, &target, &up); let inv_view = view.try_inverse().unwrap(); @@ -19,11 +20,12 @@ impl Camera { eye, target, up, - view, - inv_view, + _view: view, + _inv_view: inv_view, } } + /// Create a unit camera with default parameters pub fn unit() -> Self { let eye = Point3::new(0.0, 0.0, 1.0); let target = Point3::new(0.0, 0.0, 0.0); @@ -31,23 +33,27 @@ impl Camera { Camera::new(eye, target, up) } + /// Set the position of the camera's eye pub fn set_eye(&mut self, new_eye: Point3) { self.eye = new_eye; self.recalculate_matrix(); } + /// Set the position the camera is looking at pub fn set_target(&mut self, new_target: Point3) { self.target = new_target; self.recalculate_matrix(); } + /// Set the up vector of the camera pub fn set_up(&mut self, new_up: Vector3) { self.up = new_up; self.recalculate_matrix(); } + /// Recalculate the view and inverse view matrices based on the current eye, target, and up vectors fn recalculate_matrix(&mut self) { - self.view = Matrix4::look_at_lh(&self.eye, &self.target, &self.up); - self.inv_view = self.view.try_inverse().unwrap(); + self._view = Matrix4::look_at_lh(&self.eye, &self.target, &self.up); + self._inv_view = self._view.try_inverse().unwrap(); } } diff --git a/src/gui.rs b/src/gui.rs index 400c2ce..82c7f81 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -4,7 +4,7 @@ use crate::{ primitive::*, scene::{Node, Scene}, state::{INIT_FILE, SAVE_FILE}, - UP_VECTOR_F32, ZERO_VECTOR_F32, + EPSILON, }; use imgui::*; use nalgebra::{Point3, Vector3}; @@ -16,7 +16,7 @@ const BUFFER_PROPORTION_INIT: f32 = 0.2; const BUFFER_PROPORTION_MIN: f32 = 0.1; const BUFFER_PROPORTION_MAX: f32 = 1.0; -const RAYS_INIT: i32 = 1000; +const RAYS_INIT: i32 = 7000; const RAYS_MIN: i32 = 100; const RAYS_MAX: i32 = 30000; @@ -27,11 +27,10 @@ const CAMERA_INIT: f32 = 5.0; /// Manages all state required for rendering Dear ImGui over `Pixels`test. pub enum GuiEvent { BufferResize(f32, f32), - CameraUpdate(Camera), + CameraUpdate(Camera, f32), SceneLoad(Scene), SaveImage(String), } - pub struct Gui { imgui: imgui::Context, platform: imgui_winit_support::WinitPlatform, @@ -115,20 +114,19 @@ impl Gui { buffer_proportion: BUFFER_PROPORTION_INIT, camera_eye: [CAMERA_INIT, CAMERA_INIT, CAMERA_INIT], - camera_target: ZERO_VECTOR_F32.into(), - camera_up: UP_VECTOR_F32.into(), + camera_target: Vector3::zeros().into(), + camera_up: Vector3::y().into(), camera_fov: 110.0, image_filename: String::from(SAVE_FILE), } } - /// Prepare Dear ImGuBi. + /// Prepare Dear ImGui. pub fn prepare( &mut self, window: &winit::window::Window, ) -> Result<(), winit::error::ExternalError> { - // Prepare Dear ImGui let now = Instant::now(); self.imgui.io_mut().update_delta_time(now - self.last_frame); self.last_frame = now; @@ -153,26 +151,27 @@ impl Gui { } //Top Menu Bar - let mut about_open = false; - ui.main_menu_bar(|| { - ui.menu("Help", || { - about_open = ui.menu_item("About..."); - }); - }); + // let mut about_open = false; + // ui.main_menu_bar(|| { + // ui.menu("Help", || { + // about_open = ui.menu_item("About..."); + // }); + // }); - //Raytracing options + //Raytracing options ------------------------------------------- if CollapsingHeader::new("Raytracer").build(ui) { - //Ray Renderer + // Numbers of rays to render ui.slider("# Rays: ", RAYS_MIN, RAYS_MAX, &mut self.ray_num); - //Buffer Options + // Proportion of the window the buffer occupies ui.slider( "% Buffer: ", BUFFER_PROPORTION_MIN, BUFFER_PROPORTION_MAX, &mut self.buffer_proportion, ); + // Fov of the buffer ui.slider("fov", CAMERA_MIN_FOV, CAMERA_MAX_FOV, &mut self.camera_fov); - //Apply changes + // Apply stored changes if ui.button("Apply") { self.event = Some(GuiEvent::BufferResize( self.buffer_proportion, @@ -180,13 +179,13 @@ impl Gui { )); }; } - //Camera options + // CAMERA OPTIONS ---------------------------------------- if CollapsingHeader::new("Camera").build(ui) { + // Eye, target and up vector inputs ui.text("Camera options:"); ui.input_float3("Eye", &mut self.camera_eye).build(); ui.input_float3("Target", &mut self.camera_target).build(); ui.input_float3("Up", &mut self.camera_up).build(); - // Create three input fields for x, y, and z components if ui.button("Apply Camera") { println!("Camera changed: {:?}", self.camera_eye); let (eye, target, up) = (&self.camera_eye, &self.camera_target, &self.camera_up); @@ -198,21 +197,25 @@ impl Gui { Point3::new(tx, ty, tz), Vector3::new(ux, uy, uz), ); - self.event = Some(GuiEvent::CameraUpdate(camera)); + self.event = Some(GuiEvent::CameraUpdate(camera, self.camera_fov)); } } - //Scripting + // SCRIPTING -------------------------------------------- if CollapsingHeader::new("Scripting").build(ui) { - //Import from file (We just want to replace the contents of self.script) + // Import file into multiline script ui.input_text("Scene file", &mut self.script_filename) .build(); if ui.button("Import from File") { - match std::fs::read_to_string(&self.script_filename) { - Ok(script) => self.script = script, - Err(e) => println!("{e}"), + match std::fs::read_to_string(&mut self.script_filename) { + Ok(script) => { + self.script = script; + } + Err(e) => println!("{}", e), } } - if ui.button("Apply script") { + ui.same_line(); + // Load scene from multiline script using engine + if ui.button("Load scene") { match self.engine.eval(&self.script) { Ok(scene) => { self.scene = scene; @@ -221,24 +224,68 @@ impl Gui { Err(e) => println!("{e}"), } } + ui.same_line(); + // Save script to file if ui.button("Save script") { match std::fs::write(&self.script_filename, &self.script) { Ok(_) => println!("Script saved successfully"), Err(e) => println!("{}", e), } } - //Script block - ui.input_text_multiline("script", &mut self.script, [600., 1500.]) + // Multiline script + ui.input_text_multiline("##", &mut self.script, [900., 300.]) .build(); } - + // IMAGE -------------------------------------------- if CollapsingHeader::new("Image").build(ui) { + // Image filename ui.input_text("Image file", &mut self.image_filename) .build(); + // Save image to file if ui.button("Save Image") { self.event = Some(GuiEvent::SaveImage(self.image_filename.clone())); } } + // SCENE -------------------------------------------- + if CollapsingHeader::new("Scene").build(ui) { + if ui.button("Update Scene") { + for node in &mut self.scene.nodes { + node.compute(); + } + self.event = Some(GuiEvent::SceneLoad(self.scene.clone())); + } + // Edit transformation of nodes + if let Some(_t) = ui.tree_node("Nodes") { + for node in &mut self.scene.nodes { + ui.text("node"); + ui.slider_config("Translation", -10.0, 10.0) + .build_array(&mut node.translation); + ui.slider_config("Rotation", -180.0, 180.0) + .build_array(&mut node.rotation); + ui.slider_config("Scale", -10.0, 10.0) + .build_array(&mut node.scale); + } + } + //Edit color, position and falloff of lights + if let Some(_t) = ui.tree_node("Lights") { + for light in &mut self.scene.lights { + ui.slider_config("Colour", 0.0, 1.0) + .build_array(light.colour.as_mut_slice()); + ui.slider_config("Position", -10.0, 10.0) + .build_array(light.position.coords.as_mut_slice()); + ui.slider_config("Falloff", 0.0, f32::MAX) + .build_array(light.falloff.as_mut_slice()); + } + } + //Use different cameras in the scene + if let Some(_t) = ui.tree_node("Cameras") { + for camera in &self.scene.cameras { + if ui.button("Use camera") { + GuiEvent::CameraUpdate(camera.clone(), self.camera_fov); + } + } + } + } // Render Dear ImGui with WGPU let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { diff --git a/src/light.rs b/src/light.rs index a1226f2..82d8f85 100644 --- a/src/light.rs +++ b/src/light.rs @@ -3,13 +3,15 @@ use nalgebra::{Point3, Vector3}; #[derive(Clone)] pub struct Light { pub position: Point3, - pub colour: Vector3, - pub falloff: Vector3, + pub colour: Vector3, + pub falloff: Vector3, pub ambient: bool, } impl Light { pub fn new(position: Point3, colour: Vector3, falloff: Vector3) -> Self { + let colour = colour.cast(); + let falloff = falloff.cast(); Light { position, colour, @@ -20,7 +22,7 @@ impl Light { pub fn ambient(colour: Vector3) -> Self { Light { position: Point3::new(0.0, 0.0, 0.0), - colour, + colour: colour.cast(), falloff: Vector3::new(0.0, 0.0, 0.0), ambient: true, } diff --git a/src/main.rs b/src/main.rs index 2396e67..cea87e9 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,11 +2,7 @@ use crate::state::run; use error_iter::ErrorIter; const EPSILON: f64 = 1e-6; -const INFINITY: f64 = f64::MAX; -const EPSILON_VECTOR: Vector3 = Vector3::new(EPSILON, EPSILON, EPSILON); -static ZERO_VECTOR: Vector3 = Vector3::new(0.0, 0.0, 0.0); -static ZERO_VECTOR_F32: Vector3 = Vector3::new(0.0, 0.0, 0.0); -static UP_VECTOR_F32: Vector3 = Vector3::new(0.0, 1.0, 0.0); +const INFINITY: f64 = 1e-10; use log::error; use std::env; @@ -21,8 +17,6 @@ mod raytracer; mod scene; mod state; -use nalgebra::Vector3; - fn main() { env_logger::init(); diff --git a/src/primitive.rs b/src/primitive.rs index 59b3ba2..d212e66 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -1,6 +1,6 @@ #[allow(dead_code)] use crate::ray::Ray; -use crate::{EPSILON, EPSILON_VECTOR, INFINITY}; +use crate::{EPSILON, INFINITY}; use nalgebra::{distance, Matrix4, Point3, Vector3}; use roots::{find_roots_quadratic, find_roots_quartic, Roots}; use std::fs::File; @@ -9,17 +9,21 @@ use std::sync::Arc; // MATERIAL ----------------------------------------------------------------- #[derive(Clone)] pub struct Material { - pub kd: Vector3, - pub ks: Vector3, - pub shininess: f64, + pub kd: Vector3, + pub ks: Vector3, + pub shininess: f32, } impl Material { pub fn new(kd: Vector3, ks: Vector3, shininess: f64) -> Arc { + let kd = kd.cast(); + let ks = ks.cast(); + let shininess = shininess as f32; Arc::new(Material { kd, ks, shininess }) } pub fn magenta() -> Arc { let kd = Vector3::new(1.0, 0.0, 1.0); + let ks = Vector3::new(1.0, 0.0, 1.0); let shininess = 0.5; Arc::new(Material { kd, ks, shininess }) @@ -81,8 +85,8 @@ struct BoundingBox { impl BoundingBox { fn new(bln: Point3, trf: Point3) -> Self { - let bln = bln - EPSILON_VECTOR; - let trf = trf + EPSILON_VECTOR; + let bln = bln + Vector3::new(EPSILON, EPSILON, EPSILON); + let trf = trf - Vector3::new(EPSILON, EPSILON, EPSILON); BoundingBox { bln, trf } } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { diff --git a/src/ray.rs b/src/ray.rs index fe80574..a726f8d 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -2,11 +2,11 @@ use crate::{ primitive::Intersection, raytracer::phong_shade_point, scene::{Node, Scene}, - INFINITY, }; use nalgebra::{Matrix4, Point3, Vector3}; #[derive(Clone)] +// Ray struct represents a ray in 3D space with a starting point 'a' and a direction 'b' pub struct Ray { pub a: Point3, pub b: Vector3, @@ -14,91 +14,109 @@ pub struct Ray { #[allow(dead_code)] impl Ray { + //Create a new ray with a normalized direction pub fn new(a: Point3, b: Vector3) -> Ray { Ray { a, b: b.normalize(), } } + // The starting point is the origin and the direction is negative z-axis pub fn unit() -> Ray { - let a = Point3::new(0.0, 0.0, 0.0); - let b = Vector3::new(0.0, 1.0, 0.0); + let a = Point3::origin(); + let b = -Vector3::z(); Ray { a, b } } + //Return the point at distance t along the ray pub fn at_t(&self, t: f64) -> Point3 { self.a + self.b * t } - //Shade a single ray + // This function takes a scene and returns the color of the point where the ray intersects the scene pub fn shade_ray(&self, scene: &Scene) -> Option> { + //Get the closest intersection of the ray with the scene let intersect = self.get_closest_intersection(&scene.nodes); - + //Shade the intersection point if there is one match intersect { - Some(intersect) => Some(phong_shade_point(&scene, &intersect)), - None => None, + Some(intersect) => Some(phong_shade_point(&scene, &intersect)), // If there is an intersection, shade it + None => None, // If there is no intersection, return None } } // Find the closest intersection pub fn get_closest_intersection(&self, nodes: &Vec) -> Option { - let mut closest_distance = INFINITY; + //Assign no intersection + let mut closest_distance = f64::MAX; let mut closest_intersect: Option = None; for node in nodes { + // Clone arc to primitive let primitive = node.primitive.clone(); - - //Transform ray from view coords - let ray = self.transform(&node.inv_viewmodel); - + // Transform ray into local model cordinates + let ray = self.transform(&node.inv_model); + // Check bounding box intersection if primitive.intersect_bounding_box(&ray).is_some() { + // Check primitive intersection if let Some(intersect) = primitive.intersect_ray(&ray) { + // Check for closest distance if intersect.distance < closest_distance { closest_distance = intersect.distance; //Convert back to world coords let intersect = intersect.transform(&node.model, &node.inv_model); - closest_intersect = Some(intersect); } } } } - + //Return None if we find no intersection, some if we do find one closest_intersect } - + // Return a transformed version of the ray pub fn transform(&self, trans: &Matrix4) -> Ray { Ray { a: trans.transform_point(&self.a), b: trans.transform_vector(&self.b), } } - - pub fn cast_rays(fovy: f64, width: u32, height: u32) -> Vec { - let aspect = width as f64 / height as f64; + //Cast a set of rays + pub fn cast_rays( + eye: &Point3, + target: &Point3, + up: &Vector3, + fovy: f64, + width: u32, + height: u32, + ) -> Vec { + //Aspect ratio calculation + let (width, height) = (width as f64, height as f64); + let aspect = width / height; + //X and Y fov calculations let fovy_radians = fovy.to_radians(); let fovh_radians = 2.0 * ((fovy_radians / 2.0).tan() * aspect).atan(); - - let dir = Vector3::new(0.0, 0.0, 1.0); - let up = Vector3::new(0.0, 1.0, 0.0); - let hor = Vector3::new(1.0, 0.0, 0.0); + // Vectors pointing forward, right and up + let forward = (target - eye).normalize(); + let right = forward.cross(&up).normalize(); + let up = right.cross(&forward).normalize(); + // ☐ height and width of projection let vheight = 2.0 * (fovy_radians / 2.0).tan(); let vwidth = 2.0 * (fovh_radians / 2.0).tan(); - - let d_hor_vec = hor * (vwidth / width as f64) as f64; - let d_vert_vec = up * (vheight / height as f64) as f64; - - let half_width = width / 2; - let half_height = height / 2; - + // Increment of right and up per pixel + let d_hor_vec = right * (vwidth / width); + let d_vert_vec = up * (vheight / height); + // Half the width for later calculation + let half_width = width / 2.0; + let half_height = height / 2.0; + // Array of rays let mut rays = Vec::with_capacity(width as usize * height as usize); + // Iterate column by row + for row in 0..height as u32 { + for column in 0..width as u32 { + let x = (column as f64) - half_width; + let y = half_height - (row as f64); - for j in 0..height as i32 { - for i in 0..width as i32 { - let x = i - half_width as i32; - let y = -j + half_height as i32; - let horizontal = x as f64 * d_hor_vec; - let vertical = y as f64 * (d_vert_vec); - let direction = dir + horizontal + vertical; - let ray = Ray::new(Point3::new(0.0, 0.0, 0.0), direction); + let horizontal = x * &d_hor_vec; + let vertical = y * &d_vert_vec; + let direction = (forward + horizontal + vertical).normalize(); + let ray = Ray::new(eye.clone(), direction); rays.push(ray); } } diff --git a/src/raytracer.rs b/src/raytracer.rs index b4dd939..f367dc0 100644 --- a/src/raytracer.rs +++ b/src/raytracer.rs @@ -1,4 +1,4 @@ -use crate::{light::Light, primitive::Intersection, ray::Ray, scene::*, EPSILON, ZERO_VECTOR}; +use crate::{light::Light, primitive::Intersection, ray::Ray, scene::*, EPSILON}; use nalgebra::{Unit, Vector3}; @@ -17,7 +17,7 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 let shininess = material.shininess; // Compute the ambient light component and set it as base colour - let mut colour = ZERO_VECTOR; + let mut colour = Vector3::zeros(); for light in &scene.lights { let Light { @@ -34,7 +34,7 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 // Point to light let to_light = light_position - point; - let light_distance = to_light.norm(); + let light_distance = to_light.norm() as f32; let to_light = to_light; let to_light_ray = Ray::new(point.clone() + normal * EPSILON, to_light); @@ -45,14 +45,14 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 // Point to camera let to_camera = -incidence; // Diffuse component - let n_dot_l = normal.dot(&to_light).max(0.0); + let n_dot_l = normal.dot(&to_light).max(0.0) as f32; let diffuse = n_dot_l * kd; // Specular component - let mut specular = ZERO_VECTOR; + let mut specular = Vector3::zeros(); if n_dot_l > 0.0 { // Halfway vector. let h = Unit::new_normalize(to_camera.lerp(&to_light, 0.5)); - let n_dot_h = normal.dot(&h).max(0.0); + let n_dot_h = normal.dot(&h).max(0.0) as f32; specular = ks * n_dot_h.powf(shininess); } diff --git a/src/scene.rs b/src/scene.rs index 4b23d6a..fee510b 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -5,59 +5,84 @@ use nalgebra::{Matrix4, Vector3}; use std::sync::Arc; #[derive(Clone)] pub struct Node { + //Primitive pub primitive: Arc, + //Transformations + pub rotation: [f32; 3], + pub scale: [f32; 3], + pub translation: [f32; 3], + //Model matricies pub model: Matrix4, pub inv_model: Matrix4, - pub viewmodel: Matrix4, - pub inv_viewmodel: Matrix4, } impl Node { + //New node with no transformations pub fn new(primitive: Arc) -> Node { Node { primitive, + rotation: [0.0, 0.0, 0.0], + scale: [1.0, 1.0, 1.0], + translation: [0.0, 0.0, 0.0], model: Matrix4::identity(), inv_model: Matrix4::identity(), - viewmodel: Matrix4::identity(), - inv_viewmodel: Matrix4::identity(), } } + //New node with parent transformations + pub fn child(self, primitive: Arc) -> Node { + let mut child = self.clone(); + child.primitive = primitive; + child + } + //Rotate a mesh by adding to its rotation pub fn rotate(&mut self, roll: f64, pitch: f64, yaw: f64) { + //Convert to radians let roll = roll.to_radians(); + // Convert pitch and yaw to radians let pitch = pitch.to_radians(); let yaw = yaw.to_radians(); - let rotation_matrix = Matrix4::from_euler_angles(roll, pitch, yaw); - self.model = rotation_matrix * self.model; - self.inv_model = self.model.try_inverse().unwrap(); - self.viewmodel = rotation_matrix * self.viewmodel; - self.inv_viewmodel = self.inv_viewmodel.try_inverse().unwrap(); + + // Add the roll, pitch, and yaw to the current rotation + self.rotation[0] += roll as f32; + self.rotation[1] += pitch as f32; + self.rotation[2] += yaw as f32; + + // Recompute the model and inverse model matrices + self.compute(); } - pub fn translate(&mut self, translation: Vector3) { + // Translate a mesh by adding to its current position + pub fn translate(&mut self, x: f64, y: f64, z: f64) { + self.translation[0] += x as f32; + self.translation[1] += y as f32; + self.translation[2] += z as f32; + + // Recompute the model and inverse model matrices + self.compute(); + } + // Scale a mesh by adding to its current scale + pub fn scale(&mut self, x: f64, y: f64, z: f64) { + self.scale[0] += x as f32; + self.scale[1] += y as f32; + self.scale[2] += z as f32; + + // Recompute the model and inverse model matrices + self.compute(); + } + // This function computes the model and inverse model matrices + pub fn compute(&mut self) { + //Translation matrix + let translation = Vector3::from_row_slice(&self.translation); let translation_matrix = Matrix4::new_translation(&translation); - self.model = translation_matrix * self.model; - self.inv_model = self.model.try_inverse().unwrap(); - self.viewmodel = translation_matrix * self.viewmodel; - self.inv_viewmodel = self.inv_viewmodel.try_inverse().unwrap(); - } - pub fn scale(&mut self, scale: Vector3) { + // Scale matrix + let scale = &Vector3::from_row_slice(&self.scale); let scale_matrix = Matrix4::new_nonuniform_scaling(&scale); - self.model = scale_matrix * self.model; + // Rotation matrix + let (roll, pitch, yaw) = (self.rotation[0], self.rotation[1], self.rotation[2]); + let rotation_matrix = Matrix4::from_euler_angles(roll, pitch, yaw); + // Compute the model matrix by combining the translation, rotation, and scale matrices + self.model = (translation_matrix * rotation_matrix * scale_matrix).cast(); + // Compute the inverse model matrix by inverting the model matrix self.inv_model = self.model.try_inverse().unwrap(); - self.viewmodel = scale_matrix * self.viewmodel; - self.inv_viewmodel = self.inv_viewmodel.try_inverse().unwrap(); - } - pub fn child(self, primitive: Arc) -> Node { - Node { - primitive, - model: self.model, - inv_model: self.inv_model, - viewmodel: self.model, - inv_viewmodel: self.inv_model, - } - } - pub fn compute(&mut self, view: &Matrix4, inv_view: &Matrix4) { - self.viewmodel = view * self.model; - self.inv_viewmodel = self.inv_model * inv_view; } } @@ -79,21 +104,20 @@ impl Scene { cameras: Vec::new(), } } + // Adds a node to the scene pub fn add_node(&mut self, node: Node) { self.nodes.push(node); } + // Adds a material to the scene pub fn add_material(&mut self, material: Material) { self.materials.push(material); } + // Adds a light to the scene pub fn add_light(&mut self, light: Light) { self.lights.push(light); } + // Adds a camera to the scene pub fn add_camera(&mut self, camera: Camera) { self.cameras.push(camera); } - pub fn compute(&mut self, view: &Matrix4, inv_view: &Matrix4) { - for node in &mut self.nodes { - node.compute(view, inv_view); - } - } } diff --git a/src/state.rs b/src/state.rs index 1015cc4..42d4b64 100644 --- a/src/state.rs +++ b/src/state.rs @@ -6,13 +6,12 @@ use crate::{gui::Gui, scene::Scene}; use crate::{gui::GuiEvent, log_error}; use std::path::Path; +use nalgebra::{Point3, Vector3}; use rand::seq::SliceRandom; use rand::thread_rng; use std::error::Error; -use std::sync::{Arc, Mutex}; - use anyhow::Result; use pixels::{Pixels, SurfaceTexture}; use winit::dpi::{LogicalSize, PhysicalSize}; @@ -23,6 +22,7 @@ use winit::window::{Window, WindowBuilder}; const START_WIDTH: i32 = 1200; const START_HEIGHT: i32 = 1200; const COLOUR_CLEAR: [u8; 4] = [0x22, 0x00, 0x11, 0xff]; +const PIXEL_CLEAR: [u8; 4] = [0x55, 0x00, 0x22, 0xff]; pub const INIT_FILE: &str = "scene.rhai"; pub const SAVE_FILE: &str = "img.png"; @@ -35,7 +35,7 @@ pub struct State { buffer_width: u32, buffer_height: u32, - pixels: Arc>, + pixels: Pixels, gui: Gui, rays: Vec, @@ -46,7 +46,7 @@ impl State { pub fn new(window: Window, pixels: Pixels, gui: Gui) -> Self { let scene = Scene::empty(); let window_size = window.inner_size(); - let camera = Camera::unit(); + let camera = Camera::new(Point3::new(2.0, 2.0, 2.0), Point3::origin(), Vector3::y()); let rays = Vec::new(); Self { @@ -55,7 +55,7 @@ impl State { window, buffer_width: window_size.width as u32, buffer_height: window_size.height as u32, - pixels: Arc::new(Mutex::new(pixels)), + pixels: pixels, gui, rays, ray_queue: Vec::new(), @@ -68,18 +68,26 @@ impl State { GuiEvent::BufferResize(proportion, fov) => { self.resize_buffer(proportion, fov as f64)? } - GuiEvent::CameraUpdate(camera) => { + GuiEvent::CameraUpdate(camera, fovy) => { + self.rays = Ray::cast_rays( + &camera.eye, + &camera.target, + &camera.up, + fovy as f64, + self.buffer_width, + self.buffer_height, + ); self.camera = camera; self.clear()?; self.reset_queue(); } GuiEvent::SceneLoad(scene) => { self.scene = scene; + self.clear()?; self.reset_queue(); } GuiEvent::SaveImage(filename) => { - let pixels = self.pixels.lock().unwrap(); - let frame = pixels.frame(); + let frame = self.pixels.frame(); image::save_buffer( Path::new(&filename), frame, @@ -94,25 +102,35 @@ impl State { } fn resize_buffer(&mut self, proportion: f32, fovy: f64) -> Result<(), Box> { + // Calculate new buffer dimensions based on proportion let size = self.window.inner_size(); - self.buffer_width = (size.width as f32 * proportion) as u32; self.buffer_height = (size.height as f32 * proportion) as u32; + // Clear the buffer and reset the ray queue self.clear()?; self.reset_queue(); - self.rays = Ray::cast_rays(fovy, self.buffer_width, self.buffer_height); + // Recalculate rays with new buffer dimensions + self.rays = Ray::cast_rays( + &self.camera.eye, + &self.camera.target, + &self.camera.up, + fovy, + self.buffer_width, + self.buffer_height, + ); - let mut pixels = self.pixels.lock().unwrap(); - pixels.resize_buffer(self.buffer_width, self.buffer_height)?; + // Resize buffer and surface + let pixels = &mut self.pixels; pixels.resize_surface(size.width, size.height)?; + pixels.resize_buffer(self.buffer_width, self.buffer_height)?; + Ok(()) } fn resize(&mut self, size: &PhysicalSize) -> Result<(), Box> { - let mut pixels = self.pixels.lock().unwrap(); - pixels.resize_surface(size.width, size.height)?; + self.pixels.resize_surface(size.width, size.height)?; Ok(()) } @@ -127,29 +145,27 @@ impl State { } fn draw(&mut self) -> Result<(), Box> { + //Draw ray_num in a block for _ in 0..self.gui.ray_num { //Get random index from queue - let index = self.ray_queue.pop().unwrap(); + let index = match self.ray_queue.pop() { + Some(index) => index, + None => break, + }; //Shade colour for selected ray let colour = &self.rays[index].shade_ray(&self.scene); - //Assign colour to frame - let rgba = colour.map_or(COLOUR_CLEAR, |colour| [colour.x, colour.y, colour.z, 255]); - let mut pixels = self.pixels.lock().unwrap(); - let frame = pixels.frame_mut(); + //Assign colour to pixel in frame + let rgba = colour.map_or(PIXEL_CLEAR, |colour| [colour.x, colour.y, colour.z, 255]); + let frame = self.pixels.frame_mut(); frame[index * 4..(index + 1) * 4].copy_from_slice(&rgba); - - if self.ray_queue.is_empty() { - break; - }; } Ok(()) } fn clear(&mut self) -> Result<(), Box> { - let mut pixels = self.pixels.lock().unwrap(); - let frame = pixels.frame_mut(); + let frame = self.pixels.frame_mut(); for pixel in frame.chunks_exact_mut(4) { - pixel.copy_from_slice(&[0x00, 0x00, 0x00, 0xff]); + pixel.copy_from_slice(&COLOUR_CLEAR); } Ok(()) } @@ -159,26 +175,35 @@ impl State { let mut ray_queue: Vec = (0..size).collect(); ray_queue.shuffle(&mut thread_rng()); self.ray_queue = ray_queue; - self.scene.compute(&self.camera.view, &self.camera.inv_view); } fn render(&mut self) -> Result<(), Box> { - self.update()?; //Update state + // Update state + self.update()?; + // Draw rays if we have remaining rays in queue if !self.ray_queue.is_empty() { - self.draw()?; + match self.draw() { + Err(e) => { + println!("ERROR: {}", e); + } + _ => {} + } } - let pixels = self.pixels.lock().unwrap(); + // Render Gui self.gui .prepare(&self.window) - .expect("gui.prepare() failed"); //Prepare imgui - if let Err(e) = pixels.render_with(|encoder, render_target, context| { + .expect("gui.prepare() failed"); + // Try to render pixels + if let Err(e) = self.pixels.render_with(|encoder, render_target, context| { context.scaling_renderer.render(encoder, render_target); // Render pixels self.gui .render(&self.window, encoder, render_target, context)?; + Ok(()) }) { log_error("pixels.render", e); }; + Ok(()) } }