//! 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, b: &Point3, c: &Point3, d: &Point3) -> 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, b: &Point3, c: &Point3, d: &Point3, e: &Point3, ) -> 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, b: &Point3, c: &Point3, d: &Point3, ) -> Option> { 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, b: &Point3, c: &Point3, d: &Point3, ) -> Option { 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, ¢er); 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); } }