use phys_geom::math::Real;
use phys_geom::shape::Triangle;
use crate::{Raycast, RaycastHitResult};
impl Raycast for Triangle {
fn raycast(
&self,
local_ray: phys_geom::Ray,
max_distance: Real,
discard_inside_hit: bool,
) -> Option<RaycastHitResult> {
let edge1 = self.b - self.a;
let edge2 = self.c - self.a;
let h = local_ray.direction.into_inner().cross(&edge2);
let a = edge1.dot(&h);
if a > -Real::EPSILON && a < Real::EPSILON {
return None;
}
if a < 0.0 {
return None;
}
let f = 1.0 / a;
let s = local_ray.origin - self.a;
let u = f * s.dot(&h);
if !(0.0..=1.0).contains(&u) {
return None;
}
let q = s.cross(&edge1);
let v = f * local_ray.direction.into_inner().dot(&q);
if v < 0.0 || u + v > 1.0 {
return None;
}
let t = f * edge2.dot(&q);
if t > Real::EPSILON {
if t > max_distance {
return None;
}
if t < Real::EPSILON * 100.0 {
if discard_inside_hit {
return None;
}
return Some(RaycastHitResult {
distance: 0.0,
normal: -self.normal(), });
}
return Some(RaycastHitResult {
distance: t,
normal: -self.normal(), });
}
None
}
}
#[cfg(test)]
mod tests {
use approx::assert_relative_eq;
use phys_geom::math::*;
use super::*;
#[test]
fn test_raycast_triangle_basic() {
let triangle = Triangle::new(
Point3::new(-1.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
);
let ray = phys_geom::Ray::new(
Point3::new(0.0, 0.5, 2.0),
UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
);
let hit = triangle.raycast(ray, 10.0, false).unwrap();
assert_relative_eq!(hit.distance, 2.0);
assert_relative_eq!(
hit.normal,
UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0))
);
}
#[test]
fn test_raycast_triangle_from_below() {
let triangle = Triangle::new(
Point3::new(-1.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
);
let ray = phys_geom::Ray::new(
Point3::new(0.0, 0.5, -2.0),
UnitVec3::new_normalize(Vec3::new(0.0, 0.0, 1.0)),
);
assert_eq!(triangle.raycast(ray, 10.0, false), None); }
#[test]
fn test_raycast_triangle_miss() {
let triangle = Triangle::new(
Point3::new(-1.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
);
let ray = phys_geom::Ray::new(
Point3::new(2.0, 2.0, 2.0),
UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
);
assert_eq!(triangle.raycast(ray, 10.0, false), None);
}
#[test]
fn test_raycast_triangle_parallel() {
let triangle = Triangle::new(
Point3::new(-1.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
);
let ray = phys_geom::Ray::new(
Point3::new(0.0, 0.5, 2.0),
UnitVec3::new_normalize(Vec3::new(1.0, 0.0, 0.0)),
);
assert_eq!(triangle.raycast(ray, 10.0, false), None);
}
#[test]
fn test_raycast_triangle_max_distance() {
let triangle = Triangle::new(
Point3::new(-1.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
);
let ray = phys_geom::Ray::new(
Point3::new(0.0, 0.5, 5.0),
UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
);
assert_eq!(triangle.raycast(ray, 1.0, false), None);
let hit = triangle.raycast(ray, 10.0, false).unwrap();
assert_relative_eq!(hit.distance, 5.0);
}
#[test]
fn test_raycast_triangle_tilted() {
let triangle = Triangle::new(
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 1.0),
Point3::new(0.0, 1.0, 1.0),
);
let ray = phys_geom::Ray::new(
Point3::new(0.2, 0.3, 2.0),
UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
);
let hit = triangle.raycast(ray, 10.0, false).unwrap();
assert!(hit.distance > 0.0 && hit.distance < 2.0);
assert!(hit.normal.y > 0.0);
assert!(hit.normal.z < 0.0);
}
#[test]
fn test_raycast_triangle_edge_case() {
let triangle = Triangle::new(
Point3::new(0.0, 0.0, 0.0),
Point3::new(1.0, 0.0, 0.0),
Point3::new(0.0, 1.0, 0.0),
);
let ray = phys_geom::Ray::new(
Point3::new(0.0, 0.0, 1.0),
UnitVec3::new_normalize(Vec3::new(0.0, 0.0, -1.0)),
);
let hit = triangle.raycast(ray, 10.0, false).unwrap();
assert_relative_eq!(hit.distance, 1.0);
}
}