use crate::{KernelRefusal, KernelStage, OrRefuse, RefusalClass, MAX_FIT_STATIONS};
fn section_refine_gate() -> Option<f64> {
static GATE: std::sync::OnceLock<Option<f64>> = std::sync::OnceLock::new();
*GATE.get_or_init(|| match std::env::var("BREP_SECTION_REFINE_GATE") {
Ok(value) if value == "on" => Some(SECTION_REFINE_GATE),
Ok(value) => value.parse::<f64>().ok().filter(|gate| *gate >= 0.0),
Err(_) => None,
})
}
const SECTION_REFINE_GATE: f64 = 100.0;
fn even_sliver_ends(
section: &mut Vec<Vec3>,
first: &NurbsSurface,
second: &NurbsSurface,
tolerance: f64,
) -> Result<usize, String> {
static MODE: std::sync::OnceLock<String> = std::sync::OnceLock::new();
let mode = MODE.get_or_init(|| std::env::var("BREP_SECTION_SLIVER_END").unwrap_or_default());
if !(mode == "drop" || mode == "resolve") {
return Ok(0);
}
let mut evened = 0;
for _ in 0..4 {
let count = section.len();
if count < 6 {
break;
}
let mut chords: Vec<f64> = section.windows(2).map(|pair| pair[1].sub(pair[0]).length()).collect();
let lengths = chords.clone();
chords.sort_by(f64::total_cmp);
let median = chords[chords.len() / 2];
let spans = lengths.len();
let near_ends = [0, 1, 2, spans - 3, spans - 2, spans - 1];
let Some(&sliver) = near_ends
.iter()
.filter(|&&span| lengths[span] < sliver_end_fraction() * median)
.min_by(|a, b| lengths[**a].total_cmp(&lengths[**b]))
else {
break;
};
let mut candidates = Vec::new();
for station in [sliver, sliver + 1] {
if station == 0 || station == count - 1 {
continue;
}
let merged = section[station + 1].sub(section[station - 1]).length();
candidates.push((station, merged));
}
let Some(&(station, _)) = candidates.iter().min_by(|a, b| a.1.total_cmp(&b.1)) else {
break;
};
if mode == "resolve" {
let (before, after) = (section[station - 1], section[station + 1]);
let seed = before.add(after).scale(0.5);
let reach = 0.5 * after.sub(before).length();
match crate::surface_surface_intersection::refine_to_intersection(first, second, seed, tolerance, reach)? {
Some(point) => section[station] = point,
None => {
section.remove(station);
}
}
} else {
section.remove(station);
}
evened += 1;
}
Ok(evened)
}
const SLIVER_END_FRACTION: f64 = 0.5;
fn sliver_end_fraction() -> f64 {
static FRACTION: std::sync::OnceLock<f64> = std::sync::OnceLock::new();
*FRACTION.get_or_init(|| {
std::env::var("BREP_SECTION_SLIVER_FRACTION")
.ok()
.and_then(|value| value.parse::<f64>().ok())
.filter(|fraction| *fraction > 0.0 && *fraction < 1.0)
.unwrap_or(SLIVER_END_FRACTION)
})
}
fn section_fit_degree() -> usize {
static DEGREE: std::sync::OnceLock<usize> = std::sync::OnceLock::new();
*DEGREE.get_or_init(|| {
std::env::var("BREP_SECTION_FIT_DEGREE")
.ok()
.and_then(|value| value.parse::<usize>().ok())
.filter(|degree| (1..=7).contains(degree))
.unwrap_or(5)
})
}
pub(super) const SECTION_MIDSPAN_FRACTION: f64 = 0.1;
const SECTION_MIDSPAN_ROUNDS: usize = 4;
const SECTION_MIDSPAN_PROGRESS: f64 = 0.5;
fn distance_to_segment_sq(point: Vec3, from: Vec3, to: Vec3) -> f64 {
let d = to.sub(from);
let l2 = d.dot(d);
let t = if l2 > 0.0 { (point.sub(from).dot(d) / l2).clamp(0.0, 1.0) } else { 0.0 };
let q = from.add(d.scale(t));
point.sub(q).dot(point.sub(q))
}
#[derive(Clone, Copy, Debug, PartialEq)]
enum MidspanExit {
Converged,
BelowGate,
Stalled,
NoProgress,
RoundBudget,
StationCeiling,
}
fn midspan_misses(
fit: &PolylineFit,
first: &NurbsSurface,
second: &NurbsSurface,
floor: f64,
tolerance: f64,
) -> Result<(f64, usize, Vec<(usize, Vec3)>), String> {
let mut worst = 0.0f64;
let mut missing = 0usize;
let mut inserts = Vec::new();
for span in 0..fit.parameters.len().saturating_sub(1) {
let mid = 0.5 * (fit.parameters[span] + fit.parameters[span + 1]);
let point = fit.curve.evaluate(mid)?;
let off = project_point_to_surface(first, point)?
.distance
.max(project_point_to_surface(second, point)?.distance);
worst = worst.max(off);
if off <= floor {
continue;
}
missing += 1;
let reach = 0.5 * fit.kept[span + 1].sub(fit.kept[span]).length();
if let Some(refined) = crate::surface_surface_intersection::refine_to_intersection(
first, second, point, tolerance, reach,
)? {
inserts.push((span, refined));
}
}
Ok((worst, missing, inserts))
}
fn refine_section_against_carriers(
section: &mut Vec<Vec3>,
fit: PolylineFit,
first: &NurbsSurface,
second: &NurbsSurface,
fit_tolerance: f64,
tolerance: f64,
local_fit: bool,
gate: f64,
) -> Result<(PolylineFit, f64, f64, usize, MidspanExit), String> {
let floor = fit_tolerance * SECTION_MIDSPAN_FRACTION;
let mut unrefined = f64::NAN;
let mut fit = fit;
let mut rounds = 0usize;
let mut previous: Option<(PolylineFit, f64, usize)> = None;
loop {
let (worst, missing, inserts) = midspan_misses(&fit, first, second, floor, tolerance)?;
if rounds == 0 {
unrefined = worst;
}
if worst <= floor {
return Ok((fit, worst, unrefined, rounds, MidspanExit::Converged));
}
if rounds == 0 && worst <= gate {
return Ok((fit, worst, unrefined, rounds, MidspanExit::BelowGate));
}
if let Some((earlier, earlier_worst, earlier_missing)) = previous.take() {
if worst > earlier_worst * SECTION_MIDSPAN_PROGRESS && missing >= earlier_missing {
return Ok((earlier, earlier_worst, unrefined, rounds, MidspanExit::NoProgress));
}
}
if inserts.is_empty() {
return Ok((fit, worst, unrefined, rounds, MidspanExit::Stalled));
}
if rounds >= SECTION_MIDSPAN_ROUNDS {
return Ok((fit, worst, unrefined, rounds, MidspanExit::RoundBudget));
}
if section.len() + inserts.len() > MAX_FIT_STATIONS {
return Ok((fit, worst, unrefined, rounds, MidspanExit::StationCeiling));
}
let station_index = |point: Vec3, from: usize| -> Option<usize> {
section[from..]
.iter()
.position(|p| p.sub(point).length() <= 1e-15 * (1.0 + point.length()))
.map(|offset| from + offset)
};
let mut placed: Vec<(usize, Vec3)> = Vec::new();
for (span, point) in inserts {
let Some(lo) = station_index(fit.kept[span], 0) else {
continue;
};
let Some(hi) = station_index(fit.kept[span + 1], lo + 1) else {
continue;
};
if hi <= lo {
continue;
}
if section[lo..=hi].iter().any(|p| p.sub(point).length() <= floor) {
continue; }
let mut best = (lo, f64::INFINITY);
for index in lo..hi {
let d2 = distance_to_segment_sq(point, section[index], section[index + 1]);
if d2 < best.1 {
best = (index, d2);
}
}
placed.push((best.0 + 1, point));
}
if placed.is_empty() {
return Ok((fit, worst, unrefined, rounds, MidspanExit::Stalled));
}
placed.sort_by(|a, b| b.0.cmp(&a.0));
for (at, point) in placed {
section.insert(at, point);
}
let maximum_points = section.len().min(MAX_FIT_STATIONS);
let refit = fit_polyline_of_degree(section, fit_tolerance, maximum_points, local_fit, section_fit_degree())?;
previous = Some((std::mem::replace(&mut fit, refit), worst, missing));
rounds += 1;
}
}
use super::*;
use super::gate_census::{census_pair, fit_census_enabled};
pub fn build_imprints(
solid_a: &BrepSolid,
solid_b: &BrepSolid,
options: &ImprintOptions,
) -> Result<ImprintResultRecord, KernelRefusal> {
build_imprints_impl(solid_a, solid_b, options, false)
}
pub(crate) fn build_carrier_imprints(
solid_a: &BrepSolid,
solid_b: &BrepSolid,
options: &ImprintOptions,
) -> Result<ImprintResultRecord, KernelRefusal> {
build_imprints_impl(solid_a, solid_b, options, true)
}
fn build_imprints_impl(
solid_a: &BrepSolid,
solid_b: &BrepSolid,
options: &ImprintOptions,
open_carriers: bool,
) -> Result<ImprintResultRecord, KernelRefusal> {
let mut section_evidence = false;
let edges = edge_map(solid_a, solid_b);
let mut builder = ImprintBuilder {
open_carriers,
edges,
tolerance: options.tolerance,
barrier_edges: HashSet::default(),
overlap_ridden_edges: HashSet::default(),
scale: solid_scale(solid_a).max(solid_scale(solid_b)),
vertices: Vec::new(),
vertex_radii: HashMap::default(),
pieces: Vec::new(),
by_face: HashMap::default(),
edge_splits: HashMap::default(),
marched_pieces: HashSet::default(),
next_id: 1,
};
let mut first_faces = faces(solid_a, 0);
let mut second_faces = faces(solid_b, 1);
let first_restricted: Vec<Option<MarchWindow>> = first_faces
.iter()
.map(|tagged| restricted_carrier(tagged.face))
.collect();
let second_restricted: Vec<Option<MarchWindow>> = second_faces
.iter()
.map(|tagged| restricted_carrier(tagged.face))
.collect();
for (tagged, window) in first_faces.iter_mut().zip(&first_restricted) {
tagged.lifted = window.as_ref().filter(|window| window.lifted).map(|window| &window.surface);
}
for (tagged, window) in second_faces.iter_mut().zip(&second_restricted) {
tagged.lifted = window.as_ref().filter(|window| window.lifted).map(|window| &window.surface);
}
let first_faces = first_faces;
let second_faces = second_faces;
let charts: FaceCharts<'_> = first_faces
.iter()
.chain(second_faces.iter())
.filter_map(|tagged| tagged.lifted.map(|surface| (tagged.key(), surface)))
.collect();
let first_bounds = first_faces
.iter()
.zip(&first_restricted)
.map(|(tagged, restricted)| {
face_bounds(
tagged.face,
restricted.as_ref().map(|window| &window.surface),
&builder.edges,
tagged.operand,
options.tolerance,
)
})
.collect::<Result<Vec<_>, _>>()?;
let second_bounds = second_faces
.iter()
.zip(&second_restricted)
.map(|(tagged, restricted)| {
face_bounds(
tagged.face,
restricted.as_ref().map(|window| &window.surface),
&builder.edges,
tagged.operand,
options.tolerance,
)
})
.collect::<Result<Vec<_>, _>>()?;
let second_bvh = Bvh::build(&second_bounds);
let first_classify = first_faces
.iter()
.map(|tagged| SurfaceClassifyData::build(&tagged.face.surface, options.tolerance))
.collect::<Result<Vec<_>, _>>().or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
let second_classify = second_faces
.iter()
.map(|tagged| SurfaceClassifyData::build(&tagged.face.surface, options.tolerance))
.collect::<Result<Vec<_>, _>>().or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
let mut face_edge_lists: HashMap<FaceKey, Vec<&EdgeRecord>> = HashMap::default();
for tagged in first_faces.iter().chain(second_faces.iter()) {
face_edge_lists.insert(tagged.key(), face_edges(*tagged, &builder.edges)?);
}
let mut subcurves: HashMap<(u8, u64), NurbsCurve> = HashMap::default();
for (&key, edge) in &builder.edges {
if !edge.degenerate {
if let Ok(curve) = edge_subcurve(edge) {
subcurves.insert(key, curve);
}
}
}
let cached_subcurve = |operand: u8, edge: &EdgeRecord| -> Result<NurbsCurve, KernelRefusal> {
match subcurves.get(&(operand, edge.id)) {
Some(curve) => Ok(curve.clone()),
None => edge_subcurve(edge),
}
};
let mut profile = ImprintProfile::new();
let mut tangent_nodes: Vec<Vec3> = Vec::new();
let mut cosurface_pairs: Vec<(FaceKey, FaceKey)> = Vec::new();
let mut paired = Vec::new();
for (first_index, first) in first_faces.iter().enumerate() {
let first = *first;
paired.clear();
second_bvh.overlapping(
first_bounds[first_index],
options.tolerance * 100.0,
&mut paired,
);
paired.sort_unstable();
let debug_pairs = std::env::var("BREP_DEBUG_PAIRS").is_ok();
if debug_pairs {
eprintln!(
"bvh first_face={} -> {} candidate pairs {:?}",
first.face.id,
paired.len(),
paired
.iter()
.map(|&index| second_faces[index].face.id)
.collect::<Vec<_>>()
);
}
for &second_index in &paired {
let second = second_faces[second_index];
profile.pairs += 1;
let mut tangent_node: Option<Vec3> = None;
let mut lap_start = None;
profile.lap(&mut lap_start);
let pair_classification = classify_surface_pair_cached(
&first.face.surface,
&first_classify[first_index],
&second.face.surface,
&second_classify[second_index],
options.tolerance,
PAIR_ANGULAR_TOLERANCE,
).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
profile.classify += profile.lap(&mut lap_start);
if pair_classification.relation == SurfacePairRelation::Disjoint {
if debug_pairs {
eprintln!(
"pair {}x{}: DISJOINT-cull sep={:.4e}",
first.face.id, second.face.id, pair_classification.minimum_separation
);
}
continue;
}
let is_cosurface = pair_classification.relation == SurfacePairRelation::Cosurface
|| cosurface_pair(&first.face.surface, &second.face.surface, options.tolerance)?;
profile.cosurface += profile.lap(&mut lap_start);
if is_cosurface {
if debug_pairs {
eprintln!("pair {}x{}: cosurface", first.face.id, second.face.id);
}
cosurface_pairs.push((first.key(), second.key()));
for edge in &face_edge_lists[&second.key()] {
if !edge.degenerate {
builder.process_curve(
cached_subcurve(second.operand, edge)?,
first,
second,
&[first],
&[first],
false,
)?;
}
}
for edge in &face_edge_lists[&first.key()] {
if !edge.degenerate {
builder.process_curve(
cached_subcurve(first.operand, edge)?,
first,
second,
&[second],
&[second],
false,
)?;
}
}
profile.process_curve += profile.lap(&mut lap_start);
continue;
}
let planar_iso = planar_iso_intersection(
&first.face.surface,
&second.face.surface,
options.tolerance,
)?;
profile.planar_iso += profile.lap(&mut lap_start);
let analytic = crate::intersect_analytic_pair(
&first.face.surface,
&second.face.surface,
options.tolerance,
);
profile.analytic += profile.lap(&mut lap_start);
let exact_section = planar_iso.is_some() || analytic.is_some();
for edge in &face_edge_lists[&first.key()] {
if !edge.degenerate {
let curve = cached_subcurve(first.operand, edge)?;
let spans = curve_spans_on_face(curve, second.face, &face_edge_lists[&second.key()], options.tolerance, exact_section)?;
for span in spans {
builder.process_curve(span, first, second, &[second], &[second], false)?;
}
}
}
for edge in &face_edge_lists[&second.key()] {
if !edge.degenerate {
let curve = cached_subcurve(second.operand, edge)?;
let spans = curve_spans_on_face(curve, first.face, &face_edge_lists[&first.key()], options.tolerance, exact_section)?;
for span in spans {
builder.process_curve(span, first, second, &[first], &[first], false)?;
}
}
}
profile.lies_on += profile.lap(&mut lap_start);
let mut pair_census = if fit_census_enabled() {
census_pair(first, second, options.tolerance)?
} else {
None
};
if let Some(curve) = planar_iso {
if debug_pairs {
eprintln!("pair {}x{}: planar_iso", first.face.id, second.face.id);
}
builder.process_curve(
curve,
first,
second,
&[first, second],
&[first, second],
true,
)?;
profile.process_curve += profile.lap(&mut lap_start);
continue;
}
if let Some(curves) = analytic {
if debug_pairs {
eprintln!(
"pair {}x{}: analytic x{}",
first.face.id,
second.face.id,
curves.len()
);
}
for curve in curves {
builder.process_curve(
curve,
first,
second,
&[first, second],
&[first, second],
true,
)?;
}
profile.process_curve += profile.lap(&mut lap_start);
continue;
}
if pair_classification.relation == SurfacePairRelation::NearTangent
&& pair_classification.tangential_only
{
let first_march = first_restricted[first_index]
.as_ref()
.map(|window| &window.surface)
.unwrap_or(&first.face.surface);
let second_march = second_restricted[second_index]
.as_ref()
.map(|window| &window.surface)
.unwrap_or(&second.face.surface);
let supplemental = intersect_surfaces_supplemental(
first_march,
second_march,
&SurfaceIntersectionOptions {
tolerance: options.tolerance,
maximum_step: options.maximum_ssi_step,
..Default::default()
},
).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
let mut dropped_length = 0.0f64;
let mut clipped: Vec<Vec<Vec3>> = Vec::new();
for branch in &supplemental {
for run in clip_branch_to_trims(&branch.points, first, second)? {
if run.len() < 2 {
continue;
}
let length: f64 = run
.windows(2)
.map(|pair| pair[1].sub(pair[0]).length())
.sum();
dropped_length = dropped_length.max(length);
clipped.push(run);
}
}
if dropped_length <= options.tolerance * 100.0 {
if debug_pairs {
eprintln!(
"pair {}x{}: tangential-only contact, march skipped",
first.face.id, second.face.id
);
}
continue;
}
let mut tangency_inside_trims = false;
let mut tangency_witness = Vec3::default();
'clipped: for run in &clipped {
for (index, &point) in run.iter().enumerate() {
if pair_normals_parallel_at(first, second, point)? {
tangency_inside_trims = true;
tangency_witness = point;
if debug_pairs {
eprintln!(
"pair {}x{}: TANGENCY at run index {}/{} ({:.6},{:.6},{:.6}) dropped_len={:.4e}",
first.face.id, second.face.id, index, run.len(),
point.x, point.y, point.z, dropped_length
);
}
break 'clipped;
}
}
}
if tangency_inside_trims {
let contact = classify_tangent_contact(
&first.face.surface,
&second.face.surface,
tangency_witness,
options.tolerance,
)?;
if debug_pairs {
eprintln!(
"pair {}x{}: contact classification {:?}",
first.face.id, second.face.id, contact
);
}
match contact {
Some(contact) if contact.is_isolated_node() => {
tangent_node = Some(contact.point);
tangent_nodes.push(contact.point);
}
other => {
let shape = match other {
Some(contact) => contact.describe(),
None => format!(
"the tangency near ({:.6},{:.6},{:.6}) could not be refined \
onto a contact, so its shape is unproven",
tangency_witness.x, tangency_witness.y, tangency_witness.z
),
};
return Err(KernelRefusal::new(
RefusalClass::TangentNodeSingularity,
KernelStage::Intersect,
format!(
"boolean: unsupported singular/tangent-node surface intersection \
between faces {} and {}: {shape}",
first.face.id, second.face.id
),
));
}
}
}
if debug_pairs && tangent_node.is_none() {
eprintln!(
"pair {}x{}: classified tangential-only, but the curve inside both \
trims is transverse ({dropped_length:.4}) — marching",
first.face.id, second.face.id
);
}
}
let first_march = first_restricted[first_index]
.as_ref()
.map(|window| &window.surface)
.unwrap_or(&first.face.surface);
let second_march = second_restricted[second_index]
.as_ref()
.map(|window| &window.surface)
.unwrap_or(&second.face.surface);
let mut seed_points = Vec::new();
let mut min_seed_tangency = f64::INFINITY;
let mut paired_grazing_crossings = false;
for (face, other_march, other) in
[(first, second_march, second), (second, first_march, first)]
{
for edge in &face_edge_lists[&face.key()] {
if edge.degenerate {
continue;
}
let mut grazing_crossings: Vec<Vec3> = Vec::new();
for hit in intersect_curve_surface(&edge.curve, other_march, options.tolerance).or_refuse(KernelStage::Intersect, "intersect_curve_surface")?
{
if hit.t < edge.t0 - 1e-9 || hit.t > edge.t1 + 1e-9 {
continue;
}
let derivative = edge.curve.derivatives(hit.t, 1).or_refuse(KernelStage::Intersect, "derivatives")?[1];
let tangent = match derivative.normalized() {
Ok(tangent) => tangent,
Err(error) => {
if std::env::var("BREP_CUSP_TANGENT_RESCUE").as_deref() == Ok("0") {
return Err(error)
.or_refuse(KernelStage::Intersect, "normalized");
}
match edge.curve.stationary_tangent_rescue(hit.t, edge.t0, edge.t1)
{
Some(tangent) => tangent,
None => continue,
}
}
};
let normal = match other.chart().normal(hit.u, hit.v) {
Ok(normal) => normal,
Err(_) => continue,
};
if debug_pairs {
eprintln!(
"pair {}x{}: seed edge {} t={:.6} p=({:.5},{:.5},{:.5}) |tan.n|={:.4} {}",
first.face.id,
second.face.id,
edge.id,
hit.t,
hit.point.x,
hit.point.y,
hit.point.z,
tangent.dot(normal).abs(),
if tangent.dot(normal).abs() >= 0.1 { "ACCEPT" } else { "reject" }
);
}
if tangent.dot(normal).abs() >= 0.1 {
seed_points.push(hit.point);
min_seed_tangency = min_seed_tangency.min(tangent.dot(normal).abs());
section_evidence = true;
} else {
if grazing_crossings.iter().any(|point| {
point.sub(hit.point).length() > assembler_weld(options.tolerance)
}) {
paired_grazing_crossings = true;
}
grazing_crossings.push(hit.point);
}
}
}
}
profile.seeds += profile.lap(&mut lap_start);
profile.marched_pairs += 1;
if debug_pairs {
eprintln!(
"pair {}x{}: marching (relation {:?})...",
first.face.id, second.face.id, pair_classification.relation
);
}
let marched = intersect_surfaces(
first_march,
second_march,
&SurfaceIntersectionOptions {
tolerance: options.tolerance,
maximum_step: march_maximum_step(
&pair_classification,
options.maximum_ssi_step,
builder.scale,
),
seed_points: seed_points.clone(),
..Default::default()
},
);
let marched = match (marched, tangent_node) {
(Err(error), Some(node)) => {
return Err(KernelRefusal::new(
RefusalClass::TangentNodeSingularity,
KernelStage::Intersect,
format!(
"boolean: unsupported singular/tangent-node surface intersection \
between faces {} and {}: the section through the tangent node at \
({:.6},{:.6},{:.6}) could not be marched ({error})",
first.face.id, second.face.id, node.x, node.y, node.z
),
));
}
(marched, _) => marched,
};
let mut branches = marched
.map_err(|error| {
format!(
"{error} (marching faces {} and {})",
first.face.id, second.face.id
)
}).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
profile.march += profile.lap(&mut lap_start);
let rescue_started = profile.enabled.then(Instant::now);
if branches.is_empty()
&& !seed_points.is_empty()
&& pair_classification.relation == SurfacePairRelation::Candidate
&& std::env::var("BREP_MARCH_SWAP_RESCUE").as_deref() != Ok("0")
{
let swapped = intersect_surfaces(
second_march,
first_march,
&SurfaceIntersectionOptions {
tolerance: options.tolerance,
maximum_step: march_maximum_step(
&pair_classification,
options.maximum_ssi_step,
builder.scale,
),
seed_points: seed_points.clone(),
..Default::default()
},
)
.unwrap_or_default();
if debug_pairs {
eprintln!(
"pair {}x{}: MARCH-SWAP RESCUE attempt -> {} branches, pts {:?}",
first.face.id,
second.face.id,
swapped.len(),
swapped.iter().map(|b| b.points.len()).collect::<Vec<_>>()
);
}
if !swapped.is_empty() {
branches = swapped;
}
}
if !branches.is_empty()
&& !seed_points.is_empty()
&& min_seed_tangency < 0.5
&& pair_classification.relation == SurfacePairRelation::Candidate
&& std::env::var("BREP_MARCH_SWAP_CLIP_RESCUE").as_deref() != Ok("0")
{
let swapped = intersect_surfaces(
second_march,
first_march,
&SurfaceIntersectionOptions {
tolerance: options.tolerance,
maximum_step: march_maximum_step(
&pair_classification,
options.maximum_ssi_step,
builder.scale,
),
seed_points: seed_points.clone(),
..Default::default()
},
)
.unwrap_or_default();
if !swapped.is_empty() && swapped.len() == branches.len() {
let totals = (|| -> Result<(f64, f64), KernelRefusal> {
let mut fwd = 0.0;
for b in &branches {
let refined = insert_seed_points_into_branch(
&b.points,
&seed_points,
options.tolerance,
);
for run in clip_branch_to_trims(&refined, first, second)? {
fwd += run
.windows(2)
.map(|p| p[1].sub(p[0]).length())
.sum::<f64>();
}
}
let mut swp = 0.0;
for b in &swapped {
let refined = insert_seed_points_into_branch(
&b.points,
&seed_points,
options.tolerance,
);
for run in clip_branch_to_trims(&refined, first, second)? {
swp += run
.windows(2)
.map(|p| p[1].sub(p[0]).length())
.sum::<f64>();
}
}
Ok((fwd, swp))
})();
if let Ok((fwd_clip, swp_clip)) = totals {
let margin = fwd_clip.max(swp_clip) * 1.0e-3;
let adopt = swp_clip > fwd_clip + margin;
if debug_pairs {
eprintln!(
"pair {}x{}: CLIP-SWAP RESCUE fwd_clip={:.6} swp_clip={:.6} margin={:.6}{}",
first.face.id,
second.face.id,
fwd_clip,
swp_clip,
margin,
if adopt { " ADOPT" } else { "" }
);
}
if adopt {
branches = swapped;
}
}
}
}
if let Some(started) = rescue_started {
profile.rescue_march += started.elapsed();
}
if branches.iter().any(|branch| branch.points.len() >= 2) {
section_evidence = true;
}
if debug_pairs {
eprintln!(
"pair {}x{}: MARCH {} branches, pts {:?}",
first.face.id,
second.face.id,
branches.len(),
branches.iter().map(|b| b.points.len()).collect::<Vec<_>>()
);
}
for branch in branches {
let refined_points =
insert_seed_points_into_branch(&branch.points, &seed_points, options.tolerance);
let clip_started = profile.enabled.then(Instant::now);
let runs = clip_branch_to_trims(&refined_points, first, second)?;
if let Some(started) = clip_started {
profile.clip += started.elapsed();
}
for run in runs {
if debug_pairs {
eprintln!(
"pair {}x{}: clip run len_pts={} length={:.4e}",
first.face.id,
second.face.id,
run.len(),
run.windows(2)
.map(|pair| pair[1].sub(pair[0]).length())
.sum::<f64>()
);
}
if run.len() < 2 {
continue;
}
let length: f64 = run
.windows(2)
.map(|pair| pair[1].sub(pair[0]).length())
.sum();
if length <= options.tolerance * 100.0 {
continue;
}
let shared_started = profile.enabled.then(Instant::now);
let follows_shared = branch_follows_shared_boundary(
&run,
first,
second,
&builder.edges,
options.tolerance,
builder.scale,
)?;
if let Some(started) = shared_started {
profile.shared_boundary += started.elapsed();
}
if follows_shared {
if debug_pairs {
eprintln!(
"pair {}x{}: run dropped (follows shared boundary)",
first.face.id, second.face.id
);
}
continue;
}
if let Some(census) = pair_census.as_mut() {
census.record_stations(&run);
}
let pieces_before = builder.pieces.len();
let chunk_points = options.fit_chunk_points.unwrap_or(run.len()).max(2);
let mut start = 0;
while start + 1 < run.len() {
let end = (start + chunk_points - 1).min(run.len() - 1);
let fit_started = profile.enabled.then(Instant::now);
let fit_tolerance = options.tolerance.max(1e-7);
let mut section: Vec<Vec3> = run[start..=end].to_vec();
let slivers = even_sliver_ends(&mut section, &first.face.surface, &second.face.surface, options.tolerance)
.or_refuse(KernelStage::Intersect, "csg.imprint.driver.sliver_end")?;
if slivers > 0 {
if let Ok(path) = std::env::var("BREP_SECTION_SLIVER_CENSUS") {
use std::io::Write;
if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
let _ = writeln!(file, "sliver-end census: pair {}x{} slivers {slivers} stations {}", first.face.id, second.face.id, section.len());
}
}
}
let (mut fit, recovered) = crate::fit::fit_polyline_of_degree_with_recovery(
§ion,
fit_tolerance,
options.maximum_fit_points,
options.local_fit,
section_fit_degree(),
).or_refuse(KernelStage::Intersect, "csg.imprint.driver")?;
let refine_gate = section_refine_gate()
.or_else(|| recovered.then_some(1.0)).or_else(|| {
(paired_grazing_crossings
&& std::env::var("BREP_GRAZE_SECTION_REFINE").as_deref() != Ok("0"))
.then_some(1.0)
});
if let Some(gate) = refine_gate {
let stations_before = fit.kept.len();
let (refined, worst, unrefined, rounds, exit) = refine_section_against_carriers(
&mut section,
fit,
&first.face.surface,
&second.face.surface,
fit_tolerance,
options.tolerance,
options.local_fit,
gate * fit_tolerance,
).or_refuse(KernelStage::Intersect, "csg.imprint.driver.midspan")?;
if let Ok(path) = std::env::var("BREP_SECTION_REFINE_CENSUS") {
use std::io::Write;
if let Ok(mut file) = std::fs::OpenOptions::new().create(true).append(true).open(path) {
let _ = writeln!(
file,
"section-refine census: pair {}x{} exit {:?} rounds {} stations {} -> {} unrefined {:.3e} worst {:.3e}",
first.face.id, second.face.id, exit, rounds, stations_before, refined.kept.len(), unrefined, worst
);
}
}
if debug_pairs && (rounds > 0 || !matches!(exit, MidspanExit::Converged | MidspanExit::BelowGate)) {
eprintln!(
"pair {}x{}: mid-span refinement {:?} after {} round(s): {} stations, worst {:.3e}",
first.face.id, second.face.id, exit, rounds, section.len(), worst
);
}
fit = refined;
}
if let Some(started) = fit_started {
profile.fit += started.elapsed();
}
if let Some(census) = pair_census.as_mut() {
census.record_fit(&fit);
}
let process_started = profile.enabled.then(Instant::now);
builder.process_curve(
fit.curve,
first,
second,
&[first, second],
&[first, second],
true,
)?;
if let Some(started) = process_started {
profile.process += started.elapsed();
}
start = end;
}
for piece in &builder.pieces[pieces_before..] {
builder.marched_pieces.insert(piece.id);
}
if debug_pairs {
eprintln!(
"pair {}x{}: run -> {} pieces",
first.face.id,
second.face.id,
builder.pieces.len() - pieces_before
);
}
}
}
profile.clip_and_fit += profile.lap(&mut lap_start);
}
}
profile.report();
let mut tail_lap: Option<Instant> = None;
let mut tail_ms = Vec::<(&str, f64)>::new();
let mut lap = |name: &'static str, started: &mut Option<Instant>, tail_ms: &mut Vec<(&str, f64)>| {
if profile.enabled {
let now = Instant::now();
if let Some(previous) = *started {
tail_ms.push((name, (now - previous).as_secs_f64() * 1_000.0));
}
*started = Some(now);
}
};
lap("pairs", &mut tail_lap, &mut tail_ms);
if std::env::var("BREP_SELF_TOUCH_SPLIT").as_deref() != Ok("0") {
builder.split_self_touching_loops(&first_faces, &face_edge_lists, &cached_subcurve)?;
builder.split_self_touching_loops(&second_faces, &face_edge_lists, &cached_subcurve)?;
}
builder.exchange_piece_endpoint_junctions()?;
lap("self_touch", &mut tail_lap, &mut tail_ms);
let mut edge_splits = builder
.edge_splits
.into_iter()
.map(|((operand, edge_id), mut parameters)| {
parameters.sort_by(f64::total_cmp);
EdgeSplitRecord {
operand,
edge_id,
parameters,
}
})
.collect::<Vec<_>>();
edge_splits.sort_by_key(|record| (record.operand, record.edge_id));
let mut by_face = builder
.by_face
.into_iter()
.map(|(face, piece_ids)| FaceImprints {
operand: face.operand,
face_id: face.face_id,
piece_ids,
})
.collect::<Vec<_>>();
by_face.sort_by_key(|record| (record.operand, record.face_id));
let section_evidence = section_evidence || !builder.pieces.is_empty();
let marched_pieces = std::mem::take(&mut builder.marched_pieces);
let mut result = ImprintResultRecord {
tangent_nodes,
vertices: builder.vertices,
pieces: builder.pieces,
by_face,
edge_splits,
barrier_edges: builder.barrier_edges.into_iter().collect(),
section_evidence,
cosurface_pairs,
};
lap("collect", &mut tail_lap, &mut tail_ms);
if std::env::var("BREP_COINCIDENT_PIECE_MERGE").as_deref() != Ok("0") {
let weld = assembler_weld(options.tolerance).max(options.tolerance * 10.0);
let mut ends: Vec<[Vec3; 2]> = Vec::with_capacity(result.pieces.len());
for piece in &result.pieces {
let [d0, d1] = piece.curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
ends.push([
piece.curve.evaluate(d0).or_refuse(KernelStage::Intersect, "evaluate")?,
piece.curve.evaluate(d1).or_refuse(KernelStage::Intersect, "evaluate")?,
]);
}
let end_band = 4.0 * weld;
let ends_match = |a: &[Vec3; 2], b: &[Vec3; 2]| -> bool {
let near = |p: Vec3, q: Vec3| p.sub(q).length() <= end_band;
near(a[0], a[1])
|| near(b[0], b[1])
|| (near(a[0], b[0]) && near(a[1], b[1]))
|| (near(a[0], b[1]) && near(a[1], b[0]))
};
let mut removed: Vec<u64> = Vec::new();
let mut index = 0;
while index < result.pieces.len() {
let mut other = index + 1;
while other < result.pieces.len() {
let coincide = ends_match(&ends[index], &ends[other]) && {
let a = &result.pieces[index];
let b = &result.pieces[other];
max_curve_deviation(&a.curve, &b.curve)? <= weld
&& max_curve_deviation(&b.curve, &a.curve)? <= weld
};
if coincide {
let absorbed = result.pieces.remove(other);
ends.remove(other);
removed.push(absorbed.id);
let keeper = &mut result.pieces[index];
for pcurve in absorbed.pcurves {
if !keeper
.pcurves
.iter()
.any(|existing| {
existing.operand == pcurve.operand
&& existing.face_id == pcurve.face_id
})
{
keeper.pcurves.push(pcurve);
}
}
let keeper_id = keeper.id;
for record in &mut result.by_face {
if let Some(position) =
record.piece_ids.iter().position(|&id| id == absorbed.id)
{
if record.piece_ids.contains(&keeper_id) {
record.piece_ids.remove(position);
} else {
record.piece_ids[position] = keeper_id;
}
}
}
} else {
other += 1;
}
}
index += 1;
}
if !removed.is_empty() && std::env::var("BREP_DEBUG_BOOL").is_ok() {
eprintln!("coincident-piece merge: absorbed {:?}", removed);
}
}
lap("coincident_merge", &mut tail_lap, &mut tail_ms);
extend_truncated_sections(
&mut result,
&face_edge_lists,
solid_a,
solid_b,
&charts,
options.tolerance,
)?;
lap("extend_truncated", &mut tail_lap, &mut tail_ms);
canonicalize_imprint_junctions(&mut result, solid_a, solid_b, &charts, options.tolerance)?;
lap("canonicalize", &mut tail_lap, &mut tail_ms);
let dissolved = dissolve_section_origin_vertices(
&mut result,
&marched_pieces,
solid_a,
solid_b,
&charts,
options.tolerance,
)?;
if dissolved > 0 && std::env::var("BREP_DEBUG_BOOL").is_ok() {
eprintln!("origin-dissolve: {dissolved} parameterization vertex/vertices removed");
}
lap("origin_dissolve", &mut tail_lap, &mut tail_ms);
reuse_boundary_section_edges(
&mut result,
&face_edge_lists,
solid_a,
solid_b,
options.tolerance,
)?;
lap("reuse_boundary", &mut tail_lap, &mut tail_ms);
report_graze_contacts(&result, &face_edge_lists, solid_a, solid_b, options.tolerance)?;
if profile.enabled {
let line = tail_ms
.iter()
.map(|(name, ms)| format!("{name}={ms:.2}"))
.collect::<Vec<_>>()
.join(" ");
eprintln!("imprint.stages {line}");
}
Ok(result)
}
fn report_graze_contacts(
result: &ImprintResultRecord,
face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
solid_a: &BrepSolid,
solid_b: &BrepSolid,
tolerance: f64,
) -> Result<(), KernelRefusal> {
if std::env::var("BREP_DEBUG_GRAZE").as_deref() != Ok("1") {
return Ok(());
}
let raw_extent = raw_solid_extent(solid_a).max(raw_solid_extent(solid_b));
let (_, band_cap) = residual_merge_bands(raw_extent, tolerance);
const SAMPLES: usize = 17;
for piece in &result.pieces {
let [t0, t1] = [piece.t0, piece.t1];
if !(t1 > t0) {
continue;
}
for key in piece.support_faces {
let Some(edges) = face_edge_lists.get(&key) else {
continue;
};
for edge in edges {
if edge.degenerate {
continue;
}
let mut in_band = 0usize;
let mut min_dev = f64::INFINITY;
let mut max_dev_in_band = 0.0f64;
let mut span = 0.0f64;
let mut prev: Option<(bool, Vec3)> = None;
for k in 0..SAMPLES {
let t = t0 + (t1 - t0) * k as f64 / (SAMPLES - 1) as f64;
let point = piece.curve.evaluate(t).or_refuse(KernelStage::Intersect, "evaluate")?;
let deviation = project_point_to_curve(&edge.curve, point).or_refuse(KernelStage::Intersect, "project_point_to_curve")?.distance;
min_dev = min_dev.min(deviation);
let inside = deviation <= band_cap;
if inside {
in_band += 1;
max_dev_in_band = max_dev_in_band.max(deviation);
if let Some((true, prev_point)) = prev {
span += point.sub(prev_point).length();
}
}
prev = Some((inside, point));
}
if in_band >= 3 && span > (4.0 * min_dev).max(assembler_weld(tolerance)) {
eprintln!(
"graze: piece {} sup=[{}:{},{}:{}] ~ edge {}:{} span={:.3e} band=[{:.3e},{:.3e}] samples_in_band={}/{}",
piece.id,
piece.support_faces[0].operand,
piece.support_faces[0].face_id,
piece.support_faces[1].operand,
piece.support_faces[1].face_id,
key.operand,
edge.id,
span,
min_dev,
max_dev_in_band,
in_band,
SAMPLES
);
}
}
}
}
Ok(())
}
fn pair_normals_parallel_at(
first: TaggedFace<'_>,
second: TaggedFace<'_>,
point: Vec3,
) -> Result<bool, KernelRefusal> {
let mut normals = [Vec3::default(); 2];
for (slot, face) in normals.iter_mut().zip([first, second]) {
let projection = project_point_to_surface(&face.face.surface, point)
.or_refuse(KernelStage::Intersect, "project_point_to_surface")?;
let Ok(normal) = face.face.surface.normal(projection.u, projection.v) else {
return Ok(true);
};
*slot = normal;
}
Ok(normals[0].cross(normals[1]).length() <= crate::TRANSVERSE_SEED_CROSS)
}