diff --git a/src/ray.rs b/src/ray.rs index 965eeaf..f5d024a 100644 --- a/src/ray.rs +++ b/src/ray.rs @@ -1,5 +1,24 @@ use crate::{node::Node, scene::Scene}; use nalgebra::{Matrix4, Point3, Vector3}; +use rand; + +const MAX_DEPTH: u8 = 5; +const DIFFUSE_RAYS: i8 = 5; +const DIFFUSE_COEFFICIENT: f32 = 0.5; + +fn random_vec() -> Vector3 { + Vector3::new(rand::random(), rand::random(), rand::random()) +} +fn random_unit_vec() -> Vector3 { + random_vec().normalize() +} +fn random_on_hemisphere(normal: &Vector3) -> Vector3 { + let dir = random_unit_vec(); + match dir.dot(normal) > 0.0 { + true => dir, + false => -dir, + } +} // INTERSECTION ----------------------------------------------------------------- pub struct Intersection { @@ -8,6 +27,7 @@ pub struct Intersection { pub normal: Vector3, pub distance: f64, } +//Intersection point including point and normal impl Intersection { pub fn transform(&self, trans: &Matrix4, inv_trans: &Matrix4) -> Intersection { let point = trans.transform_point(&self.point); @@ -46,13 +66,35 @@ impl Ray { pub fn at_t(&self, t: f64) -> Point3 { self.a + self.b * t } - // This function takes a scene and returns the color of the point where the ray intersects the scene - pub fn shade_ray(&self, scene: &Scene) -> Option> { - //Get the closest intersection of the ray with the scene + // Return a transformed version of the ray + pub fn transform(&self, trans: &Matrix4) -> Ray { + Ray { + a: trans.transform_point(&self.a), + b: trans.transform_vector(&self.b), + } + } + //This function will determine if the ray hits an object in the scene + pub fn hit_scene(&self, scene: &Scene) -> bool { + for (_, node) in &scene.nodes { + if !node.active { + continue; + } + // Transform ray into local model cordinates + let ray = self.transform(&node.inv_model); + // Check bounding box intersection + if node.primitive.intersect_bounding_box(&ray) { + // Check primitive intersection + if node.primitive.intersect_ray(&ray).is_some() { + return true; + } + } + } + false + } + //This function find the closest intersection point of a ray with an object in the scene + pub fn closest_intersect<'a>(&'a self, scene: &'a Scene) -> Option<(&Node, Intersection)> { let mut closest_distance = f64::MAX; - let mut closest_intersect: Option = None; - let mut closest_node = None; - + let mut closest_intersect: Option<(&Node, Intersection)> = None; for (_, node) in &scene.nodes { if !node.active { continue; @@ -66,20 +108,28 @@ impl Ray { // Check for closest distance if intersect.distance < closest_distance { closest_distance = intersect.distance; - closest_intersect = Some(intersect); - closest_node = Some(node); + closest_intersect = Some((node, intersect)); } } } } - - //Shade the intersection point if there is one match closest_intersect { - Some(intersect) => { - //Inverse transform back to world coords - let node = closest_node.unwrap(); - let intersect = intersect.transform(&node.model, &node.inv_model); - Some(Ray::phong_shade_point(&scene, &self, &node, &intersect)) // If there is an intersection, shade it + Some((node, intersect)) => { + Some((node, intersect.transform(&node.model, &node.inv_model))) + } + None => None, + } + } + // This function takes a scene and returns the color of the point where the ray intersects the scene + pub fn shade_ray(&self, scene: &Scene, depth: u8) -> Option> { + if depth == MAX_DEPTH { + return None; + } + match self.closest_intersect(scene) { + Some((node, intersect)) => { + Some(Ray::phong_shade_point( + &scene, &self, &node, &intersect, depth, + )) // If there is an intersection, shade it } None => None, // If there is no intersection, return None } @@ -91,18 +141,13 @@ impl Ray { ray: &Ray, node: &Node, intersect: &Intersection, - ) -> Vector3 { + depth: u8, + ) -> Vector3 { let point = &intersect.point; let normal = &intersect.normal; let incidence = &ray.b; let material = &node.material; - let kd = &material.kd; - let ks = &material.ks; - let shininess = material.shininess; - - // Point to camera - let to_camera = -incidence; // Compute the ambient light component and set it as base colour let mut colour = Vector3::zeros(); @@ -121,59 +166,62 @@ impl Ray { let light_distance = to_light.norm() as f32; let to_light = to_light.normalize(); - // let to_light_ray = Ray::new(point.clone() + 0.0001 * normal, to_light); - // if to_light_ray.light_blocked(scene) { - // continue; - // } + let to_light_ray = Ray::new(point.clone() + 0.001 * normal, to_light); + if to_light_ray.light_blocked(scene, node) { + continue; + } - // Diffuse component let n_dot_l = normal.dot(&to_light).max(0.0) as f32; - let diffuse = n_dot_l * kd; - // Specular component + + //Diffuse component + let mut diffuse = Vector3::zeros(); + // diffuse = material.kd * n_dot_l; + for _ in 0..DIFFUSE_RAYS { + let diffuse_dir = random_on_hemisphere(normal); + let ray = Ray::new(point.clone() + normal, diffuse_dir); + if let Some(col) = ray.shade_ray(scene, depth + 1) { + diffuse += col * DIFFUSE_COEFFICIENT; + } + } + + //Specular component let mut specular = Vector3::zeros(); if n_dot_l > 0.0 { - // Halfway vector. - let h = to_camera + to_light.normalize(); + let h = (to_light - incidence).normalize(); let n_dot_h = normal.dot(&h).max(0.0) as f32; - specular = ks * n_dot_h.powf(shininess); + specular = material.ks * n_dot_h.powf(material.shininess); } - // Compute light falloff + + //Falloff let falloff = 1.0 / (1.0 + light.falloff[0] + light.falloff[1] * light_distance - + light.falloff[2] * light_distance.powi(2)); + + light.falloff[2] * light_distance * light_distance); - let light_intensity = light.colour.component_mul(&(diffuse + specular)) * falloff; - colour += &light_intensity; + let intensity = light + .colour + .component_mul(&((diffuse + specular) * falloff)); + colour += &intensity; } - colour *= 255.0; - let (r, g, b) = (colour.x as u8, colour.y as u8, colour.z as u8); - Vector3::new(r, g, b) + colour } - pub fn light_blocked(&mut self, scene: &Scene) -> bool { + pub fn light_blocked(&self, scene: &Scene, _node: &Node) -> bool { for (_, node) in &scene.nodes { if !node.active { continue; } - self.transform(&node.inv_model); - if node.primitive.intersect_bounding_box(&self) { - if node.primitive.intersect_ray(&self).is_some() { + let ray = self.transform(&node.inv_model); + if node.primitive.intersect_bounding_box(&ray) { + if node.primitive.intersect_ray(&ray).is_some() { return true; } } } false } - // Return a transformed version of the ray - pub fn transform(&self, trans: &Matrix4) -> Ray { - Ray { - a: trans.transform_point(&self.a), - b: trans.transform_vector(&self.b), - } - } //Cast a set of rays pub fn cast_rays( eye: &Point3, @@ -190,29 +238,31 @@ impl Ray { let fovy_radians = fovy.to_radians(); let fovh_radians = 2.0 * ((fovy_radians / 2.0).tan() * aspect).atan(); // Vectors pointing forward, right and up - let forward = (target - eye).normalize(); - let right = forward.cross(&up).normalize(); - let up = right.cross(&forward).normalize(); + let zv = (target - eye).normalize(); + let xv = zv.cross(&up).normalize(); + let yv = xv.cross(&zv).normalize(); // ☐ height and width of projection let vheight = 2.0 * (fovy_radians / 2.0).tan(); let vwidth = 2.0 * (fovh_radians / 2.0).tan(); // Increment of right and up per pixel - let d_hor_vec = right * (vwidth / width); - let d_vert_vec = up * (vheight / height); + let dy = vheight / height; + let dx = vwidth / width; + let dxv = dx * xv; + let dyv = dy * yv; // Half the width for later calculation let half_width = width / 2.0; let half_height = height / 2.0; // Array of rays let mut rays = Vec::with_capacity(width as usize * height as usize); // Iterate column by row - for row in 0..height as u32 { - for column in 0..width as u32 { - let x = (column as f64) - half_width; - let y = half_height - (row as f64); + for y in 0..height as u32 { + for x in 0..width as u32 { + let x = (x as f64) - half_width; + let y = half_height - (y as f64); - let horizontal = x * &d_hor_vec; - let vertical = y * &d_vert_vec; - let direction = (forward + horizontal + vertical).normalize(); + let horizontal = x * &dxv; + let vertical = y * &dyv; + let direction = (zv + horizontal + vertical).normalize(); let ray = Ray::new(eye.clone(), direction); rays.push(ray); }