use crate::{primitive::Intersection, raytracer::phong_shade_point, scene::Scene}; use nalgebra::{Matrix4, Point3, Vector3}; #[derive(Clone)] // Ray struct represents a ray in 3D space with a starting point 'a' and a direction 'b' pub struct Ray { pub a: Point3, pub b: Vector3, } #[allow(dead_code)] impl Ray { //Create a new ray with a normalized direction pub fn new(a: Point3, b: Vector3) -> Ray { Ray { a, b: b.normalize(), } } // The starting point is the origin and the direction is negative z-axis pub fn unit() -> Ray { let a = Point3::origin(); let b = -Vector3::z(); Ray { a, b } } //Return the point at distance t along the 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 let mut closest_distance = f64::MAX; let mut closest_intersect: Option = None; let mut closest_node = None; for (_, node) in &scene.nodes { // 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 let Some(intersect) = node.primitive.intersect_ray(&ray) { // Check for closest distance if intersect.distance < closest_distance { closest_distance = intersect.distance; closest_intersect = Some(intersect); closest_node = Some(node); } } } } //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(phong_shade_point(&scene, &intersect)) // If there is an intersection, shade it } None => None, // If there is no intersection, return None } } // 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, target: &Point3, up: &Vector3, fovy: f64, width: u32, height: u32, ) -> Vec { //Aspect ratio calculation let (width, height) = (width as f64, height as f64); let aspect = width / height; //X and Y fov calculations 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(); // ☐ 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); // 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); let horizontal = x * &d_hor_vec; let vertical = y * &d_vert_vec; let direction = (forward + horizontal + vertical).normalize(); let ray = Ray::new(eye.clone(), direction); rays.push(ray); } } rays } }