diff --git a/src/camera.rs b/src/camera.rs index 9357e37..7075a9f 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -12,6 +12,7 @@ pub const OPENGL_TO_WGPU_MATRIX: Matrix4 = Matrix4::new( 0.0, 0.0, 0.0, 1.0, ); +#[derive(Clone)] pub struct Camera { eye: Point3, target: Point3, @@ -65,7 +66,7 @@ impl Camera { (matrix, inverse) } - pub fn cast_rays(&self, width: i32, height: i32) -> Vec { + pub fn cast_rays(&self, width: u32, height: u32) -> Vec { //All good let aspect = width as f64 / height as f64; let fovy_radians = (self.fovy as f64).to_radians(); @@ -97,7 +98,7 @@ impl Camera { rays } - pub fn cast_ray(&self, width: i32, height: i32, x: i32, y: i32) -> Ray { + pub fn cast_ray(&self, width: u32, height: u32, x: u32, y: u32) -> Ray { let aspect = width as f64 / height as f64; let fovy_radians = (self.fovy as f64).to_radians(); let fovh_radians = 2.0 * ((fovy_radians / 2.0).tan() * aspect).atan(); diff --git a/src/gui.rs b/src/gui.rs index 23e5d08..09d54de 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,6 +1,14 @@ use pixels::{wgpu, PixelsContext}; use std::time::Instant; +const BUFFER_PROPORTION_INIT: f32 = 0.8; +const BUFFER_PROPORTION_MIN: f32 = 0.5; +const BUFFER_PROPORTION_MAX: f32 = 0.9; + +const RAYS_INIT: i32 = 100; +const RAYS_MIN: i32 = 100; +const RAYS_MAX: i32 = 1000; + /// Manages all state required for rendering Dear ImGui over `Pixels`. pub(crate) struct Gui { imgui: imgui::Context, @@ -10,7 +18,9 @@ pub(crate) struct Gui { last_cursor: Option, about_open: bool, - pub num_rays: i32, + pub ray_num: i32, + pub buffer_proportion: f32, + pub buffer_resize: bool, } impl Gui { @@ -60,7 +70,9 @@ impl Gui { last_frame: Instant::now(), last_cursor: None, about_open: true, - num_rays: 8, + ray_num: RAYS_INIT, + buffer_proportion: BUFFER_PROPORTION_INIT, + buffer_resize: false, } } @@ -96,11 +108,23 @@ impl Gui { // Draw windows and GUI elements here let mut about_open = false; ui.main_menu_bar(|| { - ui.menu("Options", || { + ui.menu("Help", || { about_open = ui.menu_item("About..."); }); }); - ui.slider("Num rays", 1, 100, &mut self.num_rays); + ui.slider("# Rays: ", RAYS_MIN, RAYS_MAX, &mut self.ray_num); + + ui.slider( + "% Buffer: ", + BUFFER_PROPORTION_MIN, + BUFFER_PROPORTION_MAX, + &mut self.buffer_proportion, + ); + let mut buffer_resize = false; + if ui.button("Change Buffer") { + buffer_resize = true + }; + self.buffer_resize = buffer_resize; // Render Dear ImGui with WGPU let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { diff --git a/src/main.rs b/src/main.rs index d8489c1..a186f30 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,21 +3,21 @@ #![allow(unused_variables)] //Use linear algebra module -//Cameras - -use crate::{camera::Camera, gui::Gui, light::Light, primitive::*, ray::Ray, scene::Scene}; +use crate::primitive::*; +use crate::{camera::Camera, gui::Gui, light::Light, ray::Ray, scene::Scene}; use log::error; +use std::rc::Rc; +use std::sync::{Arc, Mutex}; +use std::{env, thread, thread::JoinHandle}; + use error_iter::ErrorIter as _; use nalgebra::{Point3, Vector3}; use pixels::{Error, Pixels, SurfaceTexture}; -use std::env; -use std::rc::Rc; -use std::sync::Arc; use winit::dpi::{LogicalSize, PhysicalSize}; use winit::event::{Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent}; use winit::event_loop::{ControlFlow, EventLoop}; -use winit::window::WindowBuilder; +use winit::window::{Window, WindowBuilder}; use winit_input_helper::WinitInputHelper; mod camera; @@ -28,9 +28,10 @@ mod ray; mod raytracer; mod scene; -const START_WIDTH: i32 = 600; -const START_HEIGHT: i32 = 555; +const START_WIDTH: i32 = 800; +const START_HEIGHT: i32 = 800; const BOX_SIZE: i16 = 64; +const COLOUR_CLEAR: [u8; 4] = [0x22, 0x22, 0x11, 0xff]; const EPSILON: f32 = 1e-6; const INFINITY: f32 = f32::MAX; @@ -38,61 +39,78 @@ const EPSILON_VECTOR: Vector3 = Vector3::new(EPSILON, EPSILON, EPSILON); const INFINITY_VECTOR: Vector3 = Vector3::new(INFINITY, INFINITY, INFINITY); struct State { - scene: Scene, - camera: Camera, - rays: Vec, + scene: Arc, + window: Window, + pixels: Arc>, + gui: Gui, index: usize, - width: i32, - height: i32, - gui: Gui, - pixels: Pixels, + camera: Camera, + rays: Arc>, } impl State { /// Create a new `World` instance that can draw a moving box. - fn new( - width: i32, - height: i32, - scene: Scene, - camera: Camera, - pixels: Pixels, - gui: Gui, - ) -> Self { - let rays = camera.cast_rays(width, height); + fn new(window: Window, scene: Scene, camera: Camera) -> Self { + let window_size = window.inner_size(); + let pixels = { + let surface_texture = + SurfaceTexture::new(window_size.width, window_size.height, &window); + Pixels::new( + window_size.width as u32, + window_size.height as u32, + surface_texture, + ) + .unwrap() + }; + let gui = Gui::new(&window, &pixels); + let rays = camera.cast_rays(window_size.width, window_size.height); Self { - width, - height, - index: 0, - rays, - scene, - camera, - pixels, + scene: Arc::new(scene), + window, + pixels: Arc::new(Mutex::new(pixels)), gui, + + camera, + index: 0, + rays: Arc::new(rays), } } /// Update the `World` internal state; bounce the box around the screen. - fn update(&mut self) { - let gui = &self.gui; - if gui.reset { + fn update(&mut self) -> bool { + if self.gui.buffer_resize { + let pixels = &self.pixels; + let size = self.window.inner_size(); + let width_new = (size.width as f32 * self.gui.buffer_proportion) as u32; + let height_new = (size.height as f32 * self.gui.buffer_proportion) as u32; + self.clear(); + let mut pixels = self.pixels.lock().unwrap(); + if let Err(err) = pixels.resize_buffer(width_new, height_new) { + log_error("pixels.resize_surface", err); + return false; + } self.index = 0; + self.rays = Arc::new(self.camera.cast_rays(width_new, height_new)); } + true } /// Resize the world fn resize(&mut self, size: &PhysicalSize) -> bool { - if let Err(err) = self.pixels.resize_surface(size.width, size.height) { + println!("RESIZING!"); + let gui = &self.gui; + let mut pixels = self.pixels.lock().unwrap(); + if let Err(err) = pixels.resize_surface(size.width, size.height) { log_error("pixels.resize_surface", err); return false; } - self.width = size.width as i32; - self.height = size.height as i32; true } fn keyboard_input(&mut self, key: &KeyboardInput) { + println!("KEYBOARD INPUT"); match key.virtual_keycode { Some(key) => match key { VirtualKeyCode::A => {} @@ -101,45 +119,70 @@ impl State { None => {} } } - fn mouse_input(&mut self, button: &MouseButton) {} + fn mouse_input(&mut self, button: &MouseButton) { + println!("MOUSE INPUT"); + } /// Draw the `World` state to the frame buffer. /// /// Assumes the default texture format: `wgpu::TextureFormat::Rgba8UnormSrgb` fn draw(&mut self) { - let frame = self.pixels.frame_mut(); - for i in 0..self.gui.num_rays { - let ray = &self.rays[self.index]; - let colour = raytracer::shade_ray(&self.scene, &ray); + for i in 0..self.gui.ray_num { + let i = self.index as usize; + let ray_num = self.gui.ray_num; + let pixels = self.pixels.clone(); + let colour = { + let ray = &self.rays[i]; + raytracer::shade_ray(&self.scene, &ray) + }; + let rgba = match colour { Some(colour) => [colour.x, colour.y, colour.z, 255], - None => [122, 122, 122, 100], + None => COLOUR_CLEAR, }; - frame[self.index * 4..(self.index + 1) * 4].copy_from_slice(&rgba); + let mut pixels = self.pixels.lock().unwrap(); + let frame = pixels.frame_mut().chunks_exact_mut(4).nth(i).unwrap(); + frame.copy_from_slice(&rgba); self.index = self.index + 1; } } + + fn clear(&mut self) { + let mut pixels = self.pixels.lock().unwrap(); + let frame = pixels.frame_mut(); + for (i, pixel) in frame.chunks_exact_mut(4).enumerate() { + let rgba = [0x00, 0x00, 0x00, 0xff]; + pixel.copy_from_slice(&rgba); + } + } + + fn render(&mut self) -> bool { + self.update(); //Update state + self.draw(); //Draw to pixels + let pixels = self.pixels.lock().unwrap(); + self.gui + .prepare(&self.window) + .expect("gui.prepare() failed"); //Prepare imgui + let render_result = 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(()) + }); + if let Err(err) = render_result { + log_error("pixels.render", err); + return false; + } + true + } } fn main() -> Result<(), Error> { env_logger::init(); + env::set_var("RUST_BACKTRACE", "1"); //Window let event_loop = EventLoop::new(); - let window = { - let size = LogicalSize::new(START_WIDTH as f64, START_HEIGHT as f64); - WindowBuilder::new() - .with_title("Hello Pixels + Dear ImGui") - .with_inner_size(size) - .with_min_inner_size(size) - .build(&event_loop) - .unwrap() - }; - //Pixel surface - let pixels = { - let window_size = window.inner_size(); - let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); - Pixels::new(START_WIDTH as u32, START_HEIGHT as u32, surface_texture)? - }; + //SCENE //Camera let eye = Point3::new(10.0, 10.0, 10.0); let target = Point3::new(0.0, 0.0, 0.0); @@ -151,9 +194,7 @@ fn main() -> Result<(), Error> { 120.0, (START_WIDTH as f32 / START_HEIGHT as f32) as f32, ); - let cameras: Vec> = Vec::new(); // SETUP PRIMITIVES - let mut primitives: Vec> = Vec::new(); let magenta = Arc::new(Material::magenta()); let blue = Arc::new(Material::blue()); let turquoise = Arc::new(Material::turquoise()); @@ -161,34 +202,38 @@ fn main() -> Result<(), Error> { // primitives.push(sphere.clone()); // let cone = Arc::new(Cone::new(0.25, 1.0, -0.5, turquoise.clone())); // primitives.push(cone.clone()); - let cube = Arc::new(Box::unit(blue.clone())); - primitives.push(cube.clone()); + let mut primitives: Vec> = Vec::new(); + let cube = Box::new(Cube::unit(blue.clone())); + primitives.push(cube); //Lights - let mut lights: Vec> = Vec::new(); let light_pos = Point3::new(10.0, 12.0, 10.0); let light_colour = Vector3::new(1.0, 0.0, 1.0); let light_falloff = [1.0, 0.00, 0.00]; - let light = Arc::new(Light::new(light_colour, light_pos, light_falloff)); - lights.push(light.clone()); - let ambient_light = Arc::new(Vector3::new(0.0, 0.0, 0.2)); + let light = Light::new(light_colour, light_pos, light_falloff); + + let ambient_light = Vector3::new(0.0, 0.0, 0.2); + + let scene = Scene::new(primitives, vec![light], vec![camera.clone()], ambient_light); //State - // Set up Dear ImGui - let gui = Gui::new(&window, &pixels); - let scene = Scene::new(primitives, lights, cameras, ambient_light); - let pixels = { - let window_size = window.inner_size(); - let surface_texture = SurfaceTexture::new(window_size.width, window_size.height, &window); - Pixels::new(START_WIDTH as u32, START_HEIGHT as u32, surface_texture)? + let window = { + let size = LogicalSize::new(START_WIDTH, START_HEIGHT); + WindowBuilder::new() + .with_title("Hello Pixels + Dear ImGui") + .with_inner_size(size) + .with_min_inner_size(size) + .build(&event_loop) + .unwrap() }; - let mut state = State::new(START_WIDTH, START_HEIGHT, scene, camera, pixels, gui); + let mut state = State::new(window, scene, camera); event_loop.run(move |event, _, control_flow| { // Draw the current frame - state.gui.handle_event(&window, &event); //Let gui handle its events + state.gui.handle_event(&state.window, &event); //Let gui handle its events match event { Event::WindowEvent { event, .. } => match event { WindowEvent::CloseRequested => { *control_flow = ControlFlow::Exit; + return; } WindowEvent::Resized(size) => { state.resize(&size); @@ -202,22 +247,11 @@ fn main() -> Result<(), Error> { _ => {} }, Event::RedrawRequested(_) => { - state.draw(); //Draw to pixels - state.gui.prepare(&window).expect("gui.prepare() failed"); //Prepare imgui - let render_result = state.pixels.render_with(|encoder, render_target, context| { - context.scaling_renderer.render(encoder, render_target); // Render pixels - state.gui.render(&window, encoder, render_target, context)?; - Ok(()) - }); - if let Err(err) = render_result { - log_error("pixels.render", err); - *control_flow = ControlFlow::Exit; - } + state.render(); } _ => {} } - state.update(); //Update state - window.request_redraw(); //Redraw window + state.window.request_redraw(); //Redraw window }); } diff --git a/src/raytracer.rs b/src/raytracer.rs index e2bc34c..94c0feb 100644 --- a/src/raytracer.rs +++ b/src/raytracer.rs @@ -16,7 +16,7 @@ pub fn shade_rays(scene: &Scene, rays: &Vec, width: i32, height: i32) -> Ve let mut pixel_data = vec![Vector3::new(0, 0, 0); (width * height) as usize]; for (pixel_index, ray) in rays.iter().enumerate() { - let intersect = get_closest_intersection(scene.primitives.clone(), ray); + let intersect = get_closest_intersection(&scene.primitives, ray); let colour = match intersect { Some(intersect) => phong_shade_point(scene, &intersect), None => { @@ -30,7 +30,7 @@ pub fn shade_rays(scene: &Scene, rays: &Vec, width: i32, height: i32) -> Ve } //Shade a single ray pub fn shade_ray(scene: &Scene, ray: &Ray) -> Option> { - let intersect = get_closest_intersection(scene.primitives.clone(), ray); + let intersect = get_closest_intersection(&scene.primitives, ray); match intersect { Some(intersect) => Some(phong_shade_point(&scene, &intersect)), None => None, @@ -39,7 +39,7 @@ pub fn shade_ray(scene: &Scene, ray: &Ray) -> Option> { // Find the closest intersection, given a ray in world coordinates pub fn get_closest_intersection( - primitives: Vec>, + primitives: &Vec>, ray: &Ray, ) -> Option { let mut closest_distance = INFINITY; @@ -80,14 +80,14 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 // Let us first compute the ambient light component and set it as out base colour let mut colour = kd.component_mul(ambient_light); - for arc_light in &scene.lights { + for arc_light in scene.lights.as_ref() { let light = arc_light.clone(); let Light { position: light_position, colour: light_colour, falloff: light_falloff, - } = light.as_ref(); + } = light; // Get light incidence vector let to_light = light_position - point;