use crate::geometry::{Box2D, Box3D, Point2D, Point3D};
use crate::triangle::{Triangle3, TriangleHit};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Ray2D {
pub origin: Point2D,
pub dir_x: f64,
pub dir_y: f64,
pub(crate) inv_dir_x: f64,
pub(crate) inv_dir_y: f64,
pub max_distance: f64,
}
impl Ray2D {
#[inline]
pub const fn new(origin: Point2D, dir_x: f64, dir_y: f64, max_distance: f64) -> Self {
Self {
origin,
dir_x,
dir_y,
inv_dir_x: 1.0 / dir_x,
inv_dir_y: 1.0 / dir_y,
max_distance,
}
}
#[inline]
#[allow(dead_code)] pub(crate) fn has_zero_direction(self) -> bool {
self.dir_x == 0.0 || self.dir_y == 0.0
}
#[inline]
pub fn intersects_box(self, bounds: Box2D) -> bool {
if self.max_distance < 0.0 || self.max_distance.is_nan() {
return false;
}
let mut t_min: f64 = 0.0;
let mut t_max = self.max_distance;
slab(
self.origin.x,
self.dir_x,
self.inv_dir_x,
bounds.min_x,
bounds.max_x,
&mut t_min,
&mut t_max,
) && slab(
self.origin.y,
self.dir_y,
self.inv_dir_y,
bounds.min_y,
bounds.max_y,
&mut t_min,
&mut t_max,
)
}
#[inline]
pub fn enter_t(self, bounds: Box2D) -> Option<f64> {
if self.max_distance < 0.0 || self.max_distance.is_nan() {
return None;
}
let mut t_min: f64 = 0.0;
let mut t_max = self.max_distance;
let hit = slab(
self.origin.x,
self.dir_x,
self.inv_dir_x,
bounds.min_x,
bounds.max_x,
&mut t_min,
&mut t_max,
) && slab(
self.origin.y,
self.dir_y,
self.inv_dir_y,
bounds.min_y,
bounds.max_y,
&mut t_min,
&mut t_max,
);
hit.then_some(t_min)
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct Ray3D {
pub origin: Point3D,
pub dir_x: f64,
pub dir_y: f64,
pub dir_z: f64,
pub(crate) inv_dir_x: f64,
pub(crate) inv_dir_y: f64,
pub(crate) inv_dir_z: f64,
pub max_distance: f64,
}
impl Ray3D {
#[inline]
pub const fn new(
origin: Point3D,
dir_x: f64,
dir_y: f64,
dir_z: f64,
max_distance: f64,
) -> Self {
Self {
origin,
dir_x,
dir_y,
dir_z,
inv_dir_x: 1.0 / dir_x,
inv_dir_y: 1.0 / dir_y,
inv_dir_z: 1.0 / dir_z,
max_distance,
}
}
#[inline]
#[allow(dead_code)] pub(crate) fn has_zero_direction(self) -> bool {
self.dir_x == 0.0 || self.dir_y == 0.0 || self.dir_z == 0.0
}
#[inline]
pub fn intersects_box(self, bounds: Box3D) -> bool {
if self.max_distance < 0.0 || self.max_distance.is_nan() {
return false;
}
let mut t_min: f64 = 0.0;
let mut t_max = self.max_distance;
slab(
self.origin.x,
self.dir_x,
self.inv_dir_x,
bounds.min_x,
bounds.max_x,
&mut t_min,
&mut t_max,
) && slab(
self.origin.y,
self.dir_y,
self.inv_dir_y,
bounds.min_y,
bounds.max_y,
&mut t_min,
&mut t_max,
) && slab(
self.origin.z,
self.dir_z,
self.inv_dir_z,
bounds.min_z,
bounds.max_z,
&mut t_min,
&mut t_max,
)
}
#[inline]
pub fn enter_t(self, bounds: Box3D) -> Option<f64> {
if self.max_distance < 0.0 || self.max_distance.is_nan() {
return None;
}
let mut t_min: f64 = 0.0;
let mut t_max = self.max_distance;
let hit = slab(
self.origin.x,
self.dir_x,
self.inv_dir_x,
bounds.min_x,
bounds.max_x,
&mut t_min,
&mut t_max,
) && slab(
self.origin.y,
self.dir_y,
self.inv_dir_y,
bounds.min_y,
bounds.max_y,
&mut t_min,
&mut t_max,
) && slab(
self.origin.z,
self.dir_z,
self.inv_dir_z,
bounds.min_z,
bounds.max_z,
&mut t_min,
&mut t_max,
);
hit.then_some(t_min)
}
#[inline]
pub fn closest_triangle<T: Triangle3>(&self, triangles: &[T]) -> Option<TriangleHit> {
let o = [self.origin.x, self.origin.y, self.origin.z];
let d = [self.dir_x, self.dir_y, self.dir_z];
T::closest_hit(o, d, self.max_distance, triangles)
}
}
#[inline]
fn slab(
origin: f64,
direction: f64,
inverse: f64,
min: f64,
max: f64,
t_min: &mut f64,
t_max: &mut f64,
) -> bool {
if direction == 0.0 {
return origin >= min && origin <= max;
}
let mut near = (min - origin) * inverse;
let mut far = (max - origin) * inverse;
if near > far {
core::mem::swap(&mut near, &mut far);
}
*t_min = (*t_min).max(near);
*t_max = (*t_max).min(far);
*t_min <= *t_max
}
#[cfg(test)]
mod triangle_tests {
use super::*;
use crate::{Point3D, Triangle3D};
fn ray(o: [f64; 3], d: [f64; 3]) -> Ray3D {
Ray3D::new(Point3D::new(o[0], o[1], o[2]), d[0], d[1], d[2], 100.0)
}
#[test]
fn closest_triangle_hits_nearest_and_misses() {
let near = Triangle3D::new([0.0, 0.0, 2.0], [2.0, 0.0, 2.0], [0.0, 2.0, 2.0]);
let far = Triangle3D::new([0.0, 0.0, 5.0], [2.0, 0.0, 5.0], [0.0, 2.0, 5.0]);
let tris = [far, near];
let hit = ray([0.25, 0.25, 0.0], [0.0, 0.0, 1.0])
.closest_triangle(&tris)
.unwrap();
assert_eq!(hit.index, 1);
assert!((hit.t - 2.0).abs() < 1e-4, "t={}", hit.t);
assert!(
ray([0.25, 0.25, 0.0], [0.0, 0.0, -1.0])
.closest_triangle(&tris)
.is_none()
);
assert!(
ray([0.25, 0.25, 0.0], [0.0, 0.0, 1.0])
.closest_triangle::<Triangle3D>(&[])
.is_none()
);
}
#[test]
fn closest_triangle_f32_records() {
use crate::Triangle3DF32;
let near = Triangle3DF32::new([0.0, 0.0, 2.0], [2.0, 0.0, 2.0], [0.0, 2.0, 2.0]);
let far = Triangle3DF32::new([0.0, 0.0, 5.0], [2.0, 0.0, 5.0], [0.0, 2.0, 5.0]);
let hit = ray([0.25, 0.25, 0.0], [0.0, 0.0, 1.0])
.closest_triangle(&[far, near])
.unwrap();
assert_eq!(hit.index, 1);
assert!((hit.t - 2.0).abs() < 1e-4, "t={}", hit.t);
}
}