use crate::ray::Ray; use crate::{EPSILON, INFINITY}; use nalgebra::{distance, Matrix4, Point3, Vector3}; use roots::{find_roots_quadratic, Roots}; use std::fs::File; use std::io::{BufRead, BufReader}; use std::path::Path; use std::sync::Arc; // MATERIAL ----------------------------------------------------------------- pub struct Material { pub kd: Vector3, pub ks: Vector3, pub shininess: f32, } impl Material { pub fn new(kd: Vector3, ks: Vector3, shininess: f32) -> Self { Material { kd, ks, shininess } } pub fn magenta() -> Self { let kd = Vector3::new(1.0, 0.0, 1.0); let ks = Vector3::new(0.0, 1.0, 1.0); let shininess = 0.5; Material { kd, ks, shininess } } } // INTERSECTION ----------------------------------------------------------------- pub struct Intersection { // Information about an intersection pub point: Point3, pub normal: Vector3, pub incidence: Vector3, pub material: Arc, pub distance: f32, } impl Intersection { pub fn new( point: Point3, normal: Vector3, incidence: Vector3, material: Arc, t: f32, ) -> Self { Intersection { point, normal, incidence, material, distance: t, } } } // BOUNDING BOX ----------------------------------------------------------------- struct BoundingBox { bln: Point3, trf: Point3, } impl BoundingBox { fn new(bln: Point3, trf: Point3) -> Self { BoundingBox { bln, trf } } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { let t1 = (self.bln - ray.a).component_div(&ray.b); let t2 = (self.trf - ray.a).component_div(&ray.b); let tmin = t1.inf(&t2).min(); let tmax = t1.sup(&t2).max(); if tmax >= tmin { Some(ray.at_t(tmin)) } else { None } } } // PRIMITIVE TRAIT ----------------------------------------------------------------- pub trait Primitive { fn intersect_ray(&self, ray: &Ray) -> Option; fn intersect_bounding_box(&self, ray: &Ray) -> Option>; fn get_material(&self) -> Arc; } // SPHERE ----------------------------------------------------------------- pub struct Sphere { position: Point3, radius: f32, bounding_box: BoundingBox, material: Arc, } impl Sphere { fn new(position: Point3, radius: f32, material: Arc) -> Self { let radius_vec = Vector3::new(radius, radius, radius); let bln = position - radius_vec; let trf = position + radius_vec; let bounding_box = BoundingBox::new(bln, trf); Sphere { position, radius, bounding_box, material, } } pub fn unit(material: Arc) -> Self { Sphere::new(Point3::new(0.0, 0.0, 0.0), 1.0, material) } } impl Primitive for Sphere { fn intersect_ray(&self, ray: &Ray) -> Option { let pos = &ray.a; let dir = &ray.b; let l = pos - self.position; let a = dir.dot(dir); let b = 2.0 * l.dot(dir); let c = l.dot(&l) - self.radius * self.radius; let t = match find_roots_quadratic(a, b, c) { Roots::No(_) => return None, Roots::One([x1]) => x1, Roots::Two([x1, x2]) => { if x1 <= 0.0 && x2 <= 0.0 { return None; } else { if x1.abs() < x2.abs() { x1 } else { x2 } } } _ => return None, }; let intersect = ray.at_t(t); let normal = (intersect - self.position).normalize(); Some(Intersection { point: intersect, normal, incidence: ray.b, material: Arc::clone(&self.material), distance: t, }) } fn get_material(&self) -> Arc { Arc::clone(&self.material) } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { return self.bounding_box.intersect_bounding_box(ray); } } // CIRCLE ----------------------------------------------------------------- struct Circle { position: Point3, radius: f32, normal: Vector3, material: Arc, bounding_box: BoundingBox, } impl Circle { fn new( position: Point3, radius: f32, normal: Vector3, material: Arc, ) -> Self { let radius_vec = Vector3::new(radius, radius, radius); let bln = position - radius_vec; let trf = position + radius_vec; let bounding_box = BoundingBox::new(bln, trf); Circle { position, radius, normal: normal.normalize(), material, bounding_box, } } pub fn unit(material: Arc) -> Self { let position = Point3::new(0.0, 0.0, 0.0); let normal = Vector3::new(0.0, 1.0, 0.0); let radius = 1.0; let material = material; let bln = Point3::new(-radius, 0.0, -EPSILON); let trf = Point3::new(radius, 0.0, EPSILON); let bounding_box = BoundingBox { bln, trf }; Circle { position, normal, radius, material, bounding_box, } } } impl Primitive for Circle { fn intersect_ray(&self, ray: &Ray) -> Option { let constant = self.position.coords.dot(&self.normal); let denominator = ray.b.dot(&self.normal); let t = (constant - ray.a.coords.dot(&self.normal)) / denominator; if t > INFINITY { return None; }; let intersect = ray.at_t(t); let distance = distance(&intersect, &self.position); match distance > self.radius { true => return None, false => { return Some(Intersection { point: intersect, normal: self.normal, incidence: ray.b, material: Arc::clone(&self.material), distance: t, }) } } } fn get_material(&self) -> Arc { Arc::clone(&self.material) } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { self.bounding_box.intersect_bounding_box(ray) } } // CYLINDER ----------------------------------------------------------------- struct Cylinder { radius: f32, base: f32, height: f32, base_circle: Circle, height_circle: Circle, material: Arc, } impl Cylinder {} impl Primitive for Cylinder { fn intersect_ray(&self, ray: &Ray) -> Option { todo!() } fn get_material(&self) -> Arc { todo!() } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { todo!() } } // CONE ----------------------------------------------------------------- pub struct Cone { radius: f32, base: f32, height: f32, circle: Circle, material: Arc, bounding_box: BoundingBox, } impl Cone { pub fn new(radius: f32, height: f32, base: f32, material: Arc) -> Self { let circle = Circle::new( Point3::new(0.0, base, 0.0), radius, Vector3::new(0.0, 1.0, 0.0), Arc::clone(&material), ); let bln = Point3::new(-radius, base, -radius); let trf = Point3::new(radius, base + height, radius); Cone { radius, base, height, circle, material, bounding_box: BoundingBox { bln, trf }, } } pub fn unit(material: Arc) -> Self { Cone::new(1.0, 2.0, -1.0, material) } pub fn get_normal(&self, intersect: Point3) -> Vector3 { let r = self.radius; let h = self.height; let (x, y, z) = (intersect.x, intersect.y, intersect.z); let normal = Vector3::new(2.0 * x, 2.0 * r * r * (h - y), 2.0 * z).normalize(); normal } } impl Primitive for Cone { fn intersect_ray(&self, ray: &Ray) -> Option { let point = &ray.a; let dir = &ray.b; let (r, h) = (self.radius, self.height); let (a1, a2, a3) = (point.x, point.y, point.z); let (b1, b2, b3) = (dir.x, dir.y, dir.z); let r2 = r * r; let a = b1 * b1 + b3 * b3 - r2 * (b2 * b2); let b = 2.0 * (a1 * b1 + a3 * b3 + r2 * (h * b2 - a2 * b2)); let c = a1 * a1 + a3 * a3 + r2 * (2.0 * h * a2 - h * h - a2 * a2); let t = match find_roots_quadratic(a, b, c) { Roots::No(_) => None, Roots::One([x1]) => Some(x1), Roots::Two([x1, x2]) => { if x1 <= 0.0 && x2 <= 0.0 { None } else { if x1.abs() < x2.abs() { Some(x1) } else { Some(x2) } } } _ => None, }; let cone_intersect = match t { None => None, Some(t) => { let intersect = ray.at_t(t); match intersect.y >= self.base && intersect.y <= self.height { true => Some(Intersection { point: intersect, normal: self.get_normal(intersect), material: Arc::clone(&self.material), incidence: ray.b, distance: t, }), false => None, } } }; let circle_intersect = self.circle.intersect_ray(ray); match (cone_intersect, circle_intersect) { (None, None) => None, (Some(cone_intersect), None) => Some(cone_intersect), (None, Some(circle_intersect)) => Some(circle_intersect), (Some(cone_intersect), Some(circle_intersect)) => { let circle_distance = distance(&ray.a, &circle_intersect.point); let cone_distance = distance(&ray.a, &cone_intersect.point); match cone_distance < circle_distance { true => Some(cone_intersect), false => Some(circle_intersect), } } } } fn get_material(&self) -> Arc { Arc::clone(&self.material) } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { self.bounding_box.intersect_bounding_box(ray) } } // RECTANGLE ----------------------------------------------------------------- struct Rectangle { position: Point3, normal: Vector3, width_direction: Vector3, material: Arc, width: f32, height: f32, bounding_box: BoundingBox, } impl Rectangle { fn new( position: Point3, normal: Vector3, width_direction: Vector3, width: f32, height: f32, material: Arc, ) -> Self { let normal = normal.normalize(); let width_direction = width_direction.normalize(); let height_direction = width_direction.cross(&normal); let bln = position - width / 2.0 * width_direction - height / 2.0 * height_direction; let trf = position + width / 2.0 * width_direction + height / 2.0 * height_direction; Rectangle { position, normal: normal.normalize(), width_direction: width_direction.normalize(), width, height, material, bounding_box: BoundingBox { bln, trf }, } } pub fn unit(material: Arc) -> Self { Rectangle::new( Point3::new(0.0, 0.0, 0.0), Vector3::new(0.0, 1.0, 0.0), Vector3::new(1.0, 0.0, 0.0), 2.0, 2.0, material, ) } } impl Primitive for Rectangle { fn intersect_ray(&self, ray: &Ray) -> Option { let constant = self.position.coords.dot(&self.normal); let denominator = ray.b.dot(&self.normal); let t = (constant - ray.a.coords.dot(&self.normal)) / denominator; if t > INFINITY { return None; } let intersect = ray.at_t(t); let height_direction = self.width_direction.cross(&self.normal); let (w2, h2) = (self.width / 2.0, self.height / 2.0); let r1 = w2 * self.width_direction; let r2 = h2 * height_direction; let pi = intersect - self.position; let pi_dot_r1 = pi.dot(&r1); let pi_dot_r2 = pi.dot(&r2); if pi_dot_r1 >= -w2 && pi_dot_r1 <= w2 && pi_dot_r2 >= -h2 && pi_dot_r2 <= h2 { return Some(Intersection { point: intersect, normal: self.normal, incidence: ray.b, material: Arc::clone(&self.material), distance: t, }); } None } fn get_material(&self) -> Arc { Arc::clone(&self.material) } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { self.bounding_box.intersect_bounding_box(ray) } } // BOX ----------------------------------------------------------------- pub struct Box { width: f32, height: f32, depth: f32, material: Arc, bounding_box: BoundingBox, } impl Box { fn new(width: f32, height: f32, depth: f32, material: Arc) -> Self { let trf = Point3::new(width / 2.0, height / 2.0, depth / 2.0); let bln = Point3::new(-width / 2.0, -height / 2.0, -depth / 2.0); Box { width, height, depth, material, bounding_box: BoundingBox { bln, trf }, } } pub fn unit(material: Arc) -> Self { Box::new(2.0, 2.0, 2.0, material) } } impl Primitive for Box { fn intersect_ray(&self, ray: &Ray) -> Option { // Compute the minimum and maximum t-values for each axis of the bounding box let t1 = (self.bounding_box.bln - ray.a).component_div(&ray.b); let t2 = (self.bounding_box.trf - ray.a).component_div(&ray.b); // Find the largest minimum t-value and the smallest maximum t-value among the axes let tmin = t1.inf(&t2).max(); let tmax = t1.sup(&t2).min(); // Check if there's an intersection between tmin and tmax if tmax >= tmin { // The ray intersects the box, and tmin is the entry point, tmax is the exit point let intersect = ray.at_t(tmin); // Check if the intersection is outside the box if intersect.x < -self.width / 2.0 || intersect.x > self.width / 2.0 || intersect.y < -self.height / 2.0 || intersect.y > self.height / 2.0 || intersect.z < -self.depth / 2.0 || intersect.z > self.depth / 2.0 { return None; // Intersection is outside the box } //Get normal of intersection point let normal = if tmin == t1.x { Vector3::new(-1.0, 0.0, 0.0) } else if tmin == t1.y { Vector3::new(0.0, -1.0, 0.0) } else if tmin == t1.z { Vector3::new(0.0, 0.0, -1.0) } else if tmin == t2.x { Vector3::new(1.0, 0.0, 0.0) } else if tmin == t2.y { Vector3::new(0.0, 1.0, 0.0) } else { Vector3::new(0.0, 0.0, 1.0) }; Some(Intersection { point: intersect, normal, incidence: ray.b, material: Arc::clone(&self.material), distance: tmin, }) } else { None // No intersection with the box } } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { self.bounding_box.intersect_bounding_box(ray) } fn get_material(&self) -> Arc { Arc::clone(&self.material) } } // TRIANGLE ----------------------------------------------------------------- struct Triangle { u: Point3, v: Point3, w: Point3, normal: Vector3, material: Arc, bounding_box: BoundingBox, } impl Triangle { fn new(u: Point3, v: Point3, w: Point3, material: Arc) -> Self { let uv = v - u; let uw = w - u; let normal = uv.cross(&uw).normalize(); let bln = u.inf(&v).inf(&w); let trf = u.sup(&v).sup(&w); let bounding_box = BoundingBox { bln, trf }; Triangle { u, v, w, normal, material, bounding_box, } } pub fn unit(material: Arc) -> Self { let u = Point3::new(-1.0, 0.0, -1.0); let v = Point3::new(0.0, 0.0, 1.0); let w = Point3::new(1.0, 0.0, -1.0); let material = material; Triangle::new(u, v, w, material) } } impl Primitive for Triangle { fn intersect_ray(&self, ray: &Ray) -> Option { let constant = self.u.coords.dot(&self.normal); let denominator = ray.b.dot(&self.normal); let t = (constant - ray.a.coords.dot(&self.normal)) / denominator; if t > INFINITY { return None; } let intersect = ray.at_t(t); let uv = self.v - self.u; let vw = self.w - self.v; let wu = self.u - self.w; let ui = intersect - self.u; let vi = intersect - self.v; let wi = intersect - self.w; let u_cross = uv.cross(&ui); let v_cross = vw.cross(&vi); let w_cross = wu.cross(&wi); let normal = self.normal; if u_cross.dot(&normal) >= 0.0 && v_cross.dot(&normal) >= 0.0 && w_cross.dot(&normal) >= 0.0 { Some(Intersection { point: intersect, normal, incidence: ray.b, material: Arc::clone(&self.material), distance: t, }) } else { None } } fn get_material(&self) -> Arc { Arc::clone(&self.material) } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { self.bounding_box.intersect_bounding_box(ray) } } // MESH ----------------------------------------------------------------- struct Mesh { triangles: Vec, material: Arc, bounding_box: BoundingBox, } impl Mesh { fn new(triangles: Vec, material: Arc) -> Self { // Calculate the bounding box for the entire mesh based on the bounding boxes of individual triangles let bounding_box = Mesh::compute_bounding_box(&triangles); Mesh { triangles, material, bounding_box, } } fn compute_bounding_box(triangles: &Vec) -> BoundingBox { let mut bln = Point3::new(INFINITY, INFINITY, INFINITY); let mut trf = -bln; for triangle in triangles { bln = bln.inf(&triangle.u); bln = bln.inf(&triangle.v); bln = bln.inf(&triangle.w); trf = trf.sup(&triangle.u); trf = trf.sup(&triangle.v); trf = trf.sup(&triangle.w); } BoundingBox { bln, trf } } fn from_file(filename: &str, material: Arc) -> Self { let mut triangles: Vec = Vec::new(); let mut vertices: Vec> = Vec::new(); let file = File::open(filename).expect("Failed to open file"); let reader = BufReader::new(file); for line in reader.lines() { if let Ok(line) = line { let mut parts = line.split_whitespace(); if let Some(keyword) = parts.next() { match keyword { "v" => { // Parse vertex coordinates if let (Some(x_str), Some(y_str), Some(z_str)) = (parts.next(), parts.next(), parts.next()) { let x: f32 = x_str.parse().expect("Failed to parse vertex X"); let y: f32 = y_str.parse().expect("Failed to parse vertex Y"); let z: f32 = z_str.parse().expect("Failed to parse vertex Z"); vertices.push(Point3::new(x, y, z)); } } "f" => { // Parse face indices if let (Some(v1_str), Some(v2_str), Some(v3_str)) = (parts.next(), parts.next(), parts.next()) { let v1: usize = v1_str.parse().expect("Failed to parse vertex index"); let v2: usize = v2_str.parse().expect("Failed to parse vertex index"); let v3: usize = v3_str.parse().expect("Failed to parse vertex index"); // Indices in OBJ files are 1-based, so subtract 1 to convert to 0-based. let a = vertices[v1 - 1]; let b = vertices[v2 - 1]; let c = vertices[v3 - 1]; triangles.push(Triangle::new(a, b, c, Arc::clone(&material))); } } _ => {} } } } } todo!(); } } impl Primitive for Mesh { fn intersect_ray(&self, ray: &Ray) -> Option { let mut closest_distance = INFINITY; let mut closest_intersect: Option = None; for triangle in &self.triangles { match triangle.intersect_ray(ray) { Some(intersect) => { let distance = intersect.distance; if distance < closest_distance { closest_distance = distance; closest_intersect = Some(intersect); }; } None => continue, } } closest_intersect } fn get_material(&self) -> Arc { Arc::clone(&self.material) } fn intersect_bounding_box(&self, ray: &Ray) -> Option> { self.bounding_box.intersect_bounding_box(ray) } }