use crate::spatial::Aabb;
use crate::{BrepSolid, NurbsSurface, Vec3};
pub(crate) fn imprint_enabled() -> bool {
crate::switch!("BREP_FEATURE_SCALE_IMPRINT", true)
}
pub(crate) fn select_enabled() -> bool {
crate::switch!("BREP_FEATURE_SCALE_SELECT", true) && imprint_enabled()
}
pub(crate) fn fragment_enabled() -> bool {
crate::switch!("BREP_FEATURE_SCALE_FRAGMENT", true)
}
pub(crate) const FRACTION: f64 = 0.25;
const THIN_ANGLE: f64 = 1e-3;
#[derive(Clone, Copy, Debug)]
pub(crate) struct ThinPair {
pub(crate) height: f64,
pub(crate) region: Aabb,
pub(crate) same_operand: bool,
}
const LONGEST_BAND: f64 = 1.5e-3;
#[derive(Clone, Debug, Default)]
pub(crate) struct FeatureScale {
pub(crate) thin_pairs: Vec<ThinPair>,
}
impl FeatureScale {
pub(crate) fn between(first_solid: &BrepSolid, second_solid: &BrepSolid, tolerance: f64) -> Result<Self, String> {
let coincident = (tolerance * 10.0).max(crate::tolerance::COINCIDENCE_DISTANCE_FLOOR);
let mut planes: Vec<(u8, Vec3, Vec3, Aabb, &crate::FaceRecord)> = Vec::new();
for (operand, solid) in [(0u8, first_solid), (1u8, second_solid)] {
for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
if let Some((origin, normal)) = plane_of(&face.surface)? {
planes.push((operand, origin, normal, Aabb::from_surface_controls(&face.surface)?, face));
}
}
}
let reach = 4.0 * LONGEST_BAND;
let mut thin_pairs = Vec::new();
for (i, &(a_operand, a_origin, a_normal, a_box, a_face)) in planes.iter().enumerate() {
for &(b_operand, b_origin, b_normal, b_box, b_face) in &planes[i + 1..] {
let same_operand = a_operand == b_operand;
if same_operand && a_normal.dot(b_normal) >= 0.0 {
continue;
}
if a_normal.cross(b_normal).length() >= THIN_ANGLE {
continue;
}
let span = |lo_a: f64, hi_a: f64, lo_b: f64, hi_b: f64| -> Option<(f64, f64)> {
let (lo, hi) = (lo_a.max(lo_b), hi_a.min(hi_b));
if lo <= hi {
Some((lo, hi))
} else if lo - hi <= reach {
Some((hi, lo))
} else {
None
}
};
let (Some(x), Some(y), Some(z)) = (
span(a_box.minimum.x, a_box.maximum.x, b_box.minimum.x, b_box.maximum.x),
span(a_box.minimum.y, a_box.maximum.y, b_box.minimum.y, b_box.maximum.y),
span(a_box.minimum.z, a_box.maximum.z, b_box.minimum.z, b_box.maximum.z),
) else {
continue;
};
let region = Aabb { minimum: Vec3::new(x.0, y.0, z.0), maximum: Vec3::new(x.1, y.1, z.1) };
let mut height = 0.0f64;
for corner in corners(region) {
for (on_origin, on_normal, other_origin, other_normal) in [
(a_origin, a_normal, b_origin, b_normal),
(b_origin, b_normal, a_origin, a_normal),
] {
let on_plane = corner.sub(on_normal.scale(corner.sub(on_origin).dot(on_normal)));
height = height.max(on_plane.sub(other_origin).dot(other_normal).abs());
}
}
let floor = if same_operand { tolerance } else { coincident };
if height > floor
&& FRACTION * height < LONGEST_BAND
&& faces_overlap(a_face, b_face, region)?
&& (same_operand || !crate::imprint::reads_as_one_surface(&a_face.surface, &b_face.surface, tolerance)?)
{
if env_read!("BREP_DEBUG_FEATURE_SCALE").is_some() {
eprintln!(
"feature_scale: sliver faces {}/{} height={height:.3e} same_operand={same_operand} region={:?}..{:?}",
a_face.id, b_face.id, region.minimum, region.maximum
);
}
thin_pairs.push(ThinPair { height, region, same_operand });
}
}
}
Ok(Self { thin_pairs })
}
pub(crate) fn is_empty(&self) -> bool {
self.thin_pairs.is_empty()
}
pub(crate) fn cap(&self, point: Vec3, uncapped: f64) -> f64 {
self.thin_pairs
.iter()
.filter(|pair| pair.region.expanded(uncapped).contains(point))
.fold(uncapped, |cap, pair| cap.min(FRACTION * pair.height))
}
pub(crate) fn cap_by_slabs(&self, point: Vec3, uncapped: f64) -> f64 {
self.thin_pairs
.iter()
.filter(|pair| pair.same_operand && pair.region.expanded(uncapped).contains(point))
.fold(uncapped, |cap, pair| cap.min(FRACTION * pair.height))
}
pub(crate) fn regions(&self) -> Vec<(Aabb, f64)> {
self.thin_pairs.iter().map(|pair| (pair.region, FRACTION * pair.height)).collect()
}
pub(crate) fn thinnest(&self) -> Option<f64> {
self.thin_pairs.iter().map(|pair| pair.height).min_by(f64::total_cmp)
}
}
fn faces_overlap(first: &crate::FaceRecord, second: &crate::FaceRecord, region: Aabb) -> Result<bool, String> {
use crate::classification::{parameter_point_in_face, PolygonClass};
let inside = |face: &crate::FaceRecord, point: Vec3| -> Result<bool, String> {
let projection = crate::project_point_to_surface(&face.surface, point)?;
Ok(matches!(
parameter_point_in_face(face, crate::Vec2 { x: projection.u, y: projection.v }, 1e-9)?,
PolygonClass::Inside
))
};
let (lo, hi) = (region.minimum, region.maximum);
for fx in [0.1, 0.5, 0.9] {
for fy in [0.1, 0.5, 0.9] {
for fz in [0.1, 0.5, 0.9] {
let point = Vec3::new(lo.x + (hi.x - lo.x) * fx, lo.y + (hi.y - lo.y) * fy, lo.z + (hi.z - lo.z) * fz);
if inside(first, point)? && inside(second, point)? {
return Ok(true);
}
}
}
}
Ok(false)
}
fn plane_of(surface: &NurbsSurface) -> Result<Option<(Vec3, Vec3)>, String> {
if !surface.is_affine()? {
return Ok(None);
}
let [u0, u1] = surface.domain_u()?;
let [v0, v1] = surface.domain_v()?;
let (u, v) = (0.5 * (u0 + u1), 0.5 * (v0 + v1));
Ok(Some((surface.evaluate(u, v)?, surface.normal(u, v)?)))
}
fn corners(region: Aabb) -> [Vec3; 8] {
let (lo, hi) = (region.minimum, region.maximum);
[
Vec3::new(lo.x, lo.y, lo.z),
Vec3::new(hi.x, lo.y, lo.z),
Vec3::new(lo.x, hi.y, lo.z),
Vec3::new(hi.x, hi.y, lo.z),
Vec3::new(lo.x, lo.y, hi.z),
Vec3::new(hi.x, lo.y, hi.z),
Vec3::new(lo.x, hi.y, hi.z),
Vec3::new(hi.x, hi.y, hi.z),
]
}