#![allow(dead_code)]
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
pub struct Ray {
pub origin: [f32; 3],
pub direction: [f32; 3],
}
#[allow(dead_code)]
#[derive(Debug, Clone, Copy)]
pub struct RayHit {
pub t: f32,
pub u: f32,
pub v: f32,
pub triangle: usize,
}
#[inline]
fn dot(a: [f32; 3], b: [f32; 3]) -> f32 {
a[0] * b[0] + a[1] * b[1] + a[2] * b[2]
}
#[inline]
fn cross(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[
a[1] * b[2] - a[2] * b[1],
a[2] * b[0] - a[0] * b[2],
a[0] * b[1] - a[1] * b[0],
]
}
#[inline]
fn sub(a: [f32; 3], b: [f32; 3]) -> [f32; 3] {
[a[0] - b[0], a[1] - b[1], a[2] - b[2]]
}
#[allow(dead_code)]
pub fn ray_triangle(
ray: &Ray,
v0: [f32; 3],
v1: [f32; 3],
v2: [f32; 3],
triangle: usize,
) -> Option<RayHit> {
let edge1 = sub(v1, v0);
let edge2 = sub(v2, v0);
let h = cross(ray.direction, edge2);
let det = dot(edge1, h);
if det.abs() < 1e-8 {
return None; }
let inv_det = 1.0 / det;
let s = sub(ray.origin, v0);
let u = dot(s, h) * inv_det;
if !(0.0..=1.0).contains(&u) {
return None;
}
let q = cross(s, edge1);
let v = dot(ray.direction, q) * inv_det;
if v < 0.0 || u + v > 1.0 {
return None;
}
let t = dot(edge2, q) * inv_det;
if t < 1e-8 {
return None;
}
Some(RayHit { t, u, v, triangle })
}
#[allow(dead_code)]
pub fn raycast(ray: &Ray, positions: &[[f32; 3]], indices: &[u32]) -> Option<RayHit> {
let tri_count = indices.len() / 3;
let mut closest: Option<RayHit> = None;
for t in 0..tri_count {
let ia = indices[t * 3] as usize;
let ib = indices[t * 3 + 1] as usize;
let ic = indices[t * 3 + 2] as usize;
if ia >= positions.len() || ib >= positions.len() || ic >= positions.len() {
continue;
}
if let Some(hit) = ray_triangle(ray, positions[ia], positions[ib], positions[ic], t) {
if closest.is_none_or(|c: RayHit| hit.t < c.t) {
closest = Some(hit);
}
}
}
closest
}
#[allow(dead_code)]
pub fn raycast_all(ray: &Ray, positions: &[[f32; 3]], indices: &[u32]) -> Vec<RayHit> {
let tri_count = indices.len() / 3;
let mut hits = Vec::new();
for t in 0..tri_count {
let ia = indices[t * 3] as usize;
let ib = indices[t * 3 + 1] as usize;
let ic = indices[t * 3 + 2] as usize;
if ia >= positions.len() || ib >= positions.len() || ic >= positions.len() {
continue;
}
if let Some(hit) = ray_triangle(ray, positions[ia], positions[ib], positions[ic], t) {
hits.push(hit);
}
}
hits
}
#[allow(dead_code)]
pub fn hit_point(ray: &Ray, hit: &RayHit) -> [f32; 3] {
let d = ray.direction;
let o = ray.origin;
[
o[0] + d[0] * hit.t,
o[1] + d[1] * hit.t,
o[2] + d[2] * hit.t,
]
}
#[allow(dead_code)]
pub fn ray_direction(from: [f32; 3], to: [f32; 3]) -> [f32; 3] {
let d = sub(to, from);
let len = dot(d, d).sqrt().max(f32::EPSILON);
[d[0] / len, d[1] / len, d[2] / len]
}
#[allow(dead_code)]
pub fn hit_to_json(hit: &RayHit) -> String {
format!(
"{{\"t\":{:.6},\"u\":{:.6},\"v\":{:.6},\"triangle\":{}}}",
hit.t, hit.u, hit.v, hit.triangle
)
}
#[cfg(test)]
mod tests {
use super::*;
fn unit_ray() -> Ray {
Ray {
origin: [0.0, 0.0, -5.0],
direction: [0.0, 0.0, 1.0],
}
}
fn front_tri() -> ([f32; 3], [f32; 3], [f32; 3]) {
([-1.0, -1.0, 0.0], [1.0, -1.0, 0.0], [0.0, 1.0, 0.0])
}
#[test]
fn test_hit_front_face() {
let ray = unit_ray();
let (v0, v1, v2) = front_tri();
let hit = ray_triangle(&ray, v0, v1, v2, 0);
assert!(hit.is_some());
}
#[test]
fn test_miss_parallel() {
let ray = Ray {
origin: [0.0, 0.0, 0.0],
direction: [1.0, 0.0, 0.0],
};
let (v0, v1, v2) = front_tri();
let hit = ray_triangle(&ray, v0, v1, v2, 0);
assert!(hit.is_none());
}
#[test]
fn test_t_value() {
let ray = unit_ray();
let (v0, v1, v2) = front_tri();
let hit = ray_triangle(&ray, v0, v1, v2, 0).expect("should succeed");
assert!((hit.t - 5.0).abs() < 1e-4);
}
#[test]
fn test_uv_valid() {
let ray = unit_ray();
let (v0, v1, v2) = front_tri();
let hit = ray_triangle(&ray, v0, v1, v2, 0).expect("should succeed");
assert!(hit.u >= 0.0 && hit.v >= 0.0 && hit.u + hit.v <= 1.0);
}
#[test]
fn test_raycast_finds_hit() {
let positions = vec![[-1.0, -1.0, 0.0f32], [1.0, -1.0, 0.0], [0.0, 1.0, 0.0]];
let indices = vec![0u32, 1, 2];
let ray = unit_ray();
let hit = raycast(&ray, &positions, &indices);
assert!(hit.is_some());
}
#[test]
fn test_raycast_all_count() {
let positions = vec![[-1.0, -1.0, 0.0f32], [1.0, -1.0, 0.0], [0.0, 1.0, 0.0]];
let indices = vec![0u32, 1, 2];
let ray = unit_ray();
let hits = raycast_all(&ray, &positions, &indices);
assert_eq!(hits.len(), 1);
}
#[test]
fn test_hit_point() {
let ray = unit_ray();
let (v0, v1, v2) = front_tri();
let hit = ray_triangle(&ray, v0, v1, v2, 0).expect("should succeed");
let pt = hit_point(&ray, &hit);
assert!((pt[2]).abs() < 1e-4);
}
#[test]
fn test_ray_direction() {
let d = ray_direction([0.0, 0.0, 0.0], [0.0, 0.0, 5.0]);
assert!((d[2] - 1.0).abs() < 1e-5);
}
#[test]
fn test_hit_to_json() {
let hit = RayHit {
t: 5.0,
u: 0.3,
v: 0.3,
triangle: 0,
};
let json = hit_to_json(&hit);
assert!(json.contains("triangle"));
}
#[test]
fn test_behind_ray_miss() {
let ray = Ray {
origin: [0.0, 0.0, 5.0],
direction: [0.0, 0.0, 1.0],
};
let (v0, v1, v2) = front_tri();
let hit = ray_triangle(&ray, v0, v1, v2, 0);
assert!(hit.is_none());
}
}