use crate::Scalar;
use crate::ids::{FaceId, VertexId};
use crate::linalg::vec3::{Vec3, add, cross, dot, length, scale, sub};
use crate::storage::MeshStorage;
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct RayHit {
pub position: [Scalar; 3],
pub t: Scalar,
pub face: FaceId,
pub barycentric: (f64, f64),
}
pub fn point_triangle_distance(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> f64 {
let ab = sub(b, a);
let ac = sub(c, a);
let ap = sub(p, a);
let d1 = dot(ab, ap);
let d2 = dot(ac, ap);
if d1 <= 0.0 && d2 <= 0.0 {
return length(ap); }
let bp = sub(p, b);
let d3 = dot(ab, bp);
let d4 = dot(ac, bp);
if d3 >= 0.0 && d4 <= d3 {
return length(bp); }
let vc = d1 * d4 - d3 * d2;
if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
let v = if d1 - d3 == 0.0 { 0.0 } else { d1 / (d1 - d3) };
let closest = add(a, scale(ab, v));
return length(sub(p, closest)); }
let cp = sub(p, c);
let d5 = dot(ab, cp);
let d6 = dot(ac, cp);
if d6 >= 0.0 && d5 <= d6 {
return length(cp); }
let vb = d5 * d2 - d1 * d6;
if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
let w = if d2 - d6 == 0.0 { 0.0 } else { d2 / (d2 - d6) };
let closest = add(a, scale(ac, w));
return length(sub(p, closest)); }
let va = d3 * d6 - d5 * d4;
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
let denom = (d4 - d3) + (d5 - d6);
let w = if denom == 0.0 { 0.0 } else { (d4 - d3) / denom };
let closest = add(b, scale(sub(c, b), w));
return length(sub(p, closest)); }
let denom = va + vb + vc;
if denom.abs() < 1e-20 {
return length(ap).min(length(bp)).min(length(cp));
}
let inv = 1.0 / denom;
let v = vb * inv;
let w = vc * inv;
let closest = add(a, add(scale(ab, v), scale(ac, w)));
length(sub(p, closest))
}
pub fn closest_point_on_triangle(p: Vec3, a: Vec3, b: Vec3, c: Vec3) -> Vec3 {
let ab = sub(b, a);
let ac = sub(c, a);
let ap = sub(p, a);
let d1 = dot(ab, ap);
let d2 = dot(ac, ap);
if d1 <= 0.0 && d2 <= 0.0 {
return a; }
let bp = sub(p, b);
let d3 = dot(ab, bp);
let d4 = dot(ac, bp);
if d3 >= 0.0 && d4 <= d3 {
return b; }
let vc = d1 * d4 - d3 * d2;
if vc <= 0.0 && d1 >= 0.0 && d3 <= 0.0 {
let v = if d1 - d3 == 0.0 { 0.0 } else { d1 / (d1 - d3) };
return add(a, scale(ab, v)); }
let cp = sub(p, c);
let d5 = dot(ab, cp);
let d6 = dot(ac, cp);
if d6 >= 0.0 && d5 <= d6 {
return c; }
let vb = d5 * d2 - d1 * d6;
if vb <= 0.0 && d2 >= 0.0 && d6 <= 0.0 {
let w = if d2 - d6 == 0.0 { 0.0 } else { d2 / (d2 - d6) };
return add(a, scale(ac, w)); }
let va = d3 * d6 - d5 * d4;
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
let denom = (d4 - d3) + (d5 - d6);
let w = if denom == 0.0 { 0.0 } else { (d4 - d3) / denom };
return add(b, scale(sub(c, b), w)); }
let denom = va + vb + vc;
if denom.abs() < 1e-20 {
let la = dot(ap, ap);
let lb = dot(bp, bp);
let lc = dot(cp, cp);
if la <= lb && la <= lc {
return a;
}
if lb <= lc {
return b;
}
return c;
}
let inv = 1.0 / denom;
let v = vb * inv;
let w = vc * inv;
add(a, add(scale(ab, v), scale(ac, w)))
}
pub fn ray_triangle_intersection(
origin: [f64; 3],
direction: [f64; 3],
v0: [f64; 3],
v1: [f64; 3],
v2: [f64; 3],
) -> Option<(f64, f64, f64)> {
use crate::predicates::{orient3d, point_in_triangle_2d};
let a = orient3d(v0, v1, v2, origin);
let end = [
origin[0] + direction[0],
origin[1] + direction[1],
origin[2] + direction[2],
];
let b = orient3d(v0, v1, v2, end);
let denom = b - a;
if denom == 0.0 {
return None; }
let t = -a / denom;
if t <= 1e-12 {
return None; }
let p = [
origin[0] + t * direction[0],
origin[1] + t * direction[1],
origin[2] + t * direction[2],
];
let e1 = sub(v1, v0);
let e2 = sub(v2, v0);
let normal = cross(e1, e2);
let abs_n = [normal[0].abs(), normal[1].abs(), normal[2].abs()];
let drop_axis = if abs_n[0] >= abs_n[1] && abs_n[0] >= abs_n[2] {
0
} else if abs_n[1] >= abs_n[2] {
1
} else {
2
};
let project = |q: [f64; 3]| -> [f64; 2] {
match drop_axis {
0 => [q[1], q[2]],
1 => [q[0], q[2]],
_ => [q[0], q[1]],
}
};
let p2d = project(p);
let a2d = project(v0);
let b2d = project(v1);
let c2d = project(v2);
if !point_in_triangle_2d(p2d, a2d, b2d, c2d) {
return None;
}
let ab = [b2d[0] - a2d[0], b2d[1] - a2d[1]];
let ac = [c2d[0] - a2d[0], c2d[1] - a2d[1]];
let ap = [p2d[0] - a2d[0], p2d[1] - a2d[1]];
let denom = ab[0] * ac[1] - ab[1] * ac[0];
if denom == 0.0 {
return None; }
let inv_denom = 1.0 / denom;
let u = (ap[0] * ac[1] - ap[1] * ac[0]) * inv_denom;
let v = (ab[0] * ap[1] - ab[1] * ap[0]) * inv_denom;
Some((t, u, v))
}
pub fn ray_mesh_intersection(
origin: [f64; 3],
direction: [f64; 3],
mesh: &MeshStorage,
) -> Option<RayHit> {
let mut best: Option<RayHit> = None;
for f in mesh.face_ids() {
let verts: Vec<VertexId> = crate::traversal::FaceVertices::new(mesh, f).collect();
if verts.len() != 3 {
continue;
}
let v0 = mesh.get_vertex(verts[0])?.position;
let v1 = mesh.get_vertex(verts[1])?.position;
let v2 = mesh.get_vertex(verts[2])?.position;
if let Some((t, u, v)) = ray_triangle_intersection(origin, direction, v0, v1, v2) {
let hit = RayHit {
position: [
origin[0] + t * direction[0],
origin[1] + t * direction[1],
origin[2] + t * direction[2],
],
t,
face: f,
barycentric: (u, v),
};
match best {
Some(ref b) if hit.t < b.t => best = Some(hit),
None => best = Some(hit),
_ => {}
}
}
}
best
}
pub fn ray_mesh_intersects(origin: [f64; 3], direction: [f64; 3], mesh: &MeshStorage) -> bool {
let mut count = 0u32;
for f in mesh.face_ids() {
let verts: Vec<VertexId> = crate::traversal::FaceVertices::new(mesh, f).collect();
if verts.len() != 3 {
continue;
}
let (v0, v1, v2) = match (
mesh.get_vertex(verts[0]),
mesh.get_vertex(verts[1]),
mesh.get_vertex(verts[2]),
) {
(Some(a), Some(b), Some(c)) => (a.position, b.position, c.position),
_ => continue,
};
if ray_triangle_intersection(origin, direction, v0, v1, v2).is_some() {
count += 1;
}
}
count % 2 == 1
}
pub fn ray_mesh_intersection_par(
mesh: &MeshStorage,
origin: [f64; 3],
direction: [f64; 3],
) -> Vec<RayHit> {
use rayon::prelude::*;
let faces: Vec<FaceId> = mesh.face_ids().collect();
faces
.par_iter()
.filter_map(|&f| {
let verts: Vec<VertexId> = crate::traversal::FaceVertices::new(mesh, f).collect();
if verts.len() != 3 {
return None;
}
let v0 = mesh.get_vertex(verts[0])?.position;
let v1 = mesh.get_vertex(verts[1])?.position;
let v2 = mesh.get_vertex(verts[2])?.position;
let (t, u, v) = ray_triangle_intersection(origin, direction, v0, v1, v2)?;
let position = [
origin[0] + direction[0] * t,
origin[1] + direction[1] * t,
origin[2] + direction[2] * t,
];
Some(RayHit {
position,
t,
face: f,
barycentric: (u, v),
})
})
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn point_triangle_distance_point_on_face() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [0.0, 1.0, 0.0];
let p = [1.0 / 3.0, 1.0 / 3.0, 0.0];
assert!(point_triangle_distance(p, a, b, c).abs() < 1e-9);
}
#[test]
fn point_triangle_distance_above_face() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [0.0, 1.0, 0.0];
let p = [0.2, 0.2, 1.0];
assert!((point_triangle_distance(p, a, b, c) - 1.0).abs() < 1e-9);
}
#[test]
fn point_triangle_distance_vertex_region() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [0.0, 1.0, 0.0];
let p = [-1.0, -1.0, 0.0];
assert!((point_triangle_distance(p, a, b, c) - 2.0_f64.sqrt()).abs() < 1e-9);
}
#[test]
fn point_triangle_distance_edge_region() {
let a = [0.0, 0.0, 0.0];
let b = [1.0, 0.0, 0.0];
let c = [0.0, 1.0, 0.0];
let p = [2.0, 0.0, 0.0];
assert!((point_triangle_distance(p, a, b, c) - 1.0).abs() < 1e-9);
}
#[test]
fn ray_triangle_hit() {
let v0 = [0.0, 0.0, 0.0];
let v1 = [1.0, 0.0, 0.0];
let v2 = [0.0, 1.0, 0.0];
let hit = ray_triangle_intersection([0.25, 0.25, 1.0], [0.0, 0.0, -1.0], v0, v1, v2);
assert!(hit.is_some());
let (t, u, v) = hit.unwrap();
assert!((t - 1.0).abs() < 1e-10);
assert!((u - 0.25).abs() < 1e-10);
assert!((v - 0.25).abs() < 1e-10);
}
#[test]
fn ray_triangle_miss_parallel() {
let v0 = [0.0, 0.0, 0.0];
let v1 = [1.0, 0.0, 0.0];
let v2 = [0.0, 1.0, 0.0];
assert!(ray_triangle_intersection([0.0, 0.0, 1.0], [1.0, 0.0, 0.0], v0, v1, v2).is_none());
}
#[test]
fn test_ray_mesh_intersects() {
let mesh = crate::test_util::build_icosphere(2);
let hits = ray_mesh_intersects([2.0, 0.0, 0.0], [-1.0, 0.0, 0.0], &mesh);
let _ = hits;
}
#[test]
fn test_ray_mesh_intersection_icosphere() {
let mesh = crate::test_util::build_icosphere(2);
let hit = ray_mesh_intersection([2.0, 0.0, 0.0], [-1.0, 0.0, 0.0], &mesh);
assert!(hit.is_some());
let h = hit.unwrap();
assert!((h.position[0] - 1.0).abs() < 0.1);
assert!(h.t > 0.0 && h.t < 2.0);
}
#[test]
fn test_ray_mesh_intersection_miss() {
let mesh = crate::test_util::build_icosphere(2);
assert!(ray_mesh_intersection([3.0, 0.0, 0.0], [1.0, 0.0, 0.0], &mesh).is_none());
}
#[test]
fn ray_mesh_intersects_empty_mesh_returns_false() {
let mesh = MeshStorage::new();
assert!(!ray_mesh_intersects(
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
&mesh
));
}
#[test]
fn ray_mesh_intersects_origin_outside_returns_false() {
let mesh = crate::test_util::build_icosphere(1);
let result = ray_mesh_intersects([0.0, 0.0, -5.0], [0.0, 0.0, 1.0], &mesh);
assert!(!result, "原点在球外,奇偶判定应返回 false");
}
#[test]
fn ray_mesh_intersects_misses_returns_false() {
let mesh = crate::test_util::build_icosphere(1);
let result = ray_mesh_intersects([10.0, 10.0, 10.0], [1.0, 0.0, 0.0], &mesh);
assert!(!result, "射线不与球面相交,应返回 false");
}
#[test]
fn ray_mesh_intersection_par_matches_serial() {
let mesh = crate::test_util::build_icosphere(1);
let origin = [2.0, 0.0, 0.0];
let direction = [-1.0, 0.0, 0.0];
let serial = ray_mesh_intersection(origin, direction, &mesh);
let par = ray_mesh_intersection_par(&mesh, origin, direction);
match serial {
Some(s_hit) => {
assert!(!par.is_empty(), "par 应至少有一个交点");
let min_par = par
.iter()
.min_by(|a, b| a.t.partial_cmp(&b.t).unwrap())
.unwrap();
assert!(
(s_hit.t - min_par.t).abs() < 1e-10,
"t 不一致: serial={} par={}",
s_hit.t,
min_par.t
);
}
None => {
assert!(par.is_empty(), "serial 无交点时 par 应为空");
}
}
}
}