From 2e70fc9a686b4092406262ce2a23dbac95e0039e Mon Sep 17 00:00:00 2001 From: STP Date: Sun, 26 Nov 2023 04:19:25 -0500 Subject: [PATCH] Shadows, New rays and niceness --- scene.rhai | 36 ++++++----- src/bvh.rs | 1 - src/camera.rs | 11 +--- src/gui.rs | 106 ++++++++++++++++++++++++++++---- src/happy-tree.png | Bin 28134 -> 0 bytes src/main.rs | 2 +- src/primitive.rs | 44 ++++++++----- src/ray.rs | 67 ++++++++++++-------- src/raytracer.rs | 25 +++++++- src/scene.rs | 149 ++++++++++++++++----------------------------- src/state.rs | 41 ++++++++----- 11 files changed, 284 insertions(+), 198 deletions(-) delete mode 100644 src/bvh.rs delete mode 100644 src/happy-tree.png diff --git a/scene.rhai b/scene.rhai index b44aff6..e4ed72c 100644 --- a/scene.rhai +++ b/scene.rhai @@ -1,35 +1,33 @@ let scene = Scene(); -let eye = P(0.0, 0.0, 3.0); -let target = P(0.0, 0.0, 0.0); -let up = V(0.0, 1.0, 3.0); - -let cam = Camera(eye, target, up, 70.0); - let material = Material(V(0.5,0.5,0.5), V(0.8, 0.8, 0.8), 25.0); -let ambient = Light(P(10.0,0.0,0.0), V(1.0,1.0,1.0), V(0.0, 0.0, 0.0)); +//let ambient = Light(P(10.0,0.0,0.0), V(1.0,1.0,1.0), V(0.0, 0.0, 0.0)); +//scene.addLight(ambient); -let light2 = Light(P(0.0,0.0,10.0), V(0.0,1.0,1.0), V(0.1, 0.01, 0.001)); - -scene.addLight(light2); -scene.addLight(ambient); +let light = Light(P(3.0,3.0,1.0), V(0.0,1.0,1.0), V(0.1, 0.01, 0.001)); +scene.addLight(light); -// let sphere = Sphere(P(0.0,0.0,0.0), 1.0, material); -// let sphere_node = Node(sphere); +let sphere = Sphere(P(0.0,0.0,0.0), 1.0, material); +let sphere_node = Node(sphere); +// let child = sphere_node.child(sphere); +// child.translate(V(1.0,1.0,1.0)); // scene.addNode(sphere_node); +// scene.addNode(child); // let cube = CubeUnit(material); // let cube_node = Node(cube); // scene.addNode(cube_node); -let gnonom = Gnonom(material); -let gnonom_node = Node(gnonom); -scene.addNode(gnonom_node); +// let gnonom = Gnonom(material); +// let gnonom_node = Node(gnonom); +// scene.addNode(gnonom_node); -// let cylinder = Cylinder(1.0,1.0, material); -// let cylinder_node = Node(cylinder); -// scene.addNode(cylinder_node); +let cylinder = Cylinder(1.0,1.0, material); +let cylinder_node = Node(cylinder); +cylinder_node.scale(V(4.0,0.3,0.3)); +cylinder_node.translate(V(0.0,-4.0,1.0)); +scene.addNode(cylinder_node); scene \ No newline at end of file diff --git a/src/bvh.rs b/src/bvh.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/bvh.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/camera.rs b/src/camera.rs index d75fbae..066321f 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,9 +1,4 @@ -use crate::ray::Ray; -use crate::{EPSILON, INFINITY}; -use nalgebra::{Matrix4, Perspective3, Point3, Unit, Vector3}; - -const ZNEAR: f64 = EPSILON; -const ZFAR: f64 = INFINITY; +use nalgebra::{Matrix4, Point3, Vector3}; #[allow(dead_code)] #[derive(Clone)] @@ -11,8 +6,8 @@ pub struct Camera { eye: Point3, target: Point3, up: Vector3, - view: Matrix4, - inv_view: Matrix4, + pub view: Matrix4, + pub inv_view: Matrix4, } #[allow(dead_code)] diff --git a/src/gui.rs b/src/gui.rs index a888df1..569cf8a 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -1,16 +1,24 @@ -use crate::{camera::Camera, scene::Scene, state::INIT_FILE, UP_VECTOR_F32, ZERO_VECTOR_F32}; +use crate::{ + camera::Camera, + light::Light, + primitive::*, + scene::{Node, Scene}, + state::INIT_FILE, + UP_VECTOR_F32, ZERO_VECTOR_F32, +}; use imgui::*; use nalgebra::{Point3, Vector3}; use pixels::{wgpu, PixelsContext}; +use rhai::Engine; use std::time::Instant; const BUFFER_PROPORTION_INIT: f32 = 1.0; const BUFFER_PROPORTION_MIN: f32 = 0.5; const BUFFER_PROPORTION_MAX: f32 = 1.0; -const RAYS_INIT: i32 = 9000; +const RAYS_INIT: i32 = 1000; const RAYS_MIN: i32 = 100; -const RAYS_MAX: i32 = 10000; +const RAYS_MAX: i32 = 100000; const CAMERA_MIN_FOV: f32 = 10.0; const CAMERA_MAX_FOV: f32 = 160.0; @@ -18,7 +26,7 @@ const CAMERA_INIT: f32 = 5.0; /// Manages all state required for rendering Dear ImGui over `Pixels`test. pub enum GuiEvent { - BufferResize(f32), + BufferResize(f32, f32), CameraUpdate(Camera), SceneLoad(Scene), } @@ -34,6 +42,7 @@ pub struct Gui { script_filename: String, script: String, + engine: Engine, scene: Scene, pub ray_num: i32, @@ -63,7 +72,7 @@ impl Gui { // Configure Dear ImGui fonts let hidpi_factor = window.scale_factor(); - let font_size = (11.0 * hidpi_factor) as f32; + let font_size = (16.0 * hidpi_factor) as f32; imgui.io_mut().font_global_scale = (1.0 / hidpi_factor) as f32; imgui .fonts() @@ -96,6 +105,7 @@ impl Gui { script_filename: String::from(INIT_FILE), script: String::new(), + engine: init_engine(), scene: Scene::empty(), ray_num: RAYS_INIT, @@ -156,9 +166,13 @@ impl Gui { BUFFER_PROPORTION_MAX, &mut self.buffer_proportion, ); + ui.slider("fov", CAMERA_MIN_FOV, CAMERA_MAX_FOV, &mut self.camera_fov); //Apply changes if ui.button("Apply") { - self.event = Some(GuiEvent::BufferResize(self.buffer_proportion)); + self.event = Some(GuiEvent::BufferResize( + self.buffer_proportion, + self.camera_fov, + )); }; } //Camera options @@ -167,7 +181,6 @@ impl Gui { 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(); - ui.slider("fov", CAMERA_MIN_FOV, CAMERA_MAX_FOV, &mut self.camera_fov); // Create three input fields for x, y, and z components if ui.button("Apply Camera") { println!("Camera changed: {:?}", self.camera_eye); @@ -175,14 +188,10 @@ impl Gui { let (ex, ey, ez) = (eye[0] as f64, eye[1] as f64, eye[2] as f64); let (tx, ty, tz) = (target[0] as f64, target[1] as f64, target[2] as f64); let (ux, uy, uz) = (up[0] as f64, up[1] as f64, up[2] as f64); - let camera = Camera::new( Point3::new(ex, ey, ez), Point3::new(tx, ty, tz), Vector3::new(ux, uy, uz), - 1, - 1, - self.camera_fov as f64, ); self.event = Some(GuiEvent::CameraUpdate(camera)); } @@ -199,7 +208,7 @@ impl Gui { } } if ui.button("Apply script") { - match Scene::from_rhai(&self.script) { + match self.engine.eval(&self.script) { Ok(scene) => { self.scene = scene; self.event = Some(GuiEvent::SceneLoad(self.scene.clone())); @@ -208,7 +217,7 @@ impl Gui { } } //Script block - ui.input_text_multiline("script", &mut self.script, [500., 900.]) + ui.input_text_multiline("script", &mut self.script, [600., 1500.]) .build(); } @@ -244,3 +253,74 @@ impl Gui { .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); + + 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); + engine + .register_type::() + .register_fn("Light", Light::new); + 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("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("Rectangle", Rectangle::new) + .register_fn("RectangleUnit", Rectangle::unit); + engine + .register_type::() + .register_fn("Steiner", SteinerSurface::new); + engine + .register_type::() + .register_fn("Torus", Torus::new); + engine + .register_type::() + .register_fn("Gnonom", Gnonom::new); + engine +} diff --git a/src/happy-tree.png b/src/happy-tree.png deleted file mode 100644 index fc86db345c14b95728250b8914fa284f34193834..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 28134 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 D;T5;R diff --git a/src/main.rs b/src/main.rs index 33df581..fcdddf2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,7 +1,7 @@ use crate::state::run; use error_iter::ErrorIter; -const EPSILON: f64 = 1e-9; +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); diff --git a/src/primitive.rs b/src/primitive.rs index 0d27275..da4e0bf 100644 --- a/src/primitive.rs +++ b/src/primitive.rs @@ -1,8 +1,8 @@ #[allow(dead_code)] use crate::ray::Ray; use crate::{EPSILON, EPSILON_VECTOR, INFINITY}; -use nalgebra::{distance, Point3, Unit, Vector3}; -use roots::{find_roots_cubic, find_roots_quadratic, find_roots_quartic, Roots}; +use nalgebra::{distance, Matrix4, Point3, Vector3}; +use roots::{find_roots_quadratic, find_roots_quartic, Roots}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::sync::Arc; @@ -54,11 +54,22 @@ impl Material { pub struct Intersection { // Information about an intersection pub point: Point3, - pub normal: Unit>, - pub incidence: Unit>, + pub normal: Vector3, + pub incidence: Vector3, pub material: Arc, pub distance: f64, } +impl Intersection { + pub fn transform(&self, trans: &Matrix4, inv_trans: &Matrix4) -> Intersection { + Intersection { + point: trans.transform_point(&self.point), + normal: inv_trans.transpose().transform_vector(&self.normal), + incidence: trans.transform_vector(&self.incidence), + material: self.material.clone(), + distance: self.distance, + } + } +} // BOUNDING BOX ----------------------------------------------------------------- #[derive(Clone)] @@ -154,7 +165,7 @@ impl Primitive for Sphere { }; let intersect = ray.at_t(t); - let normal = Unit::new_normalize(intersect - self.position); + let normal = (intersect - self.position).normalize(); Some(Intersection { point: intersect, normal, @@ -238,7 +249,7 @@ impl Primitive for Circle { false => { return Some(Intersection { point: intersect, - normal: Unit::new_normalize(self.normal), + normal: self.normal.normalize(), incidence: ray.b, material: Arc::clone(&self.material), distance: t, @@ -329,9 +340,9 @@ impl Primitive for Cylinder { let normal = Vector3::new(2.0 * intersect.x, 0.0, 2.0 * intersect.z); Some(Intersection { point: intersect, - normal: Unit::new_normalize(normal), - material: Arc::clone(&self.material), + normal: normal, incidence: ray.b, + material: Arc::clone(&self.material), distance: t, }) } else { @@ -462,9 +473,9 @@ impl Primitive for Cone { match intersect.y >= self.base && intersect.y <= self.apex { true => Some(Intersection { point: intersect, - normal: Unit::new_normalize(self.get_normal(intersect)), - material: Arc::clone(&self.material), + normal: self.get_normal(intersect), incidence: ray.b, + material: Arc::clone(&self.material), distance: t, }), false => None, @@ -568,7 +579,7 @@ impl Primitive for Rectangle { if pi_dot_r1 >= -w2 && pi_dot_r1 <= w2 && pi_dot_r2 >= -h2 && pi_dot_r2 <= h2 { return Some(Intersection { point: intersect, - normal: Unit::new_normalize(self.normal), + normal: self.normal, incidence: ray.b, material: Arc::clone(&self.material), distance: t, @@ -657,7 +668,7 @@ impl Primitive for Cube { Some(Intersection { point: intersect, - normal: Unit::new_normalize(normal), + normal: normal, incidence: ray.b, material: Arc::clone(&self.material), distance: tmin, @@ -748,7 +759,7 @@ impl Primitive for Triangle { { Some(Intersection { point: intersect, - normal: Unit::new_normalize(normal), + normal: normal, incidence: ray.b, material: Arc::clone(&self.material), distance: t, @@ -958,11 +969,12 @@ impl Primitive for SteinerSurface { //Now we have the smallest non-zero t let point = ray.at_t(t); let (x, y, z) = (point.x, point.y, point.z); - let normal = Unit::new_normalize(Vector3::new( + let normal = Vector3::new( 2.0 * x * y * y + 2.0 * x * z * z + y * z, 2.0 * x * x * y + 2.0 * z * z * y + x * z, 2.0 * x * x * z + 2.0 * z * y * y + x * y, - )); + ) + .normalize(); Some(Intersection { point, @@ -1106,7 +1118,7 @@ impl Primitive for Torus { -4.0 * (r2.powf(2.0) - r1.powf(2.0) + x.powf(2.0) + y.powf(2.0) + z.powf(2.0)) * r1; let dz = -8.0 * r2.powf(2.0) * x + 4.0 * (r2.powf(2.0) - r1.powf(2.0) + x.powf(2.0) + y.powf(2.0) + z.powf(2.0)) * x; - let normal = Unit::new_normalize(Vector3::new(dx, dy, dz)); + let normal = Vector3::new(dx, dy, dz).normalize(); Some(Intersection { point, diff --git a/src/ray.rs b/src/ray.rs index 0b50f54..f808f11 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -1,52 +1,61 @@ use crate::{ - primitive::Intersection, + primitive::{self, Intersection}, raytracer::phong_shade_point, scene::{Node, Scene}, EPSILON, INFINITY, }; -use nalgebra::{Point3, Unit, Vector3}; +use nalgebra::{Matrix4, Point3, Vector3}; #[derive(Clone)] pub struct Ray { pub a: Point3, - pub b: Unit>, + pub b: Vector3, } impl Ray { - pub fn new(a: Point3, b: Unit>) -> Ray { - Ray { a, b } + pub fn new(a: Point3, b: Vector3) -> Ray { + Ray { + a, + b: b.normalize(), + } } pub fn unit() -> Ray { let a = Point3::new(0.0, 0.0, 0.0); - let b = Unit::new_normalize(Vector3::new(0.0, 1.0, 0.0)); + let b = Vector3::new(0.0, 1.0, 0.0); Ray { a, b } } pub fn at_t(&self, t: f64) -> Point3 { - self.a + self.b.into_inner() * (t + EPSILON) + self.a + self.b * t } //Shade a single ray pub fn shade_ray(&self, scene: &Scene) -> Option> { let intersect = self.get_closest_intersection(&scene.nodes); + match intersect { Some(intersect) => Some(phong_shade_point(&scene, &intersect)), None => None, } } - // Find the closest intersection, given a ray in world coordinates + // Find the closest intersection pub fn get_closest_intersection(&self, nodes: &Vec) -> Option { let mut closest_distance = INFINITY; let mut closest_intersect: Option = None; for node in nodes { let primitive = node.primitive.clone(); - let trans = node.trans; - let inv_trans = node.inv_trans; - if let Some(intersect) = primitive.intersect_ray(self) { - if intersect.distance < closest_distance { - closest_distance = intersect.distance; - closest_intersect = Some(intersect); + //Transform ray to view coords + let ray = self.transform(&node.inv_viewmodel); + + if primitive.intersect_bounding_box(&ray).is_some() { + if let Some(intersect) = primitive.intersect_ray(&ray) { + 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); + } } } } @@ -54,30 +63,40 @@ impl Ray { closest_intersect } + 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; let fovy_radians = fovy.to_radians(); - //Verify this part later + 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); - let half_height = fovy_radians.tan(); - let half_width = aspect * half_height; + let vheight = 2.0 * (fovy_radians / 2.0).tan(); + let vwidth = 2.0 * (fovh_radians / 2.0).tan(); - let d_hor_vec = hor * (2.0 * half_width / width as f64) as f64; - let d_vert_vec = up * (2.0 * half_height / height as f64) as f64; + let d_hor_vec = hor * (vwidth / width as f64) as f64; + let d_vert_vec = up * (vheight / height as f64) as f64; - //All good + let half_width = width / 2; + let half_height = height / 2; let mut rays = Vec::with_capacity(width as usize * height as usize); for j in 0..height as i32 { for i in 0..width as i32 { - let horizontal = (i - half_width as i32) as f64 * (d_hor_vec); - let vertical = (-j + half_height as i32) as f64 * (d_vert_vec); - + 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), Unit::new_normalize(direction)); + let ray = Ray::new(Point3::new(0.0, 0.0, 0.0), direction); rays.push(ray); } } diff --git a/src/raytracer.rs b/src/raytracer.rs index b88004f..91accb2 100644 --- a/src/raytracer.rs +++ b/src/raytracer.rs @@ -1,4 +1,4 @@ -use crate::{light::Light, primitive::Intersection, scene::*, ZERO_VECTOR}; +use crate::{light::Light, primitive::Intersection, ray::Ray, scene::*, EPSILON, ZERO_VECTOR}; use nalgebra::{Unit, Vector3}; @@ -11,6 +11,7 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 material, .. } = intersect; + let kd = material.kd; let ks = material.ks; let shininess = material.shininess; @@ -28,9 +29,15 @@ 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 to_light = Unit::new_normalize(to_light); + let to_light = to_light; + + let to_light_ray = Ray::new(point.clone() + normal * EPSILON, to_light); + if light_blocked(scene, to_light_ray) { + continue; + } + // Point to camera - let to_camera = Unit::new_normalize(-incidence.into_inner()); + let to_camera = -incidence; // Diffuse component let n_dot_l = normal.dot(&to_light).max(0.0); let diffuse = n_dot_l * kd; @@ -58,3 +65,15 @@ pub fn phong_shade_point(scene: &Scene, intersect: &Intersection) -> Vector3 let (r, g, b) = (colour.x as u8, colour.y as u8, colour.z as u8); Vector3::new(r, g, b) } + +fn light_blocked(scene: &Scene, ray: Ray) -> bool { + for node in &scene.nodes { + let ray = ray.transform(&node.inv_model); + if node.primitive.intersect_bounding_box(&ray).is_some() { + if node.primitive.intersect_ray(&ray).is_some() { + return true; + } + } + } + false +} diff --git a/src/scene.rs b/src/scene.rs index 12f2dbf..4b23d6a 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,38 +1,66 @@ use crate::camera::Camera; use crate::light::Light; use crate::primitive::*; -use nalgebra::{Matrix4, Point3, Vector3}; -use rhai::{Engine, EvalAltResult}; +use nalgebra::{Matrix4, Vector3}; use std::sync::Arc; #[derive(Clone)] pub struct Node { pub primitive: Arc, - pub model_transform: Matrix4, + pub model: Matrix4, + pub inv_model: Matrix4, + pub viewmodel: Matrix4, + pub inv_viewmodel: Matrix4, } impl Node { - pub fn new(primitive: Arc) -> Self { + pub fn new(primitive: Arc) -> Node { Node { primitive, - model_transform: Matrix4::identity(), + model: Matrix4::identity(), + inv_model: Matrix4::identity(), + viewmodel: Matrix4::identity(), + inv_viewmodel: Matrix4::identity(), } } - pub fn rotate(&mut self, roll: f32, pitch: f32, yaw: f32) { - let roll = (roll as f64).to_radians() as f32; - let pitch = (pitch as f64).to_radians() as f32; - let yaw = (yaw as f64).to_radians() as f32; + pub fn rotate(&mut self, roll: f64, pitch: f64, yaw: f64) { + let roll = roll.to_radians(); + let pitch = pitch.to_radians(); + let yaw = yaw.to_radians(); let rotation_matrix = Matrix4::from_euler_angles(roll, pitch, yaw); - self.model_transform = rotation_matrix * self.model_transform; + 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(); } - pub fn translate(&mut self, translation: &Vector3) { - let translation_matrix = Matrix4::new_translation(translation); - self.model_transform = translation_matrix * self.model_transform; + pub fn translate(&mut self, translation: Vector3) { + 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) { - let scale_matrix = Matrix4::new_nonuniform_scaling(scale); - self.model_transform = scale_matrix * self.model_transform; + pub fn scale(&mut self, scale: Vector3) { + let scale_matrix = Matrix4::new_nonuniform_scaling(&scale); + self.model = scale_matrix * self.model; + 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; } } + #[derive(Clone)] pub struct Scene { pub nodes: Vec, @@ -51,94 +79,21 @@ impl Scene { cameras: Vec::new(), } } - fn add_node(&mut self, node: Node) { + pub fn add_node(&mut self, node: Node) { self.nodes.push(node); } - fn add_material(&mut self, material: Material) { + pub fn add_material(&mut self, material: Material) { self.materials.push(material); } - fn add_light(&mut self, light: Light) { + pub fn add_light(&mut self, light: Light) { self.lights.push(light); } - fn add_camera(&mut self, camera: Camera) { + pub fn add_camera(&mut self, camera: Camera) { self.cameras.push(camera); } - - pub fn from_rhai(script: &str) -> Result> { - 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("Scene", Scene::empty) - .register_fn("addNode", Scene::add_node) - .register_fn("addLight", Scene::add_light); - - engine - .register_type::() - .register_fn("Node", Node::new) - .register_fn("translate", Node::translate) - .register_fn("rotate", Node::rotate) - .register_fn("scale", Node::scale); - engine - .register_type::() - .register_fn("Camera", Camera::new_sizeless); - engine - .register_type::() - .register_fn("Light", Light::new); - 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("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("Rectangle", Rectangle::new) - .register_fn("RectangleUnit", Rectangle::unit); - engine - .register_type::() - .register_fn("Steiner", SteinerSurface::new); - engine - .register_type::() - .register_fn("Torus", Torus::new); - engine - .register_type::() - .register_fn("Adam", AdamShape::new); - engine - .register_type::() - .register_fn("Adam2", AdamShape2::new); - engine - .register_type::() - .register_fn("Adam3", AdamShape3::new); - - let scene: Scene = engine.eval(script.into())?; - Ok(scene) + 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 269343a..7eb28d5 100644 --- a/src/state.rs +++ b/src/state.rs @@ -1,6 +1,7 @@ //Use linear algebra module use crate::camera::Camera; +use crate::ray::Ray; use crate::{gui::Gui, scene::Scene}; use crate::{gui::GuiEvent, log_error}; @@ -35,6 +36,7 @@ pub struct State { pixels: Arc>, gui: Gui, + rays: Vec, ray_queue: Vec, } @@ -43,6 +45,7 @@ impl State { let scene = Scene::empty(); let window_size = window.inner_size(); let camera = Camera::unit(); + let rays = Vec::new(); Self { scene, @@ -52,6 +55,7 @@ impl State { buffer_height: window_size.height as u32, pixels: Arc::new(Mutex::new(pixels)), gui, + rays, ray_queue: Vec::new(), } } @@ -59,25 +63,33 @@ impl State { fn update(&mut self) -> Result<(), Box> { if let Some(event) = self.gui.event.take() { match event { - GuiEvent::BufferResize(proportion) => self.resize_buffer(proportion)?, - GuiEvent::CameraUpdate(camera) => self.set_camera(camera)?, + GuiEvent::BufferResize(proportion, fov) => { + self.resize_buffer(proportion, fov as f64)? + } + GuiEvent::CameraUpdate(camera) => { + self.camera = camera; + self.clear()?; + self.reset_queue(); + } GuiEvent::SceneLoad(scene) => { self.scene = scene; - self.clear()?; + self.reset_queue(); } } }; Ok(()) } - fn resize_buffer(&mut self, proportion: f32) -> Result<(), Box> { + fn resize_buffer(&mut self, proportion: f32, fovy: f64) -> Result<(), Box> { 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; - self.camera.set_size(self.buffer_width, self.buffer_height); self.clear()?; + self.reset_queue(); + + self.rays = Ray::cast_rays(fovy, self.buffer_width, self.buffer_height); let mut pixels = self.pixels.lock().unwrap(); pixels.resize_buffer(self.buffer_width, self.buffer_height)?; @@ -85,6 +97,9 @@ impl State { } fn resize(&mut self, size: &PhysicalSize) -> Result<(), Box> { + self.buffer_width = (size.width) as u32; + self.buffer_height = (size.height) as u32; + self.reset_queue(); let mut pixels = self.pixels.lock().unwrap(); pixels.resize_surface(size.width, size.height)?; Ok(()) @@ -105,7 +120,7 @@ impl State { //Get random index from queue let index = self.ray_queue.pop().unwrap(); //Shade colour for selected ray - let colour = &self.camera.rays[index].shade_ray(&self.scene); + 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(); @@ -128,19 +143,12 @@ impl State { Ok(()) } - fn set_camera(&mut self, camera: Camera) -> Result<(), Box> { - self.clear()?; - self.reset_queue(); - self.camera = camera; - self.camera.set_size(self.buffer_width, self.buffer_height); - Ok(()) - } - fn reset_queue(&mut self) { let size = self.buffer_height as usize * self.buffer_width as usize; 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> { @@ -171,7 +179,7 @@ pub fn run() -> Result<(), Box> { let gui = Gui::new(&window, &pixels); let mut state = State::new(window, pixels, gui); - state.resize_buffer(1.0)?; + state.resize_buffer(1.0, 90.0)?; event_loop.run(move |event, _, control_flow| { state.gui.handle_event(&state.window, &event); @@ -186,6 +194,7 @@ pub fn run() -> Result<(), Box> { *control_flow = ControlFlow::Exit; } } + Event::RedrawRequested(_) => { if let Err(_e) = state.render() { *control_flow = ControlFlow::Exit; @@ -193,7 +202,7 @@ pub fn run() -> Result<(), Box> { } _ => state.window.request_redraw(), } - }); + }) } fn create_window(event_loop: &EventLoop<()>) -> Window {