use std::f32::EPSILON; use stl::STL; use crate::{ aabb::AABB, hitable::{Hit, HitRecord}, material::Material, ray::Ray, vec3::{cross, dot, Vec3}, }; #[derive(Debug)] pub struct Triangle { normal: Vec3, verts: [Vec3; 3], // Precomputed data } #[derive(Debug)] pub struct Triangles where M: Material, { pub triangles: Vec, pub bbox: AABB, material: M, } impl Triangles where M: Material, { pub fn new(stl: &STL, material: M, scale_factor: f32) -> Triangles { let triangles: Vec<_> = stl .triangles .iter() .map(|t| { let v0 = t.verts[0] * scale_factor; let v1 = t.verts[1] * scale_factor; let v2 = t.verts[2] * scale_factor; Triangle { normal: t.normal, verts: [v0, v1, v2], } }) .collect(); let (min, max) = triangles.iter().fold( (Vec3::from(f32::MAX), Vec3::from(f32::MIN)), |(min, max), t| { let t_min_x = t.verts[0].x.min(t.verts[1].x.min(t.verts[2].x)); let t_min_y = t.verts[0].y.min(t.verts[1].y.min(t.verts[2].y)); let t_min_z = t.verts[0].z.min(t.verts[1].z.min(t.verts[2].z)); let t_max_x = t.verts[0].x.max(t.verts[1].x.max(t.verts[2].x)); let t_max_y = t.verts[0].y.max(t.verts[1].y.max(t.verts[2].y)); let t_max_z = t.verts[0].z.max(t.verts[1].z.max(t.verts[2].z)); ( Vec3::from([min.x.min(t_min_x), min.y.min(t_min_y), min.z.min(t_min_z)]), Vec3::from([max.x.max(t_max_x), max.y.max(t_max_y), max.z.max(t_max_z)]), ) }, ); let bbox = AABB::new(min, max); Triangles { triangles, bbox, material, } } } /// Based on https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection.html fn ray_triangle_intersect_moller_trumbore(r: Ray, tri: &Triangle) -> Option { // #ifdef MOLLER_TRUMBORE // Vec3f v0v1 = v1 - v0; // Vec3f v0v2 = v2 - v0; // Vec3f pvec = dir.crossProduct(v0v2); // float det = v0v1.dotProduct(pvec); // #ifdef CULLING // // if the determinant is negative, the triangle is 'back facing' // // if the determinant is close to 0, the ray misses the triangle // if (det < kEpsilon) return false; // #else // // ray and triangle are parallel if det is close to 0 // if (fabs(det) < kEpsilon) return false; // #endif // float invDet = 1 / det; // // Vec3f tvec = orig - v0; // u = tvec.dotProduct(pvec) * invDet; // if (u < 0 || u > 1) return false; // // Vec3f qvec = tvec.crossProduct(v0v1); // v = dir.dotProduct(qvec) * invDet; // if (v < 0 || u + v > 1) return false; // // t = v0v2.dotProduct(qvec) * invDet; // let v0 = tri.verts[0]; let v1 = tri.verts[1]; let v2 = tri.verts[2]; let v0v1 = v1 - v0; let v0v2 = v2 - v0; let p = cross(r.direction, v0v2); let det = dot(v0v1, p); if det < EPSILON { return None; } let inv_det = 1. / det; let t = r.origin - v0; let u = dot(t, p) * inv_det; if u < 0. || u > 1. { return None; } let q = cross(t, v0v1); let v = dot(r.direction, q) * inv_det; if v < 0. || u + v > 1. { return None; } let t = dot(v0v2, q) * inv_det; if t > EPSILON { return Some(RayTriangleResult { t, p: r.origin + r.direction * t, }); } None } impl Hit for Triangles where M: Material, { fn hit(&self, r: Ray, _t_min: f32, _t_max: f32) -> Option { // TODO(wathiede): add an acceleration structure to more cheaply skip some triangles. for tri in &self.triangles { if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_moller_trumbore(r, tri) { //if let Some(RayTriangleResult { t, p }) = ray_triangle_intersect_geometric(r, tri) { // We don't support UV (yet?). let uv = (0.5, 0.5); return Some(HitRecord { t, uv, p, normal: tri.normal, material: &self.material, }); } } None } fn bounding_box(&self, _t_min: f32, _t_max: f32) -> Option { Some(self.bbox) } } struct RayTriangleResult { t: f32, p: Vec3, }