Files
rustytorch/crates/specialized/rtx-mesh-gen/src/delaunay/predicates.rs
T
2026-03-04 00:08:42 +00:00

181 lines
5.1 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//! Geometric predicates for Delaunay triangulation.
//!
//! These predicates determine orientation and circumsphere tests.
//! For production use, these should be replaced with adaptive-precision
//! or exact arithmetic implementations (e.g., from the `robust` crate).
use nalgebra::Point3;
/// Compute the orientation of four points in 3D.
///
/// Returns:
/// - Positive if d is on the positive side of the plane defined by (a,b,c)
/// - Negative if on the negative side
/// - Zero if coplanar
pub fn orient3d(a: &Point3<f64>, b: &Point3<f64>, c: &Point3<f64>, d: &Point3<f64>) -> f64 {
// Standard orient3d using the signed volume of the tetrahedron
// Positive when d is above the plane (a,b,c) with ccw orientation
let ab = b - a;
let ac = c - a;
let ad = d - a;
// det = ab · (ac × ad)
ab.dot(&ac.cross(&ad))
}
/// Test if point e is inside the circumsphere of tetrahedron (a,b,c,d).
///
/// Returns:
/// - Positive if e is inside the circumsphere
/// - Negative if outside
/// - Zero if on the sphere
///
/// Assumes (a,b,c,d) has positive orientation (orient3d > 0).
pub fn in_circumsphere(
a: &Point3<f64>,
b: &Point3<f64>,
c: &Point3<f64>,
d: &Point3<f64>,
e: &Point3<f64>,
) -> f64 {
// InSphere determinant test
// If orient3d(a,b,c,d) > 0, then insphere(a,b,c,d,e) > 0 means e is inside
let aex = a.x - e.x;
let aey = a.y - e.y;
let aez = a.z - e.z;
let bex = b.x - e.x;
let bey = b.y - e.y;
let bez = b.z - e.z;
let cex = c.x - e.x;
let cey = c.y - e.y;
let cez = c.z - e.z;
let dex = d.x - e.x;
let dey = d.y - e.y;
let dez = d.z - e.z;
let ae_sq = aex * aex + aey * aey + aez * aez;
let be_sq = bex * bex + bey * bey + bez * bez;
let ce_sq = cex * cex + cey * cey + cez * cez;
let de_sq = dex * dex + dey * dey + dez * dez;
// 4x4 determinant using cofactor expansion
let ab = aex * bey - bex * aey;
let bc = bex * cey - cex * bey;
let cd = cex * dey - dex * cey;
let da = dex * aey - aex * dey;
let ac = aex * cey - cex * aey;
let bd = bex * dey - dex * bey;
let abc = aez * bc - bez * ac + cez * ab;
let bcd = bez * cd - cez * bd + dez * bc;
let cda = cez * da + dez * ac + aez * cd;
let dab = dez * ab + aez * bd + bez * da;
ae_sq * bcd - be_sq * cda + ce_sq * dab - de_sq * abc
}
/// Compute the circumcenter of a tetrahedron.
pub fn circumcenter(
a: &Point3<f64>,
b: &Point3<f64>,
c: &Point3<f64>,
d: &Point3<f64>,
) -> Option<Point3<f64>> {
let ba = b - a;
let ca = c - a;
let da = d - a;
let ba_sq = ba.norm_squared();
let ca_sq = ca.norm_squared();
let da_sq = da.norm_squared();
// Determinant (denominator)
let denom = 2.0 * ba.dot(&ca.cross(&da));
if denom.abs() < 1e-15 {
return None; // Degenerate tetrahedron
}
// Circumcenter = a + (ba² * (ca × da) + ca² * (da × ba) + da² * (ba × ca)) / denom
let cc = a + (ca.cross(&da) * ba_sq + da.cross(&ba) * ca_sq + ba.cross(&ca) * da_sq) / denom;
Some(cc)
}
/// Compute the circumradius of a tetrahedron.
pub fn circumradius(
a: &Point3<f64>,
b: &Point3<f64>,
c: &Point3<f64>,
d: &Point3<f64>,
) -> Option<f64> {
let cc = circumcenter(a, b, c, d)?;
Some((a - cc).norm())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_orient3d() {
let a = Point3::new(0.0, 0.0, 0.0);
let b = Point3::new(1.0, 0.0, 0.0);
let c = Point3::new(0.0, 1.0, 0.0);
let d = Point3::new(0.0, 0.0, 1.0);
// d is above the plane (a,b,c)
let o = orient3d(&a, &b, &c, &d);
assert!(o > 0.0);
// Flip d below
let d_below = Point3::new(0.0, 0.0, -1.0);
let o2 = orient3d(&a, &b, &c, &d_below);
assert!(o2 < 0.0);
}
#[test]
fn test_in_circumsphere() {
// Regular tetrahedron
let a = Point3::new(1.0, 1.0, 1.0);
let b = Point3::new(-1.0, -1.0, 1.0);
let c = Point3::new(-1.0, 1.0, -1.0);
let d = Point3::new(1.0, -1.0, -1.0);
// Center should be inside
let center = Point3::new(0.0, 0.0, 0.0);
let inside = in_circumsphere(&a, &b, &c, &d, &center);
assert!(inside > 0.0);
// Far point should be outside
let far = Point3::new(10.0, 10.0, 10.0);
let outside = in_circumsphere(&a, &b, &c, &d, &far);
assert!(outside < 0.0);
}
#[test]
fn test_circumcenter() {
// Regular tetrahedron centered at origin
let a = Point3::new(1.0, 1.0, 1.0);
let b = Point3::new(-1.0, -1.0, 1.0);
let c = Point3::new(-1.0, 1.0, -1.0);
let d = Point3::new(1.0, -1.0, -1.0);
let cc = circumcenter(&a, &b, &c, &d).unwrap();
// Center should be near origin
assert!(cc.coords.norm() < 0.01);
// All vertices should be equidistant from circumcenter
let ra = (a - cc).norm();
let rb = (b - cc).norm();
let rc = (c - cc).norm();
let rd = (d - cc).norm();
assert!((ra - rb).abs() < 1e-10);
assert!((ra - rc).abs() < 1e-10);
assert!((ra - rd).abs() < 1e-10);
}
}