From 3dbbb67536354fd50ce97f88cf99eaeea9c62656 Mon Sep 17 00:00:00 2001 From: STP Date: Sun, 19 Nov 2023 21:39:03 -0500 Subject: [PATCH] Adding scripting language --- src/camera.rs | 30 +---- src/gui.rs | 47 ++++---- src/main.rs | 294 +++-------------------------------------------- src/primitive.rs | 6 +- src/ray.rs | 7 +- src/raytracer.rs | 13 +-- src/scene.rs | 42 +++++-- 7 files changed, 90 insertions(+), 349 deletions(-) diff --git a/src/camera.rs b/src/camera.rs index 75d2db1..6df0fe7 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,16 +1,9 @@ use crate::ray::Ray; use crate::{EPSILON, INFINITY}; -use log::error; use nalgebra::{Matrix4, Perspective3, Point3, Unit, Vector3}; -use std::env; -#[rustfmt::skip] -pub const OPENGL_TO_WGPU_MATRIX: Matrix4 = Matrix4::new( - 1.0, 0.0, 0.0, 0.0, - 0.0, 1.0, 0.0, 0.0, - 0.0, 0.0, 0.5, 0.5, - 0.0, 0.0, 0.0, 1.0, -); +const ZNEAR: f32 = EPSILON; +const ZFAR: f32 = INFINITY; #[derive(Clone)] pub struct Camera { @@ -19,8 +12,6 @@ pub struct Camera { up: Vector3, fovy: f32, aspect: f32, - znear: f32, - zfar: f32, matrix: Matrix4, inverse: Matrix4, } @@ -33,20 +24,15 @@ impl Camera { fovy: f32, aspect: f32, ) -> Self { - let znear = EPSILON; - let zfar = INFINITY; - let (matrix, inverse) = - Camera::build_matrix_and_inverse(&eye, &target, &up, aspect, fovy, znear, zfar); + let (matrix, inverse) = Camera::build_matrix_and_inverse(&eye, &target, &up, fovy, aspect); Camera { eye, target, up, fovy, - aspect, - znear, - zfar, matrix, inverse, + aspect, } } @@ -54,13 +40,11 @@ impl Camera { eye: &Point3, target: &Point3, up: &Vector3, - aspect: f32, fovy: f32, - znear: f32, - zfar: f32, + aspect: f32, ) -> (Matrix4, Matrix4) { let view = Matrix4::look_at_lh(eye, target, up); - let proj = Perspective3::new(aspect, fovy, znear, zfar); + let proj = Perspective3::new(aspect, fovy, ZNEAR, ZFAR); let matrix = proj.as_matrix() * view; let inverse = view.try_inverse().expect("No view") * proj.inverse(); (matrix, inverse) @@ -103,8 +87,6 @@ impl Camera { let fovy_radians = (self.fovy as f64).to_radians(); let fovh_radians = 2.0 * ((fovy_radians / 2.0).tan() * aspect).atan(); let view_direction = (self.target - self.eye).normalize(); // Normalize the view direction vector - let dx = 2.0 / width as f32; - let dy = 2.0 / height as f32; let hor = view_direction.cross(&self.up).normalize(); // pointing right let vert = view_direction.cross(&hor).normalize(); // pointing up let h_width = 2.0 * (fovh_radians / 2.0).tan(); diff --git a/src/gui.rs b/src/gui.rs index cb452cc..4443fdf 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -15,26 +15,30 @@ const CAMERA_MAX: f32 = 10.0; const CAMERA_INIT: f32 = 5.0; /// Manages all state required for rendering Dear ImGui over `Pixels`. -pub(crate) struct Gui { +pub enum GuiEvent { + BufferResize, + CameraRelocate, + SceneLoad(String), +} + +pub struct Gui { imgui: imgui::Context, platform: imgui_winit_support::WinitPlatform, renderer: imgui_wgpu::Renderer, last_frame: Instant, last_cursor: Option, - about_open: bool, + pub event: Option, + + pub filename: String, pub ray_num: i32, - pub buffer_proportion: f32, - pub buffer_resize: bool, - pub camera_eye: Point3, - pub camera_reposition: bool, } impl Gui { /// Create Dear ImGui. - pub(crate) fn new(window: &winit::window::Window, pixels: &pixels::Pixels) -> Self { + pub fn new(window: &winit::window::Window, pixels: &pixels::Pixels) -> Self { // Create Dear ImGui context let mut imgui = imgui::Context::create(); imgui.set_ini_filename(None); @@ -78,17 +82,16 @@ impl Gui { renderer, last_frame: Instant::now(), last_cursor: None, - about_open: true, + event: None, + filename: String::new(), ray_num: RAYS_INIT, buffer_proportion: BUFFER_PROPORTION_INIT, - buffer_resize: false, camera_eye: Point3::new(CAMERA_INIT, CAMERA_INIT, CAMERA_INIT), - camera_reposition: false, } } - /// Prepare Dear ImGui. - pub(crate) fn prepare( + /// Prepare Dear ImGuBi. + pub fn prepare( &mut self, window: &winit::window::Window, ) -> Result<(), winit::error::ExternalError> { @@ -100,7 +103,7 @@ impl Gui { } /// Render Dear ImGui. - pub(crate) fn render( + pub fn render( &mut self, window: &winit::window::Window, encoder: &mut wgpu::CommandEncoder, @@ -131,25 +134,27 @@ impl Gui { BUFFER_PROPORTION_MAX, &mut self.buffer_proportion, ); - let mut buffer_resize = false; if ui.button("Change Buffer") { - buffer_resize = true + self.event = Some(GuiEvent::BufferResize); }; - self.buffer_resize = buffer_resize; - let mut camera_reposition = false; ui.text("Vector3 Input:"); // Create three input fields for x, y, and z components ui.slider("X", CAMERA_MIN, CAMERA_MAX, &mut self.camera_eye.coords[0]); ui.slider("Y", CAMERA_MIN, CAMERA_MAX, &mut self.camera_eye.coords[1]); ui.slider("Z", CAMERA_MIN, CAMERA_MAX, &mut self.camera_eye.coords[2]); // Check if any component of the Vector3 has changed - if ui.button("Apply") { + if ui.button("Apply Camera") { println!("Camera changed: {:?}", self.camera_eye); self.camera_eye = Point3::from(self.camera_eye); - camera_reposition = true; + self.event = Some(GuiEvent::CameraRelocate); + } + + //Load file from + ui.input_text("Scene file", &mut self.filename).build(); + if ui.button("Apply File") { + self.event = Some(GuiEvent::SceneLoad(self.filename.clone())); } - self.camera_reposition = camera_reposition; // Render Dear ImGui with WGPU let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { @@ -174,7 +179,7 @@ impl Gui { } /// Handle any outstanding events. - pub(crate) fn handle_event( + pub fn handle_event( &mut self, window: &winit::window::Window, event: &winit::event::Event<()>, diff --git a/src/main.rs b/src/main.rs index 1b772ec..b2889e0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,24 +1,13 @@ -#![allow(dead_code)] -#![allow(unused_imports)] -#![allow(unused_variables)] -//Use linear algebra module +use crate::state::run; +use error_iter::ErrorIter; + +const EPSILON: f32 = 1e-6; +const INFINITY: f32 = f32::MAX; +const EPSILON_VECTOR: Vector3 = Vector3::new(EPSILON, EPSILON, EPSILON); -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 winit::dpi::{LogicalSize, PhysicalSize}; -use winit::event::{Event, KeyboardInput, MouseButton, VirtualKeyCode, WindowEvent}; -use winit::event_loop::{ControlFlow, EventLoop}; -use winit::window::{Window, WindowBuilder}; -use winit_input_helper::WinitInputHelper; +use std::env; +use std::error::Error; mod camera; mod gui; @@ -27,274 +16,17 @@ mod primitive; mod ray; mod raytracer; mod scene; +mod state; -const START_WIDTH: i32 = 800; -const START_HEIGHT: i32 = 800; -const BOX_SIZE: i16 = 64; -const COLOUR_CLEAR: [u8; 4] = [0x22, 0x22, 0x11, 0xff]; +use nalgebra::Vector3; -const EPSILON: f32 = 1e-6; -const INFINITY: f32 = f32::MAX; -const EPSILON_VECTOR: Vector3 = Vector3::new(EPSILON, EPSILON, EPSILON); -const INFINITY_VECTOR: Vector3 = Vector3::new(INFINITY, INFINITY, INFINITY); - -struct State { - scene: Scene, - window: Window, - pixels: Arc>, - gui: Gui, - - index: usize, - - rays: Arc>, -} - -impl State { - /// Create a new `World` instance that can draw a moving box. - fn new(window: Window, scene: Scene) -> 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 = scene - .camera - .cast_rays(window_size.width, window_size.height); - Self { - scene, - window, - pixels: Arc::new(Mutex::new(pixels)), - gui, - - index: 0, - rays: Arc::new(rays), - } - } - - /// Update the `World` internal state; bounce the box around the screen. - fn update(&mut self) -> bool { - if self.gui.buffer_resize || self.gui.camera_reposition { - 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.scene.camera.set_position(self.gui.camera_eye); - self.rays = Arc::new(self.scene.camera.cast_rays(width_new, height_new)); - } - true - } - - /// Resize the world - fn resize(&mut self, size: &PhysicalSize) -> bool { - 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; - } - true - } - - fn keyboard_input(&mut self, key: &KeyboardInput) { - println!("KEYBOARD INPUT"); - match key.virtual_keycode { - Some(key) => match key { - VirtualKeyCode::A => {} - _ => {} - }, - None => {} - } - } - 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) { - // We want to multithread this function - //let mut threads = vec![]; - // threads.push(thread::spawn({ - // let pixels = self.pixels.clone(); - // move || { - // // 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 => COLOUR_CLEAR, - // // }; - // let mut pixels = pixels.lock().unwrap(); - // let frame = pixels.frame_mut().chunks_exact_mut(4).nth(i).unwrap(); - // frame.copy_from_slice(&[200, 100, 100, 255]); - // } - // })); - - 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 => COLOUR_CLEAR, - }; - let mut pixels = self.pixels.lock().unwrap(); - let frame = pixels.frame_mut(); - frame[i * 4..(i + 1) * 4].copy_from_slice(&rgba); - self.index = (self.index + 1) % (frame.len() / 4); - } - } - - 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> { +fn main() { env_logger::init(); env::set_var("RUST_BACKTRACE", "1"); - Scene::init("test.rhai").expect("Could not read lua file"); - - //Window - let event_loop = EventLoop::new(); - //SCENE - // //Camera - // let eye = Point3::new(10.0, 0.0, 10.0); - // let target = Point3::new(0.0, 0.0, 0.0); - // let up = Vector3::new(0.0, 1.0, 0.0); - // let camera = Camera::new( - // eye, - // target, - // up, - // 90.0, - // (START_WIDTH as f32 / START_HEIGHT as f32) as f32, - // ); - // // SETUP MATERIALS - // let magenta = Arc::new(Material::magenta()); - // let blue = Arc::new(Material::blue()); - // let turquoise = Arc::new(Material::turquoise()); - // let red = Arc::new(Material::red()); - // // SETUP PRIMITIVES - // let mut primitives: Vec> = Vec::new(); - // //let cube = Box::new(Cube::unit(turquoise.clone())); - // // primitives.push(cube); - // let sphere = Box::new(Sphere::new(Point3::new(0.0, -2.0, 0.0), 1.0, red.clone())); - // let sphere2 = Box::new(Sphere::new(Point3::new(0.0, 2.0, 0.0), 1.0, red.clone())); - // let cone = Box::new(Cone::new(1.0, 1.0, -1.0, magenta.clone())); - // primitives.push(sphere); - // primitives.push(sphere2); - // primitives.push(cone); - // - // // SETUP LIGHTS - // let light_pos = Point3::new(0.0, 12.0, 4.0); - // let light_colour = Vector3::new(0.4, 0.4, 0.6); - // let light_falloff = [1.0, 0.00, 0.00]; - // let light = Light::new(light_colour, light_pos, light_falloff); - // - // let light_pos2 = Point3::new(10.0, 12.0, -4.0); - // let light_colour2 = Vector3::new(0.4, 0.0, 0.6); - // let light_falloff2 = [1.0, 0.00, 0.00]; - // let light2 = Light::new(light_colour2, light_pos2, light_falloff2); - // - // let ambient_light = Vector3::new(0.0, 0.0, 0.2); - // - // let scene = Scene::new( - // primitives, - // vec![light, light2], - // vec![camera.clone()], - // ambient_light, - // ); - //State - 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 scene = Scene::empty(); - let mut state = State::new(window, scene); - - event_loop.run(move |event, _, control_flow| { - // Draw the current frame - 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); - } - WindowEvent::KeyboardInput { input, .. } => { - state.keyboard_input(&input); - } - WindowEvent::MouseInput { button, .. } => { - state.mouse_input(&button); - } - _ => {} - }, - Event::RedrawRequested(_) => { - state.render(); - } - _ => {} - } - state.window.request_redraw(); //Redraw window - }); + run(); } -fn log_error(method_name: &str, err: E) { +fn log_error(method_name: &str, err: E) { error!("{method_name}() failed: {err}"); for source in err.sources().skip(1) { error!(" Caused by: {source}"); diff --git a/src/primitive.rs b/src/primitive.rs index 0b0b062..4f7d50b 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -1,10 +1,10 @@ +#[warn(dead_code)] use crate::ray::Ray; use crate::{EPSILON, EPSILON_VECTOR, INFINITY}; -use nalgebra::{distance, Matrix4, Point3, Unit, Vector3}; +use nalgebra::{distance, Point3, Unit, Vector3}; use roots::{find_roots_quadratic, Roots}; use std::fs::File; use std::io::{BufRead, BufReader}; -use std::path::Path; use std::sync::Arc; // MATERIAL ----------------------------------------------------------------- @@ -510,7 +510,7 @@ pub struct Cube { } impl Cube { - fn new(width: f32, height: f32, depth: f32, material: Arc) -> Arc { + pub fn new(width: f32, height: f32, depth: f32, material: Arc) -> Arc { let trf = Point3::new(width / 2.0, height / 2.0, depth / 2.0); let bln = Point3::new(-width / 2.0, -height / 2.0, -depth / 2.0); Arc::new(Cube { diff --git a/src/ray.rs b/src/ray.rs index 7ec8fcd..3f4dea9 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -1,6 +1,9 @@ -use crate::{EPSILON, EPSILON_VECTOR}; +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] -use nalgebra::{Matrix4, Point3, Unit, Vector3}; +use crate::EPSILON; +use nalgebra::{Point3, Unit, Vector3}; pub struct Ray { pub a: Point3, diff --git a/src/raytracer.rs b/src/raytracer.rs index 64387eb..a84cd44 100644 --- a/src/raytracer.rs +++ b/src/raytracer.rs @@ -1,17 +1,8 @@ -use crate::{ - light::Light, - primitive::{Intersection, Primitive}, - ray::Ray, - scene::*, - INFINITY, -}; -use std::collections::HashMap; -use std::sync::Arc; +use crate::{light::Light, primitive::Intersection, ray::Ray, scene::*, INFINITY}; -use nalgebra::{distance, Matrix4, Point3, Unit, Vector3, Vector4}; +use nalgebra::{Unit, Vector3}; static ZERO_VECTOR: Vector3 = Vector3::new(0.0, 0.0, 0.0); -static ONE_VECTOR: Vector3 = Vector3::new(1.0, 1.0, 1.0); pub fn shade_rays(scene: &Scene, rays: &Vec, width: i32, height: i32) -> Vec> { let mut pixel_data = vec![Vector3::new(0, 0, 0); (width * height) as usize]; diff --git a/src/scene.rs b/src/scene.rs index 8180ba4..c595061 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,10 +1,14 @@ -use crate::camera::Camera; +#![allow(dead_code)] +#![allow(unused_imports)] +#![allow(unused_variables)] + use crate::light::Light; use crate::primitive::Primitive; use crate::primitive::*; +use crate::state::State; +use crate::{camera::Camera, state}; use nalgebra::{Matrix4, Point3, Vector3}; use rhai::{Engine, EvalAltResult}; -use std::collections::HashMap; use std::sync::Arc; const LIGHT_AMBIENT: f32 = 0.2; @@ -91,7 +95,8 @@ impl Scene { fn get_ambient(&self) -> Arc> { Arc::new(self.ambient_light) } - pub fn init(filename: &str) -> Result<(), Box> { + + pub fn from_script(filename: &str) -> Result> { let mut engine = Engine::new(); engine @@ -104,6 +109,7 @@ impl Scene { .register_type::() .register_fn("Scene", Scene::empty) .register_fn("addNode", Scene::add_node); + engine .register_type::() .register_fn("Node", Node::new) @@ -118,12 +124,34 @@ impl Scene { .register_fn("Light", Light::new); engine .register_type::() - .register_fn("Material", Material::new); + .register_fn("Material", Material::new) + .register_fn("MaterialRed", Material::red) + .register_fn("MaterialBlue", Material::blue) + .register_fn("MaterialGreen", Material::green) + .register_fn("MaterialMagenta", Material::magenta) + .register_fn("MaterialTurquoise", Material::turquoise); engine .register_type::() - .register_fn("Sphere", Sphere::new); + .register_fn("Sphere", Sphere::new) + .register_fn("SphereUnit", Sphere::unit); + engine + .register_type::() + .register_fn("Cube", Cube::new) + .register_fn("CubeUnit", Cube::unit); + engine + .register_type::() + .register_fn("Cone", Cone::new) + .register_fn("ConeUnit", Cone::unit); + engine + .register_type::() + .register_fn("Sphere", Sphere::new) + .register_fn("SphereUnit", Sphere::unit); + engine + .register_type::() + .register_fn("Sphere", Sphere::new) + .register_fn("SphereUnit", Sphere::unit); - engine.run_file(filename.into())?; - Ok(()) + let scene: Scene = engine.eval_file(filename.into())?; + Ok(scene) } }