use crate::topology::{BrepSolid, EdgeRecord, FaceRecord};
use crate::tolerance::PCURVE_ACCEPTANCE_REL;
use crate::{
measure_edge_against_pcurve_image, solid_scale, KernelTolerances, NurbsCurve, NurbsSurface,
Vec3,
};
use rustc_hash::FxHashMap as HashMap;
pub const EDGE_CAP_FRACTION: f64 = 0.10;
pub const VERTEX_CAP_FRACTION: f64 = 0.25;
const LENGTH_SAMPLES: usize = 8;
pub struct EntityTolerances<'s> {
solid: &'s BrepSolid,
floor: f64,
size_cap: f64,
coedges: Option<HashMap<u64, Vec<CoedgeUse<'s>>>>,
edge_index: Option<HashMap<u64, &'s EdgeRecord>>,
edges: HashMap<u64, f64>,
vertices: HashMap<u64, f64>,
}
struct CoedgeUse<'s> {
surface: &'s NurbsSurface,
pcurve: &'s NurbsCurve,
forward: bool,
}
impl<'s> EntityTolerances<'s> {
pub fn for_solid(solid: &'s BrepSolid, policy: &KernelTolerances) -> Self {
Self::with_floor(solid, policy.spatial())
}
pub fn with_floor(solid: &'s BrepSolid, floor: f64) -> Self {
let floor = if floor.is_finite() && floor > 0.0 {
floor
} else {
0.0
};
Self {
solid,
floor,
size_cap: PCURVE_ACCEPTANCE_REL * solid_scale(solid),
coedges: None,
edge_index: None,
edges: HashMap::default(),
vertices: HashMap::default(),
}
}
pub fn edge(&mut self, id: u64) -> f64 {
if let Some(&memo) = self.edges.get(&id) {
return memo;
}
let band = self.measure_edge(id);
self.edges.insert(id, band);
band
}
pub fn vertex(&mut self, id: u64) -> f64 {
if let Some(&memo) = self.vertices.get(&id) {
return memo;
}
let band = self.measure_vertex(id);
self.vertices.insert(id, band);
band
}
pub fn worst_edge(&mut self, ids: impl IntoIterator<Item = u64>) -> f64 {
let mut worst = self.floor;
for id in ids {
worst = worst.max(self.edge(id));
}
worst
}
pub fn floor(&self) -> f64 {
self.floor
}
fn measure_edge(&mut self, id: u64) -> f64 {
self.ensure_index();
let Some(edge) = self.edge_index.as_ref().and_then(|index| index.get(&id)) else {
return self.floor;
};
let edge = *edge;
if edge.degenerate {
return self.floor;
}
let cap = self.edge_cap(edge);
let probe_band = self.floor;
let Some(uses) = self.coedges.as_ref().and_then(|map| map.get(&id)) else {
return self.floor;
};
let mut worst = 0.0f64;
for use_record in uses {
match measure_edge_against_pcurve_image(
use_record.surface,
use_record.pcurve,
edge,
use_record.forward,
probe_band,
) {
Err(_) => continue,
Ok(measured) => {
let deviation = measured.deviation();
if deviation.is_finite() {
worst = worst.max(deviation);
}
}
}
}
clamp_band(self.floor, worst, cap)
}
fn measure_vertex(&mut self, id: u64) -> f64 {
let Some(point) = self
.solid
.vertices
.iter()
.find(|vertex| vertex.id == id)
.map(|vertex| vertex.point)
else {
return self.floor;
};
let mut gap = 0.0f64;
let mut shortest = f64::INFINITY;
for edge in &self.solid.edges {
if edge.degenerate {
continue;
}
for (vertex_id, parameter) in [
(edge.start_vertex_id, edge.t0),
(edge.end_vertex_id, edge.t1),
] {
if vertex_id != id {
continue;
}
shortest = shortest.min(edge_length(edge));
if let Ok(end) = edge.curve.evaluate(parameter) {
let distance = point.sub(end).length();
if distance.is_finite() {
gap = gap.max(distance);
}
}
}
}
if !shortest.is_finite() {
return self.floor;
}
clamp_band(self.floor, gap, VERTEX_CAP_FRACTION * shortest)
}
fn edge_cap(&self, edge: &EdgeRecord) -> f64 {
(EDGE_CAP_FRACTION * edge_length(edge)).min(self.size_cap)
}
fn ensure_index(&mut self) {
if self.coedges.is_some() {
return;
}
let mut coedges: HashMap<u64, Vec<CoedgeUse<'s>>> = HashMap::default();
for face in self.solid.shells.iter().flat_map(|shell| &shell.faces) {
let face: &'s FaceRecord = face;
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
coedges
.entry(coedge.edge_id)
.or_default()
.push(CoedgeUse {
surface: &face.surface,
pcurve: &coedge.pcurve,
forward: coedge.forward,
});
}
}
}
self.coedges = Some(coedges);
self.edge_index = Some(
self.solid
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect(),
);
}
}
fn clamp_band(floor: f64, measured: f64, cap: f64) -> f64 {
let measured = if measured.is_finite() && measured > 0.0 {
measured
} else {
0.0
};
let cap = if cap.is_finite() && cap > 0.0 {
cap
} else {
floor
};
floor.max(measured.min(cap.max(floor)))
}
fn edge_length(edge: &EdgeRecord) -> f64 {
let mut total = 0.0f64;
let mut previous: Option<Vec3> = None;
for step in 0..=LENGTH_SAMPLES {
let fraction = step as f64 / LENGTH_SAMPLES as f64;
let Ok(point) = edge.curve.evaluate(edge.t0 + (edge.t1 - edge.t0) * fraction) else {
return 0.0;
};
if let Some(last) = previous {
total += point.sub(last).length();
}
previous = Some(point);
}
if total.is_finite() {
total
} else {
0.0
}
}