//! Geometry the hex meshers place nodes on: the analytic surface of a B-rep face (exact closest //! point), the triangles of the boundary surface (closest point through a bounding-volume tree, //! for free-form faces), and the inside test by the generalized winding number. //! //! Closest points on the elementary surfaces are closed form. The point-to-triangle distance is //! the Voronoi-region method of C. Ericson, *Real-Time Collision Detection*, Morgan Kaufmann 2005, //! §5.1.5. The winding number is the sum of the solid angles the triangles subtend (A. Van //! Oosterom, J. Strackee, *The solid angle of a plane triangle*, IEEE Trans. Biomed. Eng. //! 30(2):125–126, 1983; A. Jacobson, L. Kavan, O. Sorkine-Hornung, *Robust inside-outside //! segmentation using generalized winding numbers*, ACM TOG 32(4):33, 2013). //! //! These are floating-point measures for placing nodes and choosing tags; no topological decision //! of the meshers rests on a bare sign here except the inside test, which is only asked at points //! far from the boundary (region centres), where the winding number is close to 0 or 1. use crate::surface::BoundarySurface; use crate::vec3::{P, cross, dot, norm, sub}; use crate::{Error, Result}; pub(crate) fn add(a: P, b: P) -> P { [a[0] + b[0], a[1] + b[1], a[2] + b[2]] } pub(crate) fn scale(a: P, s: f64) -> P { [a[0] * s, a[1] * s, a[2] * s] } pub(crate) fn dist(a: P, b: P) -> f64 { norm(sub(a, b)) } /// The unit vector along `a`, or `None` when it has no direction. pub(crate) fn unit(a: P) -> Option
{
let n = norm(a);
(n > 0.0 && n.is_finite()).then(|| scale(a, 1.0 / n))
}
/// The analytic surface a B-rep face lies on. Directions need not be unit length; they are
/// normalized when the surface is checked ([`Surface::checked`]). Lengths are model units and
/// angles radians.
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Surface {
Plane {
point: [f64; 3],
normal: [f64; 3],
},
Cylinder {
origin: [f64; 3],
axis: [f64; 3],
radius: f64,
},
/// The apex, the axis pointing into the cone, and the half-angle (0, π/2).
Cone {
apex: [f64; 3],
axis: [f64; 3],
half_angle: f64,
},
Sphere {
center: [f64; 3],
radius: f64,
},
/// The centre of the ring, its axis, the ring (major) and tube (minor) radii.
Torus {
center: [f64; 3],
axis: [f64; 3],
major: f64,
minor: f64,
},
}
impl Surface {
/// The surface with unit directions, or `None` when a number is not finite or a direction or
/// radius is degenerate (such a face is treated as free-form).
pub(crate) fn checked(&self) -> Option {
let q = match *self {
Surface::Plane { point, normal } => sub(p, scale(normal, dot(sub(p, point), normal))),
Surface::Cylinder { origin, axis, radius } => {
let w = sub(p, origin);
let h = dot(w, axis);
let radial = unit(sub(w, scale(axis, h)))?;
add(add(origin, scale(axis, h)), scale(radial, radius))
}
Surface::Cone { apex, axis, half_angle } => {
let w = sub(p, apex);
let h = dot(w, axis);
let radial = unit(sub(w, scale(axis, h)))?;
let rho = norm(sub(w, scale(axis, h)));
let (c, s) = (libm::cos(half_angle), libm::sin(half_angle));
let t = (h * c + rho * s).max(0.0);
add(apex, add(scale(axis, t * c), scale(radial, t * s)))
}
Surface::Sphere { center, radius } => add(center, scale(unit(sub(p, center))?, radius)),
Surface::Torus { center, axis, major, minor } => {
let w = sub(p, center);
let h = dot(w, axis);
let ring = add(center, scale(unit(sub(w, scale(axis, h)))?, major));
add(ring, scale(unit(sub(p, ring))?, minor))
}
};
q.iter().all(|x| x.is_finite()).then_some(q)
}
/// The unit normal of the surface at its point closest to `p`, up to sign, or `None` where
/// the closest point is not unique.
pub(crate) fn normal(&self, p: P) -> Option {
match *self {
Surface::Plane { normal, .. } => unit(normal),
Surface::Cylinder { origin, axis, .. } => {
let w = sub(p, origin);
unit(sub(w, scale(axis, dot(w, axis))))
}
Surface::Cone { apex, axis, half_angle } => {
let w = sub(p, apex);
let radial = unit(sub(w, scale(axis, dot(w, axis))))?;
unit(sub(scale(radial, libm::cos(half_angle)), scale(axis, libm::sin(half_angle))))
}
Surface::Sphere { center, .. } => unit(sub(p, center)),
Surface::Torus { center, axis, major, .. } => {
let w = sub(p, center);
let ring = add(center, scale(unit(sub(w, scale(axis, dot(w, axis))))?, major));
unit(sub(p, ring))
}
}
}
}
/// The closest point to `p` on triangle `abc`.
pub(crate) fn closest_on_triangle(p: P, a: P, b: P, c: P) -> P {
let (ab, ac, ap) = (sub(b, a), sub(c, a), sub(p, a));
let (d1, d2) = (dot(ab, ap), dot(ac, ap));
if d1 <= 0.0 && d2 <= 0.0 {
return a;
}
let bp = sub(p, b);
let (d3, d4) = (dot(ab, bp), 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 {
return add(a, scale(ab, d1 / (d1 - d3)));
}
let cp = sub(p, c);
let (d5, d6) = (dot(ab, cp), 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 {
return add(a, scale(ac, d2 / (d2 - d6)));
}
let va = d3 * d6 - d5 * d4;
if va <= 0.0 && (d4 - d3) >= 0.0 && (d5 - d6) >= 0.0 {
return add(b, scale(sub(c, b), (d4 - d3) / ((d4 - d3) + (d5 - d6))));
}
let den = va + vb + vc;
if !(den.abs() > 0.0) {
return a;
}
add(a, add(scale(ab, vb / den), scale(ac, vc / den)))
}
#[derive(Debug, Clone, Copy)]
struct Bounds {
lo: P,
hi: P,
}
impl Bounds {
const EMPTY: Bounds = Bounds { lo: [f64::INFINITY; 3], hi: [f64::NEG_INFINITY; 3] };
fn add(&mut self, p: P) {
for ((l, h), x) in self.lo.iter_mut().zip(self.hi.iter_mut()).zip(p) {
*l = l.min(x);
*h = h.max(x);
}
}
/// Squared distance from `p` to the box (0 inside).
fn dist2(&self, p: P) -> f64 {
(0..3).map(|i| (self.lo[i] - p[i]).max(0.0).max(p[i] - self.hi[i]).powi(2)).sum()
}
}
enum Node {
Leaf { bounds: Bounds, first: usize, count: usize },
Inner { bounds: Bounds, left: usize, right: usize },
}
impl Node {
fn bounds(&self) -> &Bounds {
match self {
Node::Leaf { bounds, .. } | Node::Inner { bounds, .. } => bounds,
}
}
}
const LEAF_SIZE: usize = 8;
/// Nodes a closest-point query may visit before it settles for the best triangle found.
const MAX_QUERY_VISITS: usize = 1 << 20;
/// A bounding-volume tree over the boundary surface's triangles (median splits along the longest
/// axis), for closest-point queries restricted to a set of B-rep faces.
pub(crate) struct TriangleTree<'a> {
surface: &'a BoundarySurface,
order: Vec ,
}
/// The closest triangle found by [`TriangleTree::closest`].
#[derive(Debug, Clone, Copy)]
pub(crate) struct Hit {
pub triangle: u32,
pub point: P,
pub dist: f64,
}
impl<'a> TriangleTree<'a> {
pub(crate) fn new(surface: &'a BoundarySurface) -> Result