use crate::topology::BrepSolid;
use crate::transform_topology::{transform_curve, transform_surface};
use crate::{Aabb, AffineTransform, Vec3};
use std::cell::Cell;
pub(crate) const RECENTRE_FACTOR: f64 = 32.0;
pub(crate) fn translation(offset: Vec3) -> Result<AffineTransform, String> {
AffineTransform::new([
1.0, 0.0, 0.0, offset.x, 0.0, 1.0, 0.0, offset.y, 0.0, 0.0, 1.0, offset.z, 0.0, 0.0, 0.0, 1.0,
])
}
pub(crate) fn translate_solid(solid: &BrepSolid, offset: Vec3) -> Result<BrepSolid, String> {
let transform = translation(offset)?;
let mut result = solid.clone();
result.mass_properties_cache = Default::default();
for vertex in &mut result.vertices {
vertex.point = transform.point(vertex.point);
}
for edge in &mut result.edges {
edge.curve = transform_curve(&edge.curve, transform)?;
}
for shell in &mut result.shells {
for face in &mut shell.faces {
face.surface = transform_surface(&face.surface, transform)?;
}
}
Ok(result)
}
#[cfg(not(target_arch = "wasm32"))]
fn translation_control_offset() -> Option<Vec3> {
static OFFSET: std::sync::OnceLock<Option<Vec3>> = std::sync::OnceLock::new();
*OFFSET.get_or_init(|| {
let value = std::env::var("BREP_TRANSLATION_CONTROL").ok()?;
let offset: f64 = value.trim().parse().ok()?;
(offset != 0.0 && offset.is_finite()).then(|| Vec3::new(offset, offset, offset))
})
}
#[cfg(target_arch = "wasm32")]
fn translation_control_offset() -> Option<Vec3> {
None
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum GuardMode {
Off,
On,
Always,
}
const DEFAULT_GUARD_MODE: GuardMode = GuardMode::On;
#[cfg(not(target_arch = "wasm32"))]
fn guard_mode_from_env() -> GuardMode {
static MODE: std::sync::OnceLock<GuardMode> = std::sync::OnceLock::new();
*MODE.get_or_init(|| match std::env::var("BREP_RECENTRE").as_deref() {
Err(_) => DEFAULT_GUARD_MODE,
Ok("") | Ok("0") => GuardMode::Off,
Ok("always") => GuardMode::Always,
Ok(_) => GuardMode::On,
})
}
#[cfg(target_arch = "wasm32")]
fn guard_mode_from_env() -> GuardMode {
DEFAULT_GUARD_MODE
}
thread_local! {
static GUARD_MODE_OVERRIDE: Cell<Option<GuardMode>> = const { Cell::new(None) };
}
fn guard_mode() -> GuardMode {
GUARD_MODE_OVERRIDE.with(|cell| cell.get()).unwrap_or_else(guard_mode_from_env)
}
pub(crate) fn solid_bounds(solid: &BrepSolid) -> Aabb {
let mut bounds = Aabb::from_points(solid.vertices.iter().map(|vertex| vertex.point));
for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
match Aabb::from_surface_controls(&face.surface) {
Ok(face_bounds) => bounds.include(face_bounds),
Err(_) => {
return Aabb {
minimum: Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY),
maximum: Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY),
}
}
}
}
bounds
}
pub(crate) fn guard_offset(bounds: Aabb) -> Option<Vec3> {
let mode = guard_mode();
if mode == GuardMode::Off || GUARD_DEPTH.with(|depth| depth.get()) > 0 {
return None;
}
let centre = bounds.minimum.add(bounds.maximum).scale(0.5);
let diagonal = bounds.diagonal();
let distance = centre.length();
if !(diagonal.is_finite() && diagonal > 0.0 && distance.is_finite()) {
return None;
}
(mode == GuardMode::Always || distance > RECENTRE_FACTOR * diagonal).then(|| centre.scale(-1.0))
}
pub(crate) fn profile_fired(entry: &str, offset: Vec3, bounds: Aabb) {
if crate::profile::profile_enabled() {
profile_line!(
"recentre.fired entry={entry} distance={:.4e} diagonal={:.4e}",
offset.length(),
bounds.diagonal()
);
}
}
thread_local! {
static CONTROL_DEPTH: Cell<u32> = const { Cell::new(0) };
static GUARD_DEPTH: Cell<u32> = const { Cell::new(0) };
}
pub(crate) fn control_offset_at_entry() -> Option<Vec3> {
if CONTROL_DEPTH.with(|depth| depth.get()) > 0 {
return None;
}
translation_control_offset()
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum EntryKind {
Control,
Guard,
}
pub(crate) struct TranslatedEntry(EntryKind);
impl TranslatedEntry {
pub(crate) fn open(kind: EntryKind) -> Self {
let depth = match kind {
EntryKind::Control => &CONTROL_DEPTH,
EntryKind::Guard => &GUARD_DEPTH,
};
depth.with(|depth| depth.set(depth.get() + 1));
Self(kind)
}
}
impl Drop for TranslatedEntry {
fn drop(&mut self) {
let depth = match self.0 {
EntryKind::Control => &CONTROL_DEPTH,
EntryKind::Guard => &GUARD_DEPTH,
};
depth.with(|depth| depth.set(depth.get().saturating_sub(1)));
}
}