use crate::topology::FaceRecord;
use crate::Vec3;
use std::cell::Cell;
use std::sync::atomic::{AtomicU64, Ordering};
thread_local! {
static ACTIVE: Cell<bool> = const { Cell::new(false) };
}
pub(crate) fn enabled() -> bool {
crate::switch!(live "BREP_SOUNDNESS_SCAN_MESH", true)
}
pub(crate) struct ScanMeshScope {
previous: bool,
}
impl ScanMeshScope {
pub(crate) fn open() -> Self {
Self::set(enabled())
}
pub(super) fn set(on: bool) -> Self {
Self { previous: ACTIVE.with(|cell| cell.replace(on)) }
}
}
impl Drop for ScanMeshScope {
fn drop(&mut self) {
ACTIVE.with(|cell| cell.set(self.previous));
}
}
pub(super) fn active() -> bool {
ACTIVE.with(|cell| cell.get())
}
static BOUNDED: AtomicU64 = AtomicU64::new(0);
static FALLBACKS: AtomicU64 = AtomicU64::new(0);
pub(super) fn count(bounded: bool) {
if bounded { &BOUNDED } else { &FALLBACKS }.fetch_add(1, Ordering::Relaxed);
}
pub(crate) fn take_counts() -> (u64, u64) {
(BOUNDED.swap(0, Ordering::Relaxed), FALLBACKS.swap(0, Ordering::Relaxed))
}
#[derive(Clone, Debug)]
pub(super) enum FaceBound {
Plane,
Sphere { centre: Vec3, radius: f64 },
Ruled { origin: Vec3, axis: Vec3, rho0: f64, rho1: f64, height: f64 },
Affine { sphere: crate::affine_sphere::AffineSphere, largest_semi_axis: f64 },
}
pub(super) fn face_bound(face: &FaceRecord) -> Option<FaceBound> {
if !active() {
return None;
}
use crate::AnalyticSurface;
match face.surface.analytic() {
Some(AnalyticSurface::Plane { .. }) => return Some(FaceBound::Plane),
Some(AnalyticSurface::Sphere { frame, radius }) => {
return (radius.is_finite() && *radius > 0.0).then_some(FaceBound::Sphere { centre: frame.origin, radius: *radius });
}
Some(AnalyticSurface::RuledRevolution { frame, rho0, rho1, height }) => {
return (rho0.is_finite() && rho1.is_finite() && height.is_finite() && *height > 0.0).then_some(FaceBound::Ruled {
origin: frame.origin,
axis: frame.axis,
rho0: *rho0,
rho1: *rho1,
height: *height,
});
}
_ => {}
}
let affine = face.surface.affine_sphere()?;
let largest = affine.semi_axes()[0];
if !(largest.is_finite() && largest > 0.0) {
return None;
}
Some(FaceBound::Affine { sphere: affine.clone(), largest_semi_axis: largest })
}
fn pad(scale: f64) -> f64 {
256.0 * f64::EPSILON * scale.max(1.0)
}
pub(super) fn reaches_apex(bound: &FaceBound, positions: impl Iterator<Item = Vec3>, margin: f64) -> bool {
let FaceBound::Ruled { origin, axis, rho0, rho1, height } = *bound else {
return false;
};
if rho0 == rho1 {
return false;
}
let apex = rho0 * height / (rho0 - rho1);
let (mut low, mut high) = (f64::INFINITY, f64::NEG_INFINITY);
for p in positions {
let z = p.sub(origin).dot(axis);
low = low.min(z);
high = high.max(z);
}
!(low <= high) || (apex >= low - margin && apex <= high + margin)
}
pub(super) fn triangle_bound(bound: &FaceBound, t: [Vec3; 3]) -> Option<f64> {
if t.iter().any(|p| !(p.x.is_finite() && p.y.is_finite() && p.z.is_finite())) {
return None;
}
let scale = t.iter().map(|p| p.x.abs().max(p.y.abs()).max(p.z.abs())).fold(0.0f64, f64::max);
let value = match *bound {
FaceBound::Plane => 0.0,
FaceBound::Sphere { centre, radius } => sphere_bound(centre, radius, t),
FaceBound::Ruled { origin, axis, rho0, rho1, height } => {
let radial = |p: Vec3| {
let d = p.sub(origin);
d.sub(axis.scale(d.dot(axis)))
};
let projected = [radial(t[0]), radial(t[1]), radial(t[2])];
let surface_rho = |p: Vec3| rho0 + (rho1 - rho0) * (p.sub(origin).dot(axis) / height);
let mut reach = 0.0f64;
let mut outward = 0.0f64;
for (p, q) in t.iter().zip(&projected) {
let (rho, surface) = (q.length(), surface_rho(*p));
reach = reach.max(rho).max(surface);
outward = outward.max(rho - surface);
}
let inner = point_triangle_distance(Vec3::default(), projected);
let mut inside = reach - inner;
if let Some(n) = bisector(projected) {
let linear = t
.iter()
.zip(&projected)
.map(|(p, q)| surface_rho(*p) - q.dot(n))
.fold(f64::NEG_INFINITY, f64::max);
inside = inside.min(linear);
}
inside.max(0.0) + outward.max(0.0)
}
FaceBound::Affine { ref sphere, largest_semi_axis } => {
let back = [sphere.preimage(t[0]), sphere.preimage(t[1]), sphere.preimage(t[2])];
largest_semi_axis * sphere_bound(Vec3::default(), 1.0, back)
}
};
value.is_finite().then(|| value + pad(scale))
}
fn bisector(projected: [Vec3; 3]) -> Option<Vec3> {
let mut directions = [Vec3::default(); 3];
for (slot, q) in directions.iter_mut().zip(&projected) {
let length = q.length();
if !(length > 0.0) {
return None;
}
*slot = q.scale(1.0 / length);
}
let pairs = [(0, 1, 2), (1, 2, 0), (0, 2, 1)];
let (i, j, k) = pairs
.into_iter()
.min_by(|a, b| directions[a.0].dot(directions[a.1]).total_cmp(&directions[b.0].dot(directions[b.1])))?;
let sum = directions[i].add(directions[j]);
let length = sum.length();
if !(length > 1e-6) {
return None;
}
let n = sum.scale(1.0 / length);
(directions[k].dot(n) >= directions[i].dot(n) - 1e-12).then_some(n)
}
fn sphere_bound(centre: Vec3, radius: f64, t: [Vec3; 3]) -> f64 {
let reach = t.iter().map(|p| p.sub(centre).length()).fold(0.0f64, f64::max);
let inner = point_triangle_distance(centre, t);
(reach.max(radius) - inner).max(0.0) + (reach - radius).max(0.0)
}
pub(super) fn point_triangle_distance(p: Vec3, t: [Vec3; 3]) -> f64 {
let segment = |a: Vec3, b: Vec3| -> f64 {
let ab = b.sub(a);
let length_squared = ab.dot(ab);
if !(length_squared > 0.0) {
return p.sub(a).length();
}
let s = (p.sub(a).dot(ab) / length_squared).clamp(0.0, 1.0);
p.sub(a.add(ab.scale(s))).length()
};
let edges = segment(t[0], t[1]).min(segment(t[1], t[2])).min(segment(t[2], t[0]));
let normal = t[1].sub(t[0]).cross(t[2].sub(t[0]));
let area_squared = normal.dot(normal);
let magnitude = t.iter().map(|q| q.sub(p).length()).fold(0.0f64, f64::max);
let mut best = edges;
if area_squared > 0.0 {
let n = normal.scale(1.0 / area_squared.sqrt());
let height = p.sub(t[0]).dot(n);
let foot = p.sub(n.scale(height));
let inside = (0..3).all(|i| {
let a = t[i];
let b = t[(i + 1) % 3];
b.sub(a).cross(foot.sub(a)).dot(normal) >= 0.0
});
if inside {
best = best.min(height.abs());
}
}
(best - pad(magnitude)).max(0.0)
}