use crate::{ camera::Camera, light::Light, material::*, node::*, primitive::*, scene::*, state::{RaytracingOption, INIT_FILE, SAVE_FILE}, }; use imgui::*; use nalgebra::{Point3, Vector3}; use pixels::{wgpu, PixelsContext}; use rhai::Engine; use std::time::Instant; //BUFFER CONSTANTS const BUFFER_PROPORTION_MIN: f32 = 0.1; const BUFFER_PROPORTION_MAX: f32 = 1.0; //RAY CONSTANTS const RAYS_MIN: u32 = 100; const RAYS_MAX: u32 = 10000; const MIN_DEPTH: u8 = 5; const MAX_DEPTH: u8 = 100; const MIN_SAMPLES: u32 = 5; const MAX_SAMPLES: u32 = 100; const MIN_RANDOM: f64 = 100.0; const MAX_RANDOM: f64 = 1000.0; //DIFFUSE CONSTANTS const MIN_DIFFUSE_RAYS: u8 = 5; const MAX_DIFFUSE_RAYS: u8 = 100; const MIN_DIFFUSE_COEFFICIENT: f32 = 0.0; const MAX_DIFFUSE_COEFFICIENT: f32 = 1.0; //MATERIAL CONSTANTS const MIN_D: f32 = 0.0; const MIN_S: 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; //TRANSFORMATION CONSTANTS const MIN_COLOUR: f32 = 0.0; const MIN_FALLOFF: f32 = 0.0; const MIN_SCALE: f64 = 0.0; //const MIN_POSITION: f64 = -10.0; const MIN_ROTATION: f64 = -180.0; const MIN_TRANSLATE: f64 = -10.0; //-- const MAX_COLOUR: f32 = 1.0; const MAX_FALLOFF: f32 = 1.0; const MAX_SCALE: f64 = 3.0; //const MAX_POSITION: f64 = 10.0; const MAX_ROTATION: f64 = 180.0; const MAX_TRANSLATE: f64 = 10.0; // CAMERA CONSTANTS const MIN_FOV: f64 = 10.0; const MAX_FOV: f64 = 160.0; //const CAMERA_INIT: f32 = 5.0; /// Manages all state required for rendering Dear ImGui over `Pixels`test. pub enum GuiEvent { RaytracerOption(RaytracingOption), CameraUpdate(Camera), SceneLoad(Scene), SaveImage(String), } pub struct Gui { imgui: imgui::Context, platform: imgui_winit_support::WinitPlatform, renderer: imgui_wgpu::Renderer, last_frame: Instant, last_cursor: Option, pub event: Option, script_filename: String, script: String, engine: Engine, scene: Scene, raytracing_option: RaytracingOption, camera: Camera, image_filename: String, } impl Gui { /// Create Dear ImGui. 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); // Initialize winit platform support let mut platform = imgui_winit_support::WinitPlatform::init(&mut imgui); platform.attach_window( imgui.io_mut(), window, imgui_winit_support::HiDpiMode::Default, ); // Configure Dear ImGui fonts let hidpi_factor = window.scale_factor(); let font_size = (16.0 * hidpi_factor) as f32; imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; imgui .fonts() .add_font(&[imgui::FontSource::DefaultFontData { config: Some(imgui::FontConfig { oversample_h: 1, pixel_snap_h: true, size_pixels: font_size, ..Default::default() }), }]); // Create Dear ImGui WGPU renderer let device = pixels.device(); let queue = pixels.queue(); let config = imgui_wgpu::RendererConfig { texture_format: pixels.render_texture_format(), ..Default::default() }; let renderer = imgui_wgpu::Renderer::new(&mut imgui, device, queue, config); // Return GUI context let mut gui = Self { imgui, platform, renderer, last_frame: Instant::now(), last_cursor: None, event: None, script_filename: String::from(INIT_FILE), script: String::new(), engine: init_engine(), scene: Scene::empty(), raytracing_option: RaytracingOption::default(), camera: Camera::unit(), image_filename: String::from(SAVE_FILE), }; // ------------ TESTING CODE (LOAD SCENE ON START) ----------------- match std::fs::read_to_string(&mut gui.script_filename) { Ok(script) => { gui.script = script; } Err(e) => println!("{}", e), } match gui.engine.eval(&gui.script) { Ok(scene) => { gui.scene = scene; gui.event = Some(GuiEvent::SceneLoad(gui.scene.clone())); } Err(e) => println!("{e}"), } // ------------ TESTING CODE (LOAD SCENE ON START) ----------------- gui } /// Prepare Dear ImGui. pub fn prepare( &mut self, window: &winit::window::Window, ) -> Result<(), winit::error::ExternalError> { let now = Instant::now(); self.imgui.io_mut().update_delta_time(now - self.last_frame); self.last_frame = now; self.platform.prepare_frame(self.imgui.io_mut(), window) } /// Render Dear ImGui. pub fn render( &mut self, window: &winit::window::Window, encoder: &mut wgpu::CommandEncoder, render_target: &wgpu::TextureView, context: &PixelsContext, ) -> imgui_wgpu::RendererResult<()> { // Start a new Dear ImGui frame and update the cursor let ui = self.imgui.new_frame(); let mouse_cursor = ui.mouse_cursor(); if self.last_cursor != mouse_cursor { self.last_cursor = mouse_cursor; self.platform.prepare_render(ui, window); } //Top Menu Bar // let mut about_open = false; // ui.main_menu_bar(|| { // ui.menu("Help", || { // about_open = ui.menu_item("About..."); // }); // }); //Raytracing options ------------------------------------------- if CollapsingHeader::new("Raytracer").build(ui) { // Numbers of rays to render per pass ui.slider( "Rays Per Pass", RAYS_MIN, RAYS_MAX, &mut self.raytracing_option.rays_per_pass, ); // Proportion of the window the buffer occupies ui.slider( "% Buffer: ", BUFFER_PROPORTION_MIN, BUFFER_PROPORTION_MAX, &mut self.raytracing_option.buffer_proportion, ); //Clear colour for scene ui.slider_config("Clear Colour", 0, 255) .build_array(&mut self.raytracing_option.clear_color); //Clear colour if no intersect ui.slider_config("Pixel Clear Colour", 0, 255) .build_array(&mut self.raytracing_option.pixel_clear); //Ray depth slider ui.slider( "Ray Depth", MIN_DEPTH, MAX_DEPTH, &mut self.raytracing_option.ray_depth, ); //Ray samples slider ui.slider( "Ray Samples", MIN_SAMPLES, MAX_SAMPLES, &mut self.raytracing_option.ray_samples, ); //Ray randomness ui.slider( "Ray Randomness", MIN_RANDOM, MAX_RANDOM, &mut self.raytracing_option.ray_randomness, ); //Number of diffuse rays ui.slider( "Diffuse Rays", MIN_DIFFUSE_RAYS, MAX_DIFFUSE_RAYS, &mut self.raytracing_option.diffuse_rays, ); //Diffuse Coefficient ui.slider( "Diffuse Coefficient", MIN_DIFFUSE_COEFFICIENT, MAX_DIFFUSE_COEFFICIENT, &mut self.raytracing_option.diffuse_coefficient, ); // Fov of the buffer ui.slider( "fov", MIN_FOV, MAX_FOV, &mut self.raytracing_option.buffer_fov, ); // Enable BVH ui.checkbox("Enable BVH", &mut self.raytracing_option.bvh_active); // Apply stored changes if ui.button("Apply") { self.event = Some(GuiEvent::RaytracerOption(self.raytracing_option.clone())); }; } // CAMERA OPTIONS ---------------------------------------- if CollapsingHeader::new("Camera").build(ui) { // Eye, target and up vector inputs ui.text("Camera options:"); ui.slider_config("Eye", MIN_TRANSLATE, MAX_TRANSLATE) .build_array(self.camera.eye.coords.as_mut_slice()); ui.slider_config("Target", MIN_TRANSLATE, MAX_TRANSLATE) .build_array(self.camera.target.coords.as_mut_slice()); ui.slider_config("Up", 0.0, 1.0) .build_array(self.camera.up.as_mut_slice()); if ui.button("Apply Camera") { println!("Camera changed"); self.event = Some(GuiEvent::CameraUpdate(self.camera.clone())); } } // SCRIPTING -------------------------------------------- if CollapsingHeader::new("Scripting").build(ui) { // 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(&mut self.script_filename) { Ok(script) => { self.script = script; } Err(e) => println!("{}", e), } } 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; self.event = Some(GuiEvent::SceneLoad(self.scene.clone())); } 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), } } // 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 (label, node) in &mut self.scene.nodes { ui.checkbox(format!("##active{label}"), &mut node.active); ui.same_line(); if let Some(_t) = ui.tree_node(label) { ui.slider_config("Translation", MIN_TRANSLATE, MAX_TRANSLATE) .build_array(&mut node.translation); ui.slider_config("Rotation", MIN_ROTATION, MAX_ROTATION) .build_array(&mut node.rotation); ui.slider_config("Scale", MIN_SCALE, MAX_SCALE) .build_array(&mut node.scale); } } } // Edit materials if let Some(_t) = ui.tree_node("Materials") { for (label, material) in &mut self.scene.materials { if let Some(_t) = ui.tree_node(label) { ui.slider_config("ks", MIN_D, MAX_D) .build_array(material.ks.as_mut_slice()); ui.slider_config("kd", MIN_S, MAX_S) .build_array(material.kd.as_mut_slice()); ui.slider("shine", MIN_SHINE, MAX_SHINE, &mut material.shininess); } } } //Edit color, position and falloff of lights if let Some(_t) = ui.tree_node("Lights") { for (label, light) in &mut self.scene.lights { ui.checkbox(format!("##activelight{label}"), &mut light.active); ui.same_line(); if let Some(_t) = ui.tree_node(label) { ui.slider_config("Colour", MIN_COLOUR, MAX_COLOUR) .build_array(light.colour.as_mut_slice()); ui.slider_config("Position", MIN_TRANSLATE, MAX_TRANSLATE) .build_array(light.position.coords.as_mut_slice()); ui.slider_config("Falloff", MIN_FALLOFF, MAX_FALLOFF) .build_array(light.falloff.as_mut_slice()); } } } //Use different cameras in the scene if let Some(_t) = ui.tree_node("Cameras") { for (label, camera) in &self.scene.cameras { if ui.button(label) { self.camera = camera.clone(); self.event = Some(GuiEvent::CameraUpdate(camera.clone())); } } } } // Render Dear ImGui with WGPU let mut rpass = encoder.begin_render_pass(&wgpu::RenderPassDescriptor { label: Some("imgui"), color_attachments: &[Some(wgpu::RenderPassColorAttachment { view: render_target, resolve_target: None, ops: wgpu::Operations { load: wgpu::LoadOp::Load, store: true, }, })], depth_stencil_attachment: None, }); self.renderer.render( self.imgui.render(), &context.queue, &context.device, &mut rpass, ) } /// Handle any outstanding events. pub fn handle_event( &mut self, window: &winit::window::Window, event: &winit::event::Event<()>, ) { self.platform .handle_event(self.imgui.io_mut(), window, event); } } pub fn init_engine() -> Engine { let mut engine = Engine::new(); engine .register_type::>() .register_fn("V", Vector3::::new); engine .register_type::>() .register_fn("P", Point3::::new); engine .register_type::() .register_fn("Camera", Camera::new); engine .register_type::() .register_fn("Scene", Scene::empty) .register_fn("addNode", Scene::add_node) .register_fn("addLight", Scene::add_light) .register_fn("addCamera", Scene::add_camera) .register_fn("addMaterial", Scene::add_material); engine .register_type::() .register_fn("Node", Node::new) .register_fn("translate", Node::translate) .register_fn("rotate", Node::rotate) .register_fn("scale", Node::scale) .register_fn("child", Node::child) .register_fn("active", Node::set_active); engine .register_type::() .register_fn("Light", Light::new) .register_fn("Ambient", Light::ambient) .register_fn("active", Light::set_active); engine .register_type::() .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("SphereUnit", Sphere::unit); engine .register_type::() .register_fn("Cube", Cube::new) .register_fn("CubeUnit", Cube::unit); engine .register_type::() .register_fn("Triangle", Triangle::new) .register_fn("TriangleUnit", Triangle::unit); engine .register_type::() .register_fn("Cone", Cone::new) .register_fn("ConeUnit", Cone::unit); engine .register_type::() .register_fn("Cylinder", Cylinder::new); engine .register_type::() .register_fn("Circle", Circle::new) .register_fn("CircleUnit", Circle::unit); engine .register_type::() .register_fn("Cube", Cube::new) .register_fn("CubeUnit", Cube::unit); engine .register_type::() .register_fn("Steiner", Steiner::new); engine .register_type::() .register_fn("Steiner2", Steiner2::new); engine .register_type::() .register_fn("Roman", Roman::new); engine .register_type::() .register_fn("CrossCap", CrossCap::new); engine .register_type::() .register_fn("CrossCap2", CrossCap2::new); engine .register_type::() .register_fn("Torus", Torus::new); engine .register_type::() .register_fn("Gnonom", Gnonom::new); engine .register_type::() .register_fn("Mesh", Mesh::from_file); // engine // .register_type::() // .register_fn("Rectange", Rectangle::new) // .register_fn("RectangleUnit", Rectangle::unit); engine }