Files
rust-raytracer/src/gui.rs

458 lines
16 KiB
Rust

use crate::{
camera::Camera,
light::Light,
material::*,
node::*,
primitive::*,
scene::*,
state::{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_INIT: f32 = 0.2;
const BUFFER_PROPORTION_MIN: f32 = 0.1;
const BUFFER_PROPORTION_MAX: f32 = 1.0;
//RAY CONSTANTS
const RAYS_INIT: i32 = 100;
const RAYS_MIN: i32 = 100;
const RAYS_MAX: i32 = 10000;
//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: f32 = 10.0;
const MAX_FOV: f32 = 160.0;
//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, f32),
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<imgui::MouseCursor>,
pub event: Option<GuiEvent>,
script_filename: String,
script: String,
engine: Engine,
scene: Scene,
pub ray_num: i32,
buffer_proportion: f32,
camera: Camera,
camera_fov: f32,
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
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(),
ray_num: RAYS_INIT,
buffer_proportion: BUFFER_PROPORTION_INIT,
camera: Camera::unit(),
camera_fov: 110.0,
image_filename: String::from(SAVE_FILE),
}
}
/// 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
ui.slider("# Rays: ", RAYS_MIN, RAYS_MAX, &mut self.ray_num);
// 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", MIN_FOV, MAX_FOV, &mut self.camera_fov);
// Apply stored changes
if ui.button("Apply") {
self.event = Some(GuiEvent::BufferResize(
self.buffer_proportion,
self.camera_fov,
));
};
}
// 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(), self.camera_fov));
}
}
// 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(), self.camera_fov));
}
}
}
}
// 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::<Vector3<f64>>()
.register_fn("V", Vector3::<f64>::new);
engine
.register_type::<Point3<f64>>()
.register_fn("P", Point3::<f64>::new);
engine
.register_type::<Camera>()
.register_fn("Camera", Camera::new);
engine
.register_type::<Scene>()
.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::<Node>()
.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::<Light>()
.register_fn("Light", Light::new)
.register_fn("Ambient", Light::ambient)
.register_fn("active", Light::set_active);
engine
.register_type::<Material>()
.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::<Sphere>()
.register_fn("Sphere", Sphere::new)
.register_fn("SphereUnit", Sphere::unit);
engine
.register_type::<Cube>()
.register_fn("Cube", Cube::new)
.register_fn("CubeUnit", Cube::unit);
engine
.register_type::<Triangle>()
.register_fn("Triangle", Triangle::new)
.register_fn("TriangleUnit", Triangle::unit);
engine
.register_type::<Cone>()
.register_fn("Cone", Cone::new)
.register_fn("ConeUnit", Cone::unit);
engine
.register_type::<Cylinder>()
.register_fn("Cylinder", Cylinder::new);
engine
.register_type::<Circle>()
.register_fn("Circle", Circle::new)
.register_fn("CircleUnit", Circle::unit);
engine
.register_type::<Cube>()
.register_fn("Cube", Cube::new)
.register_fn("CubeUnit", Cube::unit);
engine
.register_type::<Steiner>()
.register_fn("Steiner", Steiner::new);
engine
.register_type::<Steiner2>()
.register_fn("Steiner2", Steiner2::new);
engine
.register_type::<Roman>()
.register_fn("Roman", Roman::new);
engine
.register_type::<CrossCap>()
.register_fn("CrossCap", CrossCap::new);
engine
.register_type::<CrossCap2>()
.register_fn("CrossCap2", CrossCap2::new);
engine
.register_type::<Torus>()
.register_fn("Torus", Torus::new);
engine
.register_type::<Gnonom>()
.register_fn("Gnonom", Gnonom::new);
engine
.register_type::<Mesh>()
.register_fn("Mesh", Mesh::from_file);
// engine
// .register_type::<Rectangle>()
// .register_fn("Rectange", Rectangle::new)
// .register_fn("RectangleUnit", Rectangle::unit);
engine
}