Fixed bug in mesh intersection code.
Very small triangles were being missed because of the not-so-robust ray-triangle intersection algorithm I was using. Switched to the algorithm from the paper "Watertight Ray/Triangle Intersection" by Woop et al. Happily, the new algorithm doesn't seem to measurably slow down renders at all.
This commit is contained in:
parent
2c0e57341c
commit
71bdf203aa
|
@ -1,40 +1,113 @@
|
||||||
#![allow(dead_code)]
|
#![allow(dead_code)]
|
||||||
|
|
||||||
use std;
|
use math::Point;
|
||||||
|
|
||||||
use math::{Point, cross, dot};
|
|
||||||
use ray::Ray;
|
use ray::Ray;
|
||||||
|
|
||||||
|
|
||||||
/// Intersects ray with tri, returning (t, u, v), or None if no intersection.
|
/// Intersects `ray` with `tri`, returning `Some((t, b0, b1, b2))`, or `None`
|
||||||
pub fn intersect_ray(ray: &Ray, tri: (Point, Point, Point)) -> Option<(f32, f32, f32)> {
|
/// if no intersection.
|
||||||
let edge1 = tri.1 - tri.0;
|
///
|
||||||
let edge2 = tri.2 - tri.0;
|
/// Returned values:
|
||||||
let pvec = cross(ray.dir, edge2);
|
///
|
||||||
let det = dot(edge1, pvec);
|
/// * `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 zi = {
|
||||||
|
let xabs = ray.dir.x().abs();
|
||||||
|
let yabs = ray.dir.y().abs();
|
||||||
|
let zabs = ray.dir.z().abs();
|
||||||
|
|
||||||
if det <= -std::f32::EPSILON || det >= std::f32::EPSILON {
|
if xabs > yabs && xabs > zabs {
|
||||||
let inv_det = 1.0 / det;
|
0
|
||||||
let tvec = ray.orig - tri.0;
|
} else if yabs > zabs {
|
||||||
let qvec = cross(tvec, edge1);
|
1
|
||||||
|
} else {
|
||||||
let t = dot(edge2, qvec) * inv_det;
|
2
|
||||||
if t < 0.0 {
|
|
||||||
return None;
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
let u = dot(tvec, pvec) * inv_det;
|
let (xi, yi) = if ray.dir.get_n(zi) >= 0.0 {
|
||||||
if u < 0.0 || u > 1.0 {
|
let xi = if zi == 2 { 0 } else { zi + 1 };
|
||||||
return None;
|
let yi = if xi == 2 { 0 } else { xi + 1 };
|
||||||
}
|
(xi, yi)
|
||||||
|
|
||||||
let v = dot(ray.dir, qvec) * inv_det;
|
|
||||||
if v < 0.0 || (u + v) > 1.0 {
|
|
||||||
return None;
|
|
||||||
}
|
|
||||||
|
|
||||||
return Some((t, u, v));
|
|
||||||
} else {
|
} else {
|
||||||
|
let xi = if zi == 2 { 0 } else { zi + 1 };
|
||||||
|
let yi = if xi == 2 { 0 } else { xi + 1 };
|
||||||
|
(yi, xi)
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = (p2x * p1y) - (p2y * p1x);
|
||||||
|
let mut e1 = (p0x * p2y) - (p0y * p2x);
|
||||||
|
let mut e2 = (p1x * p0y) - (p1y * p0x);
|
||||||
|
|
||||||
|
// Fallback to test against edges using double precision.
|
||||||
|
if e0 == 0.0 || e1 == 0.0 || e2 == 0.0 {
|
||||||
|
let p2xp1y = p2x as f64 * p1y as f64;
|
||||||
|
let p2yp1x = p2y as f64 * p1x as f64;
|
||||||
|
e0 = (p2xp1y - p2yp1x) as f32;
|
||||||
|
let p0xp2y = p0x as f64 * p2y as f64;
|
||||||
|
let p0yp2x = p0y as f64 * p2x as f64;
|
||||||
|
e1 = (p0xp2y - p0yp2x) as f32;
|
||||||
|
let p1xp0y = p1x as f64 * p0y as f64;
|
||||||
|
let p1yp0x = p1y as f64 * p0x as f64;
|
||||||
|
e2 = (p1xp0y - p1yp0x) 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;
|
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 {
|
||||||
|
if t <= 0.0 || t > (ray.max_t * det) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if -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))
|
||||||
}
|
}
|
||||||
|
|
|
@ -92,7 +92,7 @@ impl<'a> Surface for TriangleMesh<'a> {
|
||||||
};
|
};
|
||||||
let mat_inv = mat_space.inverse();
|
let mat_inv = mat_space.inverse();
|
||||||
let tri = (tri.0 * mat_inv, tri.1 * mat_inv, tri.2 * mat_inv);
|
let tri = (tri.0 * mat_inv, tri.1 * mat_inv, tri.2 * mat_inv);
|
||||||
if let Some((t, _, _)) = triangle::intersect_ray(wr, tri) {
|
if let Some((t, _, _, _)) = triangle::intersect_ray(wr, tri) {
|
||||||
if t < r.max_t {
|
if t < r.max_t {
|
||||||
if r.is_occlusion() {
|
if r.is_occlusion() {
|
||||||
isects[r.id as usize] = SurfaceIntersection::Occlude;
|
isects[r.id as usize] = SurfaceIntersection::Occlude;
|
||||||
|
|
Loading…
Reference in New Issue
Block a user