psychopath/src/surface/triangle.rs

95 lines
2.8 KiB
Rust

#![allow(dead_code)]
use math::Point;
use ray::Ray;
/// Intersects `ray` with `tri`, returning `Some((t, b0, b1, b2))`, or `None`
/// if no intersection.
///
/// Returned values:
///
/// * `t` is the ray t at the hit point.
/// * `b0`, `b1`, and `b2` are the barycentric coordinates of the triangle at
/// the hit point.
///
/// Uses the ray-triangle test from the paper "Watertight Ray/Triangle
/// Intersection" by Woop et al.
pub fn intersect_ray(ray: &Ray, tri: (Point, Point, Point)) -> Option<(f32, f32, f32, f32)> {
// Calculate the permuted dimension indices for the new ray space.
let (xi, yi, zi) = {
let xabs = ray.dir.x().abs();
let yabs = ray.dir.y().abs();
let zabs = ray.dir.z().abs();
if xabs > yabs && xabs > zabs {
(1, 2, 0)
} else if yabs > zabs {
(2, 0, 1)
} else {
(0, 1, 2)
}
};
let dir_x = ray.dir.get_n(xi);
let dir_y = ray.dir.get_n(yi);
let dir_z = ray.dir.get_n(zi);
// Calculate shear constants.
let sx = dir_x / dir_z;
let sy = dir_y / dir_z;
let sz = 1.0 / dir_z;
// Calculate vertices in ray space.
let p0 = tri.0 - ray.orig;
let p1 = tri.1 - ray.orig;
let p2 = tri.2 - ray.orig;
let p0x = p0.get_n(xi) - (sx * p0.get_n(zi));
let p0y = p0.get_n(yi) - (sy * p0.get_n(zi));
let p1x = p1.get_n(xi) - (sx * p1.get_n(zi));
let p1y = p1.get_n(yi) - (sy * p1.get_n(zi));
let p2x = p2.get_n(xi) - (sx * p2.get_n(zi));
let p2y = p2.get_n(yi) - (sy * p2.get_n(zi));
// Calculate scaled barycentric coordinates.
let mut e0 = (p1x * p2y) - (p1y * p2x);
let mut e1 = (p2x * p0y) - (p2y * p0x);
let mut e2 = (p0x * p1y) - (p0y * p1x);
// Fallback to test against edges using double precision.
if e0 == 0.0 || e1 == 0.0 || e2 == 0.0 {
e0 = ((p1x as f64 * p2y as f64) - (p1y as f64 * p2x as f64)) as f32;
e1 = ((p2x as f64 * p0y as f64) - (p2y as f64 * p0x as f64)) as f32;
e2 = ((p0x as f64 * p1y as f64) - (p0y as f64 * p1x as f64)) as f32;
}
// Check if the ray hit the triangle.
if (e0 < 0.0 || e1 < 0.0 || e2 < 0.0) && (e0 > 0.0 || e1 > 0.0 || e2 > 0.0) {
return None;
}
// Determinant
let det = e0 + e1 + e2;
if det == 0.0 {
return None;
}
// Calculate t of hitpoint.
let p0z = sz * p0.get_n(zi);
let p1z = sz * p1.get_n(zi);
let p2z = sz * p2.get_n(zi);
let t = (e0 * p0z) + (e1 * p1z) + (e2 * p2z);
// Check if the hitpoint t is within ray min/max t.
if det > 0.0 && (t <= 0.0 || t > (ray.max_t * det)) {
return None;
} else if det < 0.0 && (t >= 0.0 || t < (ray.max_t * det)) {
return None;
}
// Return t and the hitpoint barycentric coordinates.
let inv_det = 1.0 / det;
Some((t * inv_det, e0 * inv_det, e1 * inv_det, e2 * inv_det))
}