169 lines
4.7 KiB
Rust
169 lines
4.7 KiB
Rust
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<M>
|
|
where
|
|
M: Material,
|
|
{
|
|
pub triangles: Vec<Triangle>,
|
|
pub bbox: AABB,
|
|
material: M,
|
|
}
|
|
|
|
impl<M> Triangles<M>
|
|
where
|
|
M: Material,
|
|
{
|
|
pub fn new(stl: &STL, material: M, scale_factor: f32) -> Triangles<M> {
|
|
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<RayTriangleResult> {
|
|
// #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<M> Hit for Triangles<M>
|
|
where
|
|
M: Material,
|
|
{
|
|
fn hit(&self, r: Ray, _t_min: f32, _t_max: f32) -> Option<HitRecord> {
|
|
// 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<AABB> {
|
|
Some(self.bbox)
|
|
}
|
|
}
|
|
|
|
struct RayTriangleResult {
|
|
t: f32,
|
|
p: Vec3,
|
|
}
|