use super::*;
use crate::{KernelRefusal, KernelStage, OrRefuse};
const SCAN_SAMPLES: usize = 16;
const REFINE_ITERATIONS: usize = 48;
const REFINE_GATE: f64 = 0.05;
pub(super) fn self_touch_band(tolerance: f64, scale: f64) -> f64 {
(tolerance * 100.0).max(WELD_FLOOR).max(2e-6 * scale)
}
struct EdgeScan<'a> {
edge: &'a EdgeRecord,
curve: NurbsCurve,
minimum: Vec3,
maximum: Vec3,
}
fn edge_scan<'a>(edge: &'a EdgeRecord, curve: NurbsCurve) -> EdgeScan<'a> {
let mut minimum = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
let mut maximum = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
for control in &curve.control_points {
if control.w.abs() <= 1e-300 {
continue;
}
let point = Vec3::new(control.x / control.w, control.y / control.w, control.z / control.w);
minimum = Vec3::new(minimum.x.min(point.x), minimum.y.min(point.y), minimum.z.min(point.z));
maximum = Vec3::new(maximum.x.max(point.x), maximum.y.max(point.y), maximum.z.max(point.z));
}
EdgeScan {
edge,
curve,
minimum,
maximum,
}
}
fn boxes_overlap(a: &EdgeScan<'_>, b: &EdgeScan<'_>, pad: f64) -> bool {
a.minimum.x <= b.maximum.x + pad
&& b.minimum.x <= a.maximum.x + pad
&& a.minimum.y <= b.maximum.y + pad
&& b.minimum.y <= a.maximum.y + pad
&& a.minimum.z <= b.maximum.z + pad
&& b.minimum.z <= a.maximum.z + pad
}
fn shares_a_vertex(a: &EdgeRecord, b: &EdgeRecord) -> bool {
a.start_vertex_id == b.start_vertex_id
|| a.start_vertex_id == b.end_vertex_id
|| a.end_vertex_id == b.start_vertex_id
|| a.end_vertex_id == b.end_vertex_id
}
fn closest_approach(
probe: &NurbsCurve,
target: &NurbsCurve,
refine_gate: f64,
) -> Result<(f64, f64, f64), KernelRefusal> {
let [t0, t1] = probe.domain().or_refuse(KernelStage::Intersect, "domain")?;
let distance_at = |t: f64| -> Result<(f64, f64), KernelRefusal> {
let point = probe.evaluate(t).or_refuse(KernelStage::Intersect, "evaluate")?;
let projection =
project_point_to_curve(target, point).or_refuse(KernelStage::Intersect, "project_point_to_curve")?;
Ok((projection.u, projection.distance))
};
let mut best_index = 0usize;
let mut best: Option<(f64, f64, f64)> = None;
for index in 0..=SCAN_SAMPLES {
let t = t0 + (t1 - t0) * index as f64 / SCAN_SAMPLES as f64;
let (u, distance) = distance_at(t)?;
if best.is_none_or(|(_, _, current)| distance < current) {
best = Some((t, u, distance));
best_index = index;
}
}
let Some(mut best) = best else {
return Err(KernelRefusal::internal(KernelStage::Intersect, "imprint.self_touch", "closest approach of an empty scan"));
};
if best.2 > refine_gate {
return Ok(best);
}
let step = (t1 - t0) / SCAN_SAMPLES as f64;
let mut low = (t0 + step * best_index.saturating_sub(1) as f64).max(t0);
let mut high = (t0 + step * (best_index + 1) as f64).min(t1);
let ratio = (5f64.sqrt() - 1.0) / 2.0;
let mut inner_low = high - ratio * (high - low);
let mut inner_high = low + ratio * (high - low);
let mut at_low = distance_at(inner_low)?;
let mut at_high = distance_at(inner_high)?;
for _ in 0..REFINE_ITERATIONS {
if at_low.1 < at_high.1 {
high = inner_high;
inner_high = inner_low;
at_high = at_low;
inner_low = high - ratio * (high - low);
at_low = distance_at(inner_low)?;
} else {
low = inner_low;
inner_low = inner_high;
at_low = at_high;
inner_high = low + ratio * (high - low);
at_high = distance_at(inner_high)?;
}
if high - low <= 1e-14 * (t1 - t0).abs().max(1.0) {
break;
}
}
for (t, (u, distance)) in [(inner_low, at_low), (inner_high, at_high)] {
if distance < best.2 {
best = (t, u, distance);
}
}
Ok(best)
}
fn face_self_touches<'a>(
operand: u8,
face_id: u64,
edges: &[&'a EdgeRecord],
band: f64,
subcurve: &dyn Fn(&EdgeRecord) -> Result<NurbsCurve, KernelRefusal>,
) -> Result<Vec<(&'a EdgeRecord, f64)>, KernelRefusal> {
if edges.len() < 2 {
return Ok(Vec::new());
}
let debug = std::env::var("BREP_DEBUG_SELF_TOUCH").is_ok();
let scans = edges
.iter()
.filter(|edge| !edge.degenerate)
.map(|edge| Ok(edge_scan(edge, subcurve(edge)?)))
.collect::<Result<Vec<_>, KernelRefusal>>()?;
let lengths = scans
.iter()
.map(|scan| curve_length_rough(&scan.curve))
.collect::<Result<Vec<_>, KernelRefusal>>()?;
let mut splits = Vec::new();
for (index, first) in scans.iter().enumerate() {
for (other, second) in scans.iter().enumerate().skip(index + 1) {
if shares_a_vertex(first.edge, second.edge) || !boxes_overlap(first, second, band) {
continue;
}
let refine_gate = (REFINE_GATE * (lengths[index] + lengths[other])).max(band);
let (t, u, distance) = closest_approach(&first.curve, &second.curve, refine_gate)?;
if distance > band {
continue;
}
if debug {
eprintln!(
"self-touch: operand {} face {} edges {} and {} touch at t={:.9} u={:.9} (gap {:.3e}, band {:.3e})",
operand, face_id, first.edge.id, second.edge.id, t, u, distance, band
);
}
splits.push((first.edge, t));
splits.push((second.edge, u));
}
}
Ok(splits)
}
pub(crate) fn self_touch_edge_splits(
solid: &BrepSolid,
operand: u8,
tolerance: f64,
scale: f64,
) -> Result<Vec<EdgeSplitRecord>, KernelRefusal> {
let band = self_touch_band(tolerance, scale);
let edges_by_id = solid
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut splits: HashMap<u64, Vec<f64>> = HashMap::default();
for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
let mut seen = HashSet::default();
let mut edges = Vec::new();
for coedge in face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
if seen.insert(coedge.edge_id) {
if let Some(edge) = edges_by_id.get(&coedge.edge_id) {
edges.push(*edge);
}
}
}
for (edge, parameter) in
face_self_touches(operand, face.id, &edges, band, &edge_subcurve)?
{
push_edge_split(
splits.entry(edge.id).or_default(),
edge,
parameter,
tolerance,
)?;
}
}
let mut records = splits
.into_iter()
.map(|(edge_id, mut parameters)| {
parameters.sort_by(f64::total_cmp);
EdgeSplitRecord {
operand,
edge_id,
parameters,
}
})
.collect::<Vec<_>>();
records.sort_by_key(|record| record.edge_id);
Ok(records)
}
impl ImprintBuilder<'_> {
pub(super) fn split_self_touching_loops(
&mut self,
faces: &[TaggedFace<'_>],
face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
subcurve: &dyn Fn(u8, &EdgeRecord) -> Result<NurbsCurve, KernelRefusal>,
) -> Result<(), KernelRefusal> {
let band = self_touch_band(self.tolerance, self.scale);
for face in faces {
let operand = face.operand;
let touches = face_self_touches(
operand,
face.face.id,
&face_edge_lists[&face.key()],
band,
&|edge| subcurve(operand, edge),
)?;
for (edge, parameter) in touches {
self.add_edge_split(operand, edge, parameter)?;
}
}
Ok(())
}
}