use crate::{KernelRefusal, KernelStage, OrRefuse, RefusalClass};
use super::*;
pub(super) fn branch_follows_shared_boundary(
points: &[Vec3],
first: TaggedFace<'_>,
second: TaggedFace<'_>,
edges: &HashMap<(u8, u64), &EdgeRecord>,
tolerance: f64,
scale: f64,
) -> Result<bool, KernelRefusal> {
let Some(last) = points.last() else {
return Ok(false);
};
let weld_limit = assembler_weld(tolerance);
let step = (points.len() / 16).max(1);
for face in [first, second] {
let face_edges = face_edges(face, edges)?;
for point in points.iter().step_by(step).chain(std::iter::once(last)) {
let first_projection = project_point_to_surface(&first.face.surface, *point).or_refuse(KernelStage::Intersect, "project_point_to_surface")?;
let second_projection = project_point_to_surface(&second.face.surface, *point).or_refuse(KernelStage::Intersect, "project_point_to_surface")?;
let first_normal = first
.face
.surface
.normal(first_projection.u, first_projection.v)
.ok();
let second_normal = second
.face
.surface
.normal(second_projection.u, second_projection.v)
.ok();
let tangential = match (first_normal, second_normal) {
(Some(a), Some(b)) => a.cross(b).length() <= 1e-2,
_ => true,
};
let limit = if tangential {
(tolerance.max(1e-7) * merge_scale(scale))
.sqrt()
.max(weld_limit)
} else {
weld_limit
};
let mut near = false;
let mut best = f64::INFINITY;
for edge in &face_edges {
if edge.degenerate {
continue;
}
let projection = project_point_to_curve(&edge.curve, *point).or_refuse(KernelStage::Intersect, "project_point_to_curve")?;
let low = edge.t0.min(edge.t1);
let high = edge.t0.max(edge.t1);
let on = edge.curve.evaluate(projection.u.clamp(low, high)).or_refuse(KernelStage::Intersect, "evaluate")?;
let distance = on.sub(*point).length();
if distance < best {
best = distance;
}
if distance <= limit {
near = true;
break;
}
}
if !near {
if std::env::var("BREP_DEBUG_BRANCH").is_ok() {
eprintln!(
"branch reject: face {} best={:.3e} limit={:.3e} point=({:.6},{:.6},{:.6})",
face.face.id, best, limit, point.x, point.y, point.z
);
}
return Ok(false);
}
}
}
Ok(true)
}
pub(super) fn insert_seed_points_into_branch(points: &[Vec3], seeds: &[Vec3], tolerance: f64) -> Vec<Vec3> {
if points.len() < 2
|| seeds.is_empty()
|| std::env::var("BREP_SEED_CLIP_REFINE").as_deref() == Ok("0")
{
return points.to_vec();
}
let mut insertions: Vec<Vec<(f64, Vec3)>> = vec![Vec::new(); points.len() - 1];
for seed in seeds {
let mut best: Option<(usize, f64, f64)> = None; for (index, pair) in points.windows(2).enumerate() {
let direction = pair[1].sub(pair[0]);
let length_squared = direction.dot(direction);
if length_squared <= 0.0 {
continue;
}
let fraction = (seed.sub(pair[0]).dot(direction) / length_squared).clamp(0.0, 1.0);
let distance = seed.sub(pair[0].add(direction.scale(fraction))).length();
if best.is_none_or(|(_, d, _)| distance < d) {
best = Some((index, distance, fraction));
}
}
let Some((index, distance, fraction)) = best else {
continue;
};
let segment_length = points[index + 1].sub(points[index]).length();
if distance > (tolerance * 10.0).max(segment_length * 0.1) {
continue; }
let near_end = fraction * segment_length <= tolerance
|| (1.0 - fraction) * segment_length <= tolerance;
if near_end {
continue;
}
if insertions[index].iter().any(|(_, queued)| queued.sub(*seed).length() <= tolerance) {
continue;
}
insertions[index].push((fraction, *seed));
}
if insertions.iter().all(Vec::is_empty) {
return points.to_vec();
}
let mut refined = Vec::with_capacity(points.len() + seeds.len());
for (index, point) in points.iter().enumerate() {
refined.push(*point);
if index < insertions.len() {
let segment = &mut insertions[index];
segment.sort_by(|a, b| a.0.total_cmp(&b.0));
refined.extend(segment.iter().map(|(_, seed)| *seed));
}
}
refined
}
pub(super) fn inside_both_trims(
point: Vec3,
first: TaggedFace<'_>,
second: TaggedFace<'_>,
) -> Result<bool, KernelRefusal> {
inside_both_trims_traced(point, first, second, false)
}
fn inside_both_trims_traced(
point: Vec3,
first: TaggedFace<'_>,
second: TaggedFace<'_>,
debug_clip: bool,
) -> Result<bool, KernelRefusal> {
for face in [first, second] {
let projection = project_point_to_surface(face.chart(), point).or_refuse(KernelStage::Intersect, "project_point_to_surface")?;
let status = parameter_point_in_face(
face.face,
Vec2 {
x: projection.u,
y: projection.v,
},
1e-6,
).or_refuse(KernelStage::Intersect, "csg.imprint.sections")?;
if debug_clip {
eprintln!(
"clip {}x{}: p=({:.5},{:.5},{:.5}) on face {} uv=({:.5},{:.5}) dist={:.2e} {:?}",
first.face.id,
second.face.id,
point.x,
point.y,
point.z,
face.face.id,
projection.u,
projection.v,
projection.distance,
status
);
}
if status == PolygonClass::Outside {
return Ok(false);
}
}
Ok(true)
}
pub(super) fn clip_branch_to_trims(
points: &[Vec3],
first: TaggedFace<'_>,
second: TaggedFace<'_>,
) -> Result<Vec<Vec<Vec3>>, KernelRefusal> {
let count = points.len();
if count < 2 {
return Ok(Vec::new());
}
let debug_clip = std::env::var("BREP_DEBUG_CLIP").is_ok_and(|value| {
value == "all"
|| value == first.face.id.to_string()
|| value == second.face.id.to_string()
});
let inside_both = |point: Vec3| -> Result<bool, KernelRefusal> {
inside_both_trims_traced(point, first, second, debug_clip)
};
let step = (count / 256).max(1);
let mut keep = vec![false; count];
let mut index = 0;
loop {
let sample = index.min(count - 1);
if inside_both(points[sample])? {
let low = sample.saturating_sub(step);
let high = (sample + step).min(count - 1);
for flag in &mut keep[low..=high] {
*flag = true;
}
}
if sample == count - 1 {
break;
}
index += step;
}
let mut runs = Vec::new();
let mut current: Vec<Vec3> = Vec::new();
for (point, kept) in points.iter().zip(&keep) {
if *kept {
current.push(*point);
} else if !current.is_empty() {
runs.push(std::mem::take(&mut current));
}
}
if !current.is_empty() {
runs.push(current);
}
Ok(runs)
}
fn overlap_hull_enabled() -> bool {
switch!("BREP_IMPRINT_OVERLAP_HULL", true)
}
thread_local! {
static OVERLAP_HULL_SKIPS: std::cell::Cell<u64> = const { std::cell::Cell::new(0) };
}
pub(super) fn take_overlap_hull_skips() -> u64 {
OVERLAP_HULL_SKIPS.with(|count| count.replace(0))
}
pub(super) fn curve_overlaps_edge(curve: &NurbsCurve, edge: &EdgeRecord, limit: f64) -> Result<bool, KernelRefusal> {
let [c0, c1] = curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
let low = edge.t0.min(edge.t1);
let high = edge.t0.max(edge.t1);
let hull = if overlap_hull_enabled() {
edge.curve.control_points.iter().map(|control| control.point()).collect::<Result<Vec<_>, _>>().ok()
.map(|points| Aabb::from_points(points).expanded(limit))
} else {
None
};
let segment = if segment_overlap_enabled() { straight_segment(&edge.curve) } else { None };
let [k0, k1] = edge.curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
let mut on = 0;
for fraction in [0.1, 0.3, 0.5, 0.7, 0.9] {
let point = curve.evaluate(c0 + (c1 - c0) * fraction).or_refuse(KernelStage::Intersect, "evaluate")?;
if let Some(segment) = &segment {
if segment.length_squared > 0.0 && k1 > k0 {
let along = (point.sub(segment.start).dot(segment.direction) / segment.length_squared).clamp(0.0, 1.0);
let parameter = (k0 + (k1 - k0) * along).clamp(low, high);
let nearest = segment.start.add(segment.direction.scale((parameter - k0) / (k1 - k0)));
if nearest.sub(point).length() <= limit {
on += 1;
}
continue;
}
}
if let Some(hull) = &hull {
if !hull.contains(point) {
OVERLAP_HULL_SKIPS.with(|count| count.set(count.get() + 1));
continue;
}
}
let projection = project_point_to_curve(&edge.curve, point).or_refuse(KernelStage::Intersect, "project_point_to_curve")?;
let nearest = edge.curve.evaluate(projection.u.clamp(low, high)).or_refuse(KernelStage::Intersect, "evaluate")?;
if nearest.sub(point).length() <= limit {
on += 1;
}
}
Ok(on >= 3)
}
pub(super) fn max_curve_deviation(a: &NurbsCurve, b: &NurbsCurve) -> Result<f64, KernelRefusal> {
max_curve_deviation_until(a, b, f64::INFINITY)
}
fn max_curve_deviation_until(a: &NurbsCurve, b: &NurbsCurve, limit: f64) -> Result<f64, KernelRefusal> {
let [d0, d1] = a.domain().or_refuse(KernelStage::Intersect, "domain")?;
if let Some(segment) = segment_deviation_on().then(|| straight_segment(b)).flatten() {
let stations: &[f64] = if straight_segment(a).is_some() { &[0.0, 1.0] } else { &SAMPLE_FRACTIONS };
let mut worst = 0.0f64;
for fraction in stations {
let point = a.evaluate(d0 + (d1 - d0) * fraction).or_refuse(KernelStage::Intersect, "evaluate")?;
worst = worst.max(segment.distance(point));
}
return Ok(worst);
}
let mut worst = 0.0f64;
for k in 0..=32 {
let t = d0 + (d1 - d0) * (k as f64 / 32.0);
let point = a.evaluate(t).or_refuse(KernelStage::Intersect, "evaluate")?;
worst = worst.max(project_point_to_curve(b, point).or_refuse(KernelStage::Intersect, "project_point_to_curve")?.distance);
if worst > limit {
break;
}
}
Ok(worst)
}
fn segment_deviation_on() -> bool {
switch!("BREP_IMPRINT_SEGMENT_DEVIATION", true)
}
pub(super) fn segment_overlap_enabled() -> bool {
switch!("BREP_IMPRINT_SEGMENT_OVERLAP", true)
}
const SAMPLE_FRACTIONS: [f64; 33] = {
let mut fractions = [0.0f64; 33];
let mut k = 0;
while k < 33 {
fractions[k] = k as f64 / 32.0;
k += 1;
}
fractions
};
struct Segment {
start: Vec3,
direction: Vec3,
length_squared: f64,
}
impl Segment {
fn distance(&self, point: Vec3) -> f64 {
let offset = point.sub(self.start);
let t = if self.length_squared > 0.0 { (offset.dot(self.direction) / self.length_squared).clamp(0.0, 1.0) } else { 0.0 };
offset.sub(self.direction.scale(t)).length()
}
}
fn straight_segment(curve: &NurbsCurve) -> Option<Segment> {
if curve.degree != 1 || curve.control_points.len() != 2 {
return None;
}
let [first, second] = [curve.control_points[0], curve.control_points[1]];
if first.w <= 0.0 || (first.w - second.w).abs() > 1e-12 * first.w.abs() {
return None;
}
let (start, end) = (first.point().ok()?, second.point().ok()?);
let direction = end.sub(start);
Some(Segment { start, direction, length_squared: direction.dot(direction) })
}
fn curve_param_subrange(curve: &NurbsCurve, lo: f64, hi: f64) -> Result<NurbsCurve, KernelRefusal> {
let [d0, d1] = curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
let epsilon = ((d1 - d0).abs() * 1e-9).max(2e-9);
let mut result = curve.clone();
if lo > d0 + epsilon && lo < d1 - epsilon {
result = result.split(lo).or_refuse(KernelStage::Intersect, "split")?.1;
}
let domain = result.domain().or_refuse(KernelStage::Intersect, "domain")?;
if hi < domain[1] - epsilon && hi > domain[0] + epsilon {
result = result.split(hi).or_refuse(KernelStage::Intersect, "split")?.0;
}
Ok(result)
}
fn reuse_hull_gate_enabled() -> bool {
switch!("BREP_SHARED_SECTION_HULL_GATE", true)
}
fn shared_section_straddle_enabled() -> bool {
switch!("BREP_SHARED_SECTION_STRADDLE", true)
}
pub(super) fn reuse_boundary_section_edges(
result: &mut ImprintResultRecord,
face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
solid_a: &BrepSolid,
solid_b: &BrepSolid,
tolerance: f64,
) -> Result<(), KernelRefusal> {
if std::env::var("BREP_SHARED_SECTION_EDGE").as_deref() == Ok("0") {
return Ok(());
}
let debug = std::env::var("BREP_DEBUG_SHARED_SECTION").is_ok();
let endpoint_gate = assembler_weld(tolerance);
let reuse_hull_gate = reuse_hull_gate_enabled();
let hull_admits = |edge: &EdgeRecord, ps: Vec3, pe: Vec3| -> bool {
let Ok(points) = edge.curve.control_points.iter().map(|control| control.point()).collect::<Result<Vec<_>, _>>() else {
return true;
};
let hull = Aabb::from_points(points).expanded(endpoint_gate);
hull.contains(ps) && hull.contains(pe)
};
let scale = solid_scale(solid_a).max(solid_scale(solid_b));
let span_band = (SHARED_SECTION_BAND_FRACTION * scale).max(endpoint_gate);
let piece_point: HashMap<u64, Vec3> =
result.vertices.iter().map(|v| (v.id, v.point)).collect();
let face_surface = |key: &FaceKey| -> Option<&NurbsSurface> {
let solid = if key.operand == 0 { solid_a } else { solid_b };
solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.find(|face| face.id == key.face_id)
.map(|face| &face.surface)
};
struct Reuse {
piece_index: usize,
owning: FaceKey,
edge_id: u64,
arc: NurbsCurve,
aligned: bool,
redundant: Vec<FaceKey>,
}
let mut decisions: Vec<Reuse> = Vec::new();
for (piece_index, piece) in result.pieces.iter().enumerate() {
if piece.shared_edge.is_some() {
continue;
}
let (Some(&ps), Some(&pe)) = (
piece_point.get(&piece.start_vertex_id),
piece_point.get(&piece.end_vertex_id),
) else {
continue;
};
if ps.sub(pe).length() <= endpoint_gate {
if std::env::var("BREP_SHARED_SECTION_RING").as_deref() != Ok("0") {
let mut chosen: Option<Reuse> = None;
let mut best_score = f64::INFINITY;
for face_key in &piece.support_faces {
let Some(edges) = face_edge_lists.get(face_key) else {
continue;
};
for edge in edges {
if edge.degenerate {
continue;
}
let (Ok(es), Ok(ee)) =
(edge.curve.evaluate(edge.t0), edge.curve.evaluate(edge.t1))
else {
continue;
};
if es.sub(ee).length() > endpoint_gate {
continue; }
let Ok(ring) = edge_subcurve(edge) else {
continue;
};
let limit = if reuse_hull_gate { endpoint_gate } else { f64::INFINITY };
let Ok(dev_a) = max_curve_deviation_until(&piece.curve, &ring, limit) else {
continue;
};
if reuse_hull_gate && dev_a > endpoint_gate {
continue;
}
let Ok(dev_b) = max_curve_deviation_until(&ring, &piece.curve, limit) else {
continue;
};
let deviation = dev_a.max(dev_b);
if deviation > endpoint_gate || deviation >= best_score {
continue;
}
let Ok(projection) = project_point_to_curve(&ring, ps) else {
continue;
};
let (Ok((_, piece_tangent)), Ok((_, ring_tangent))) =
(piece.curve.deriv1(piece.t0), ring.deriv1(projection.u))
else {
continue;
};
best_score = deviation;
chosen = Some(Reuse {
piece_index,
owning: *face_key,
edge_id: edge.id,
arc: ring.clone(),
aligned: piece_tangent.dot(ring_tangent) >= 0.0,
redundant: vec![*face_key],
});
}
}
if let Some(reuse) = chosen {
if debug {
eprintln!(
"shared-section RING piece {} -> edge {}:{} dev={best_score:.3e}",
piece.id, reuse.owning.operand, reuse.edge_id
);
}
decisions.push(reuse);
}
}
continue;
}
let mut chosen: Option<Reuse> = None;
let mut best_score = f64::INFINITY;
let mut redundant: Vec<FaceKey> = Vec::new();
let mut redundant_arcs: Vec<(FaceKey, NurbsCurve, f64)> = Vec::new();
for face_key in &piece.support_faces {
let Some(edges) = face_edge_lists.get(face_key) else {
continue;
};
for edge in edges {
if edge.degenerate {
continue;
}
if reuse_hull_gate && !hull_admits(edge, ps, pe) {
continue;
}
if profile_enabled() {
imprint_split_add(|split| split.reuse_edges += 1);
}
let (Ok(proj_s), Ok(proj_e)) = (
project_point_to_curve(&edge.curve, ps),
project_point_to_curve(&edge.curve, pe),
) else {
continue;
};
if proj_s.distance > endpoint_gate || proj_e.distance > endpoint_gate {
continue;
}
let lo_t = edge.t0.min(edge.t1);
let hi_t = edge.t0.max(edge.t1);
let margin = (hi_t - lo_t).abs() * 1e-6 + 1e-9;
if proj_s.u < lo_t - margin
|| proj_s.u > hi_t + margin
|| proj_e.u < lo_t - margin
|| proj_e.u > hi_t + margin
{
continue;
}
let aligned = proj_s.u <= proj_e.u;
let lo = proj_s.u.min(proj_e.u);
let hi = proj_s.u.max(proj_e.u);
if hi - lo <= margin {
continue; }
let Ok(mut arc) = curve_param_subrange(&edge.curve, lo, hi) else {
continue;
};
if !aligned {
let Ok(reversed) = arc.reversed() else {
continue;
};
arc = reversed;
}
let Ok(arc) = snap_curve_ends(&arc, ps, pe) else {
continue;
};
let sec_to_arc = max_curve_deviation(&piece.curve, &arc)?;
let arc_to_sec = max_curve_deviation(&arc, &piece.curve)?;
if debug {
eprintln!(
"shared-section? piece {} sup=[{}:{},{}:{}] vs edge {}:{} \
proj=({:.2e},{:.2e}) sec->arc={:.3e} arc->sec={:.3e} band={:.3e}",
piece.id,
piece.support_faces[0].operand,
piece.support_faces[0].face_id,
piece.support_faces[1].operand,
piece.support_faces[1].face_id,
face_key.operand,
edge.id,
proj_s.distance,
proj_e.distance,
sec_to_arc,
arc_to_sec,
span_band,
);
}
let span_dev = sec_to_arc.max(arc_to_sec);
if span_dev > endpoint_gate && span_dev <= span_band {
if !redundant.contains(face_key) {
redundant.push(*face_key);
}
match redundant_arcs.iter_mut().find(|(key, _, _)| key == face_key) {
Some(entry) if span_dev < entry.2 => {
entry.1 = arc.clone();
entry.2 = span_dev;
}
Some(_) => {}
None => redundant_arcs.push((*face_key, arc.clone(), span_dev)),
}
if span_dev < best_score {
best_score = span_dev;
chosen = Some(Reuse {
piece_index,
owning: *face_key,
edge_id: edge.id,
arc,
aligned,
redundant: Vec::new(),
});
}
}
}
}
if let Some(mut reuse) = chosen {
if shared_section_straddle_enabled() && redundant.len() > 1 {
let bar = endpoint_gate.max(best_score);
let mut kept = Vec::with_capacity(redundant.len());
for key in redundant {
if key == reuse.owning {
kept.push(key);
continue;
}
let Some((_, other, _)) = redundant_arcs.iter().find(|(k, _, _)| *k == key) else {
continue;
};
let apart = match (max_curve_deviation(other, &reuse.arc), max_curve_deviation(&reuse.arc, other)) {
(Ok(a), Ok(b)) => a.max(b),
_ => f64::INFINITY,
};
if debug {
eprintln!(
"shared-section STRADDLE? piece {} face {}:{} copies apart={apart:.3e} bar={bar:.3e}",
piece.id, key.operand, key.face_id
);
}
if apart <= bar {
kept.push(key);
}
}
redundant = kept;
}
reuse.redundant = if std::env::var("BREP_SHARED_SECTION_BOTH").as_deref() == Ok("0") {
vec![reuse.owning]
} else {
redundant
};
decisions.push(reuse);
}
}
for reuse in decisions {
let piece = &result.pieces[reuse.piece_index];
let mut new_pcurves = Vec::with_capacity(piece.pcurves.len());
let mut ok = true;
for facepc in &piece.pcurves {
let key = FaceKey {
operand: facepc.operand,
face_id: facepc.face_id,
};
if reuse.redundant.contains(&key) {
continue; }
let Some(surface) = face_surface(&key) else {
ok = false;
break;
};
if std::env::var("BREP_SHARED_SECTION_CARRIER_FLOOR").as_deref() != Ok("0") {
let [lo, hi] = reuse.arc.domain().or_refuse(KernelStage::Intersect, "domain")?;
let mut off = 0.0f64;
for knots in reuse.arc.knots.windows(2) {
let a = knots[0].max(lo);
let b = knots[1].min(hi);
if b <= a {
continue;
}
for station in 0..=8 {
let point = reuse.arc.evaluate(a + (b - a) * station as f64 / 8.0)
.or_refuse(KernelStage::Intersect, "evaluate")?;
off = off.max(project_point_to_surface(surface, point)
.or_refuse(KernelStage::Intersect, "project_point_to_surface")?.distance);
}
}
if debug {
eprintln!("shared-section CARRIER piece {} face {}:{} off={off:.3e}", piece.id, key.operand, key.face_id);
}
if off > tolerance.max(1e-7) {
ok = false;
break;
}
}
match build_pcurve_on_surface(surface, &reuse.arc) {
Ok(pcurve) => new_pcurves.push(FacePcurve {
operand: key.operand,
face_id: key.face_id,
pcurve,
}),
Err(_) => {
ok = false;
break;
}
}
}
let Ok([nt0, nt1]) = reuse.arc.domain() else {
continue;
};
let two_sided = !piece.pcurves.is_empty()
&& piece.pcurves.iter().all(|facepc| {
reuse.redundant.contains(&FaceKey {
operand: facepc.operand,
face_id: facepc.face_id,
})
});
if !ok || (new_pcurves.is_empty() && !two_sided) {
if debug {
eprintln!(
"shared-section SKIP piece {} (pcurve rebuild on kept face failed)",
result.pieces[reuse.piece_index].id
);
}
continue;
}
let piece = &mut result.pieces[reuse.piece_index];
let piece_id = piece.id;
piece.curve = reuse.arc;
piece.t0 = nt0;
piece.t1 = nt1;
piece.pcurves = new_pcurves;
piece.shared_edge = Some((reuse.owning.operand, reuse.edge_id, reuse.aligned));
for key in &reuse.redundant {
if let Some(entry) = result
.by_face
.iter_mut()
.find(|f| f.operand == key.operand && f.face_id == key.face_id)
{
entry.piece_ids.retain(|id| *id != piece_id);
}
}
if debug {
eprintln!(
"shared-section REUSE piece {} -> boundary edge {}:{} sub-arc \
aligned={} (dropped redundant cut from {})",
piece_id,
reuse.owning.operand,
reuse.edge_id,
reuse.aligned,
reuse
.redundant
.iter()
.map(|key| format!("face {}:{}", key.operand, key.face_id))
.collect::<Vec<_>>()
.join(", "),
);
}
}
Ok(())
}
fn support_pair_key(a: FaceKey, b: FaceKey) -> (FaceKey, FaceKey) {
if (a.operand, a.face_id) <= (b.operand, b.face_id) {
(a, b)
} else {
(b, a)
}
}
pub(super) fn chord_off_carrier(
surface: &NurbsSurface,
o: Vec3,
d: Vec3,
) -> Result<f64, KernelRefusal> {
const STATIONS: usize = 32;
let mut worst = 0.0f64;
for station in 0..=STATIONS {
let fraction = station as f64 / STATIONS as f64;
let point = o.add(d.sub(o).scale(fraction));
worst = worst.max(
project_point_to_surface(surface, point)
.or_refuse(KernelStage::Intersect, "project_point_to_surface")?
.distance,
);
}
Ok(worst)
}
fn describe_trim_window(face: &FaceRecord) -> String {
let (Ok([u0, u1]), Ok([v0, v1])) = (face.surface.domain_u(), face.surface.domain_v()) else {
return "?".into();
};
let Ok((closed_u, closed_v)) = face.surface.closed_directions() else {
return "?".into();
};
let mut low = [f64::INFINITY; 2];
let mut high = [f64::NEG_INFINITY; 2];
for coedge in face.loops.iter().flat_map(|record| &record.coedges) {
for control in &coedge.pcurve.control_points {
let Ok(point) = control.point() else {
return "?".into();
};
low[0] = low[0].min(point.x);
high[0] = high[0].max(point.x);
low[1] = low[1].min(point.y);
high[1] = high[1].max(point.y);
}
}
let mut parts = Vec::new();
for (axis, closed, domain) in [(0usize, closed_u, [u0, u1]), (1, closed_v, [v0, v1])] {
let below = (domain[0] - low[axis]).max(0.0);
let above = (high[axis] - domain[1]).max(0.0);
if !closed || (below <= 0.0 && above <= 0.0) {
continue;
}
parts.push(format!(
"{}overhang[{:.3e},{:.3e}]{}",
if axis == 0 { "u" } else { "v" },
below,
above,
if high[axis] - low[axis] < domain[1] - domain[0] {
"discarded"
} else {
"full"
}
));
}
if parts.is_empty() {
"in-domain".into()
} else {
parts.join(",")
}
}
pub(super) fn extend_truncated_sections(
result: &mut ImprintResultRecord,
face_edge_lists: &HashMap<FaceKey, Vec<&EdgeRecord>>,
solid_a: &BrepSolid,
solid_b: &BrepSolid,
charts: &FaceCharts<'_>,
tolerance: f64,
) -> Result<usize, KernelRefusal> {
if std::env::var("BREP_EXTEND_TRUNCATED_SECTIONS").as_deref() == Ok("0") {
return Ok(0);
}
let debug = std::env::var("BREP_DEBUG_EXTEND_SECTIONS").is_ok();
let vpoint: HashMap<u64, Vec3> = result.vertices.iter().map(|v| (v.id, v.point)).collect();
let mut valence: HashMap<u64, usize> = HashMap::default();
let mut pair_endpoints: HashMap<(FaceKey, FaceKey), HashSet<u64>> = HashMap::default();
for piece in &result.pieces {
for vid in [piece.start_vertex_id, piece.end_vertex_id] {
*valence.entry(vid).or_insert(0) += 1;
}
let entry = pair_endpoints
.entry(support_pair_key(piece.support_faces[0], piece.support_faces[1]))
.or_default();
entry.insert(piece.start_vertex_id);
entry.insert(piece.end_vertex_id);
}
let surface_of = |key: FaceKey| -> Option<&NurbsSurface> {
let solid = if key.operand == 0 { solid_a } else { solid_b };
solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.find(|face| face.id == key.face_id)
.map(|face| chart_of(charts, key, face))
};
let on_boundary = |pt: Vec3, fkey: FaceKey| -> Result<bool, KernelRefusal> {
let tol = (tolerance * 100.0).max(1e-6) * (1.0 + pt.length());
if let Some(edges) = face_edge_lists.get(&fkey) {
for edge in edges {
if edge.degenerate {
continue;
}
if project_point_to_curve(&edge.curve, pt).or_refuse(KernelStage::Intersect, "project_point_to_curve")?.distance <= tol {
return Ok(true);
}
}
}
Ok(false)
};
let face_of = |key: FaceKey| -> Option<&FaceRecord> {
let solid = if key.operand == 0 { solid_a } else { solid_b };
solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.find(|face| face.id == key.face_id)
};
struct BridgeSpec {
f: FaceKey,
g: FaceKey,
o: u64,
d: u64,
d_param: Vec3,
}
let mut bridges: Vec<BridgeSpec> = Vec::new();
let mut claimed: HashSet<(u64, u64)> = HashSet::default();
for piece in &result.pieces {
let support = piece.support_faces;
for &(f, g) in &[(support[0], support[1]), (support[1], support[0])] {
let fg = support_pair_key(f, g);
for (d, d_is_start) in [(piece.start_vertex_id, true), (piece.end_vertex_id, false)] {
if valence.get(&d).copied().unwrap_or(0) != 1 {
continue;
}
let d_pt = vpoint[&d];
if on_boundary(d_pt, f)? || on_boundary(d_pt, g)? {
if debug {
eprintln!(
"extend: dangler v{d} F={}:{} G={}:{} REJECT on-boundary(f={},g={})",
f.operand,
f.face_id,
g.operand,
g.face_id,
on_boundary(d_pt, f)?,
on_boundary(d_pt, g)?
);
}
continue;
}
let plen = piece.curve.evaluate(piece.t0).or_refuse(KernelStage::Intersect, "evaluate")?.sub(piece.curve.evaluate(piece.t1).or_refuse(KernelStage::Intersect, "evaluate")?).length();
let span_bound = (plen * 5.0).max(tolerance * 100.0);
let mut best: Option<(f64, u64)> = None;
for other in &result.pieces {
if other.support_faces[0] != g && other.support_faces[1] != g {
continue;
}
for o in [other.start_vertex_id, other.end_vertex_id] {
if o == d {
continue;
}
if pair_endpoints.get(&fg).is_some_and(|set| set.contains(&o)) {
continue; }
let o_pt = vpoint[&o];
let gap = o_pt.sub(d_pt).length();
if gap <= tolerance * 10.0 || gap > span_bound {
continue;
}
if !on_boundary(o_pt, f)? {
continue;
}
if best.is_none_or(|(g0, _)| gap < g0) {
best = Some((gap, o));
}
}
}
let Some((gap, o)) = best else {
if debug {
eprintln!(
"extend: dangler v{d} F={}:{} G={}:{} REJECT no-orphan (span_bound={span_bound:.3e})",
f.operand, f.face_id, g.operand, g.face_id
);
}
continue;
};
if claimed.contains(&(o, d)) || claimed.contains(&(d, o)) {
continue;
}
let (Some(fsurf), Some(gsurf)) = (surface_of(f), surface_of(g)) else {
continue;
};
let o_pt = vpoint[&o];
let off_f = chord_off_carrier(fsurf, o_pt, d_pt)?;
let off_g = chord_off_carrier(gsurf, o_pt, d_pt)?;
let fit_tolerance = tolerance.max(1e-7);
if off_f.max(off_g) > fit_tolerance {
return Err(KernelRefusal::new(
RefusalClass::NonConvergence {
what: "truncated section bridge".into(),
},
KernelStage::Intersect,
format!(
"boolean: the section of faces {}:{} and {}:{} is truncated at \
({:.6}, {:.6}, {:.6}), {gap:.3e} short of the crossing at \
({:.6}, {:.6}, {:.6}), and the chord that would bridge it stands \
{:.3e} off face {}'s carrier and {:.3e} off face {}'s, against the \
fit tolerance {fit_tolerance:.1e} every marched section is held to. \
A chord that is not on both carriers is a wrong section, not a \
loose one.",
f.operand,
f.face_id,
g.operand,
g.face_id,
d_pt.x,
d_pt.y,
d_pt.z,
o_pt.x,
o_pt.y,
o_pt.z,
off_f,
f.face_id,
off_g,
g.face_id,
),
));
}
let f_pcurve = pcurve_for_piece(piece, f)?;
let [pd0, pd1] = f_pcurve.domain().or_refuse(KernelStage::Intersect, "domain")?;
let d_param = f_pcurve.evaluate(if d_is_start { pd0 } else { pd1 }).or_refuse(KernelStage::Intersect, "evaluate")?;
claimed.insert((o, d));
if debug {
eprintln!(
"extend: bridge F={}:{} G={}:{} d={d} o={o} gap={gap:.3e} off_f={off_f:.3e} off_g={off_g:.3e} fit={fit_tolerance:.1e} f_window={} g_window={}",
f.operand,
f.face_id,
g.operand,
g.face_id,
face_of(f).map(describe_trim_window).unwrap_or_default(),
face_of(g).map(describe_trim_window).unwrap_or_default(),
);
}
bridges.push(BridgeSpec { f, g, o, d, d_param });
}
}
}
if bridges.is_empty() {
return Ok(0);
}
let mut next_id = result
.vertices
.iter()
.map(|v| v.id)
.chain(result.pieces.iter().map(|p| p.id))
.max()
.unwrap_or(0)
+ 1;
let mut added = 0usize;
for spec in bridges {
let o_pt = vpoint[&spec.o];
let d_pt = vpoint[&spec.d];
let (Some(fsurf), Some(gsurf)) = (surface_of(spec.f), surface_of(spec.g)) else {
continue;
};
let line = NurbsCurve::new(
1,
vec![0.0, 0.0, 1.0, 1.0],
vec![Vec4::from_point(o_pt, 1.0), Vec4::from_point(d_pt, 1.0)],
).or_refuse(KernelStage::Intersect, "csg.imprint.sections")?;
let g_pcurve = build_pcurve_on_surface(gsurf, &line).or_refuse(KernelStage::Intersect, "build_pcurve_on_surface")?;
let o_proj = project_point_to_surface_seeded(fsurf, o_pt, spec.d_param.x, spec.d_param.y).or_refuse(KernelStage::Intersect, "project_point_to_surface_seeded")?;
let (closed_u, closed_v) = fsurf.closed_directions().or_refuse(KernelStage::Intersect, "closed_directions")?;
let [fu0, fu1] = fsurf.domain_u().or_refuse(KernelStage::Intersect, "domain_u")?;
let [fv0, fv1] = fsurf.domain_v().or_refuse(KernelStage::Intersect, "domain_v")?;
let unwrap = |value: f64, target: f64, lo: f64, hi: f64, closed: bool| {
if !closed {
return value;
}
let period = hi - lo;
if period <= 0.0 {
return value;
}
let mut result = value;
while result - target > period * 0.5 {
result -= period;
}
while target - result > period * 0.5 {
result += period;
}
result
};
let o_u = unwrap(o_proj.u, spec.d_param.x, fu0, fu1, closed_u);
let o_v = unwrap(o_proj.v, spec.d_param.y, fv0, fv1, closed_v);
if std::env::var("BREP_DEBUG_EXTEND_SECTIONS").is_ok() {
eprintln!(
"extend apply: F={}:{} d_param=({:.4},{:.4}) o_param=({:.4},{:.4},dist={:.2e})",
spec.f.operand, spec.f.face_id, spec.d_param.x, spec.d_param.y,
o_u, o_v, o_proj.distance
);
}
let f_pcurve = NurbsCurve::new(
1,
vec![0.0, 0.0, 1.0, 1.0],
vec![
Vec4::from_point(Vec3::new(o_u, o_v, 0.0), 1.0),
Vec4::from_point(Vec3::new(spec.d_param.x, spec.d_param.y, 0.0), 1.0),
],
).or_refuse(KernelStage::Intersect, "csg.imprint.sections")?;
let id = next_id;
next_id += 1;
let (support_faces, pcurves) = if spec.f.operand <= spec.g.operand {
(
[spec.f, spec.g],
vec![
FacePcurve { operand: spec.f.operand, face_id: spec.f.face_id, pcurve: f_pcurve },
FacePcurve { operand: spec.g.operand, face_id: spec.g.face_id, pcurve: g_pcurve },
],
)
} else {
(
[spec.g, spec.f],
vec![
FacePcurve { operand: spec.g.operand, face_id: spec.g.face_id, pcurve: g_pcurve },
FacePcurve { operand: spec.f.operand, face_id: spec.f.face_id, pcurve: f_pcurve },
],
)
};
result.pieces.push(ImprintPieceRecord {
id,
curve: line,
t0: 0.0,
t1: 1.0,
start_vertex_id: spec.o,
end_vertex_id: spec.d,
pcurves,
support_faces,
shared_edge: None,
});
for key in [spec.f, spec.g] {
match result.by_face.iter_mut().find(|record| {
record.operand == key.operand && record.face_id == key.face_id
}) {
Some(record) => record.piece_ids.push(id),
None => result.by_face.push(FaceImprints {
operand: key.operand,
face_id: key.face_id,
piece_ids: vec![id],
}),
}
}
added += 1;
}
Ok(added)
}
fn pcurve_for_piece(piece: &ImprintPieceRecord, key: FaceKey) -> Result<&NurbsCurve, KernelRefusal> {
piece
.pcurves
.iter()
.find(|pcurve| pcurve.operand == key.operand && pcurve.face_id == key.face_id)
.map(|pcurve| &pcurve.pcurve)
.ok_or_else(|| KernelRefusal::internal(KernelStage::Intersect, "imprint.sections", "extend_truncated_sections: piece lacks face pcurve"))
}
pub(super) fn dissolve_section_origin_vertices(
result: &mut ImprintResultRecord,
marched_pieces: &HashSet<u64>,
solid_a: &BrepSolid,
solid_b: &BrepSolid,
charts: &FaceCharts<'_>,
tolerance: f64,
) -> Result<usize, KernelRefusal> {
if std::env::var("BREP_SECTION_ORIGIN_DISSOLVE").as_deref() == Ok("0") {
return Ok(0);
}
if !result.tangent_nodes.is_empty() {
return Ok(0);
}
let debug = std::env::var("BREP_DEBUG_ORIGIN_DISSOLVE").is_ok();
let band = tolerance.max(COINCIDENCE_DISTANCE_FLOOR).max(assembler_weld(tolerance));
let vertex_point: HashMap<u64, Vec3> =
result.vertices.iter().map(|v| (v.id, v.point)).collect();
let mut edge_boxes: Vec<(&EdgeRecord, Vec3, Vec3)> = Vec::new();
for solid in [solid_a, solid_b] {
for edge in &solid.edges {
if edge.degenerate {
continue;
}
let mut low = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
let mut high = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
let mut hull_ok = true;
for control in &edge.curve.control_points {
if control.w <= 0.0 {
hull_ok = false;
break;
}
let point = Vec3::new(control.x / control.w, control.y / control.w, control.z / control.w);
low = Vec3::new(low.x.min(point.x), low.y.min(point.y), low.z.min(point.z));
high = Vec3::new(high.x.max(point.x), high.y.max(point.y), high.z.max(point.z));
}
if !hull_ok {
low = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
high = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
}
edge_boxes.push((edge, low, high));
}
}
let lies_on_operand_edge = |point: Vec3| -> Result<bool, KernelRefusal> {
for (edge, low, high) in &edge_boxes {
if point.x < low.x - band
|| point.y < low.y - band
|| point.z < low.z - band
|| point.x > high.x + band
|| point.y > high.y + band
|| point.z > high.z + band
{
continue;
}
let projection = project_point_to_curve(&edge.curve, point)
.or_refuse(KernelStage::Intersect, "project_point_to_curve")?;
let nearest = edge
.curve
.evaluate(projection.u.clamp(edge.t0.min(edge.t1), edge.t0.max(edge.t1)))
.or_refuse(KernelStage::Intersect, "evaluate")?;
if nearest.sub(point).length() <= band {
return Ok(true);
}
}
Ok(false)
};
let mut on_operand_edge: HashMap<u64, bool> = HashMap::default();
let mut dissolved = 0usize;
loop {
let mut incident: HashMap<(FaceKey, FaceKey, u64), Vec<(usize, bool)>> = HashMap::default();
for (index, piece) in result.pieces.iter().enumerate() {
if piece.start_vertex_id == piece.end_vertex_id {
continue; }
let key = support_pair_key(piece.support_faces[0], piece.support_faces[1]);
incident
.entry((key.0, key.1, piece.start_vertex_id))
.or_default()
.push((index, true));
incident
.entry((key.0, key.1, piece.end_vertex_id))
.or_default()
.push((index, false));
}
let mut pairs_at: HashMap<u64, usize> = HashMap::default();
for &(_, _, vertex) in incident.keys() {
*pairs_at.entry(vertex).or_default() += 1;
}
let mut candidates: Vec<(u64, FaceKey, FaceKey, usize, usize)> = Vec::new();
for (&(first, second, vertex), slots) in &incident {
if pairs_at.get(&vertex).copied().unwrap_or(0) != 1 {
continue; }
if slots.len() != 2 {
continue;
}
let (a, b) = match (slots[0], slots[1]) {
((left, false), (right, true)) => (left, right),
((left, true), (right, false)) => (right, left),
_ => continue,
};
if a == b {
continue;
}
if !marched_pieces.contains(&result.pieces[a].id)
|| !marched_pieces.contains(&result.pieces[b].id)
{
continue;
}
candidates.push((vertex, first, second, a, b));
}
let mut kept = Vec::with_capacity(candidates.len());
for candidate in candidates {
let vertex = candidate.0;
let point = vertex_point.get(&vertex).copied().ok_or_else(|| {
KernelRefusal::internal(
KernelStage::Intersect,
"imprint.sections",
"dissolve_section_origin_vertices: piece names an unknown vertex",
)
})?;
let mut on_carrier_seam = false;
for key in [candidate.1, candidate.2] {
let solid = if key.operand == 0 { solid_a } else { solid_b };
let Some(face) = solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.find(|face| face.id == key.face_id)
else {
continue;
};
for seam in CarrierSeam::of(&face.surface)? {
if seam.offset(point).abs() > band || !seam.on_seam_half(point) {
continue;
}
let middle_offset = |index: usize| -> Result<f64, KernelRefusal> {
let curve = &result.pieces[index].curve;
let [t0, t1] = curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
Ok(seam.offset(
curve
.evaluate(0.5 * (t0 + t1))
.or_refuse(KernelStage::Intersect, "evaluate")?,
))
};
let (before, after) = (middle_offset(candidate.3)?, middle_offset(candidate.4)?);
if before.abs() > band && after.abs() > band && (before < 0.0) != (after < 0.0) {
on_carrier_seam = true;
}
}
}
if on_carrier_seam {
continue;
}
let on_edge = match on_operand_edge.get(&vertex) {
Some(known) => *known,
None => {
let point = vertex_point.get(&vertex).copied().ok_or_else(|| {
KernelRefusal::internal(
KernelStage::Intersect,
"imprint.sections",
"dissolve_section_origin_vertices: piece names an unknown vertex",
)
})?;
let known = lies_on_operand_edge(point)?;
on_operand_edge.insert(vertex, known);
known
}
};
if !on_edge {
kept.push(candidate);
}
}
let mut candidates = kept;
candidates.sort_by_key(|(vertex, _, _, a, b)| {
(*vertex, result.pieces[*a].id, result.pieces[*b].id)
});
let Some(&(vertex, _, _, a, b)) = candidates.first() else {
break;
};
let joined = join_section_pieces(result, a, b, solid_a, solid_b, charts, tolerance)?;
if !joined {
if debug {
eprintln!(
"origin-dissolve: vertex {vertex} declined by concatenate_exact_curve_pieces"
);
}
on_operand_edge.insert(vertex, true);
continue;
}
if debug {
let point = vertex_point.get(&vertex).copied().unwrap_or_default();
let node = result
.tangent_nodes
.iter()
.map(|node| node.sub(point).length())
.fold(f64::INFINITY, f64::min);
eprintln!(
"origin-dissolve: vertex {vertex} at ({:.6},{:.6},{:.6}) dissolved \
(nearest tangent node {node:.3e})",
point.x, point.y, point.z
);
}
dissolved += 1;
}
if dissolved > 0 {
let mut referenced: HashSet<u64> = HashSet::default();
for piece in &result.pieces {
referenced.insert(piece.start_vertex_id);
referenced.insert(piece.end_vertex_id);
}
result.vertices.retain(|v| referenced.contains(&v.id));
}
Ok(dissolved)
}
fn join_section_pieces(
result: &mut ImprintResultRecord,
a: usize,
b: usize,
solid_a: &BrepSolid,
solid_b: &BrepSolid,
charts: &FaceCharts<'_>,
tolerance: f64,
) -> Result<bool, KernelRefusal> {
if result.pieces[a].shared_edge.is_some() || result.pieces[b].shared_edge.is_some() {
return Ok(false); }
let mut a_faces: Vec<(u8, u64)> = result.pieces[a]
.pcurves
.iter()
.map(|p| (p.operand, p.face_id))
.collect();
let mut b_faces: Vec<(u8, u64)> = result.pieces[b]
.pcurves
.iter()
.map(|p| (p.operand, p.face_id))
.collect();
a_faces.sort_unstable();
b_faces.sort_unstable();
if a_faces != b_faces {
return Ok(false); }
let [a0, a1] = result.pieces[a]
.curve
.domain()
.or_refuse(KernelStage::Intersect, "domain")?;
let [b0, b1] = result.pieces[b]
.curve
.domain()
.or_refuse(KernelStage::Intersect, "domain")?;
let (a_span, b_span) = (a1 - a0, b1 - b0);
if a_span <= 0.0 || b_span <= 0.0 {
return Ok(false);
}
let split = a_span / (a_span + b_span);
let weld = assembler_weld(tolerance) * merge_scale(solid_scale(solid_a).max(solid_scale(solid_b)));
let Some(curve) = crate::concatenate_exact_curve_pieces(
&result.pieces[a].curve,
&result.pieces[b].curve,
split,
weld,
)
.map_err(|refusal| KernelRefusal { stage: KernelStage::Intersect, ..refusal })?
else {
return Ok(false);
};
let [t0, t1] = curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
let mut pcurves = Vec::with_capacity(a_faces.len());
for (operand, face_id) in &a_faces {
let solid = if *operand == 0 { solid_a } else { solid_b };
let Some(face) = solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.find(|face| face.id == *face_id)
else {
return Ok(false);
};
let find = |index: usize| {
result.pieces[index]
.pcurves
.iter()
.find(|p| p.operand == *operand && p.face_id == *face_id)
.map(|p| p.pcurve.clone())
};
let chart = chart_of(charts, FaceKey { operand: *operand, face_id: *face_id }, face);
let joined = match (find(a), find(b)) {
(Some(first), Some(second)) => {
join_trims(chart, &first, &second, split, &curve)?
}
_ => None,
};
let exact_trim_join = joined.is_some();
let pcurve = match joined {
Some(pcurve) => pcurve,
None => match {
if std::env::var("BREP_DEBUG_ORIGIN_DISSOLVE").is_ok() {
eprintln!(
"origin-dissolve: trim join declined on op{operand} face {face_id}; rebuilding"
);
}
build_pcurve_on_surface_marched(chart, &curve)
} {
Ok(pcurve) => pcurve,
Err(_) => return Ok(false),
},
};
if spans_a_period_of_a_doubly_curved_carrier(&face.surface, &pcurve)?
&& !(exact_trim_join && is_seam_anchored_iso_rim(&face.surface, &pcurve)?)
{
return Ok(false);
}
pcurves.push(FacePcurve {
operand: *operand,
face_id: *face_id,
pcurve,
});
}
let absorbed = result.pieces[b].id;
let keeper = result.pieces[a].id;
let end_vertex = result.pieces[b].end_vertex_id;
let piece = &mut result.pieces[a];
piece.curve = curve;
piece.t0 = t0;
piece.t1 = t1;
piece.end_vertex_id = end_vertex;
piece.pcurves = pcurves;
result.pieces.remove(b);
for record in &mut result.by_face {
if let Some(position) = record.piece_ids.iter().position(|&id| id == absorbed) {
if record.piece_ids.contains(&keeper) {
record.piece_ids.remove(position);
} else {
record.piece_ids[position] = keeper;
}
}
}
Ok(true)
}
fn is_seam_anchored_iso_rim(
surface: &NurbsSurface,
pcurve: &NurbsCurve,
) -> Result<bool, KernelRefusal> {
let (closed_u, closed_v) = surface
.closed_directions()
.or_refuse(KernelStage::Intersect, "closed_directions")?;
let u = surface
.domain_u()
.or_refuse(KernelStage::Intersect, "domain_u")?;
let v = surface
.domain_v()
.or_refuse(KernelStage::Intersect, "domain_v")?;
let [t0, t1] = pcurve
.domain()
.or_refuse(KernelStage::Intersect, "domain")?;
let start = pcurve
.evaluate(t0)
.or_refuse(KernelStage::Intersect, "evaluate")?;
let end = pcurve
.evaluate(t1)
.or_refuse(KernelStage::Intersect, "evaluate")?;
for (closed, period, transverse, a, b, fixed, swap) in [
(closed_u, u, v, start.x, end.x, start.y, false),
(closed_v, v, u, start.y, end.y, start.x, true),
] {
if !closed {
continue;
}
let epsilon = (period[1] - period[0]).abs() * 1e-8;
let transverse_epsilon = (transverse[1] - transverse[0]).abs() * 1e-8;
let at = |x: f64, y: f64| (x - y).abs() <= epsilon;
if !((at(a, period[0]) && at(b, period[1])) || (at(a, period[1]) && at(b, period[0])))
|| fixed < transverse[0] - transverse_epsilon
|| fixed > transverse[1] + transverse_epsilon
{
continue;
}
if pcurve.control_points.iter().all(|point| {
if point.w <= 0.0 {
return false;
}
let (along, across) = if swap {
(point.y / point.w, point.x / point.w)
} else {
(point.x / point.w, point.y / point.w)
};
along >= period[0] - epsilon
&& along <= period[1] + epsilon
&& (across - fixed).abs() <= transverse_epsilon
}) {
return Ok(true);
}
}
Ok(false)
}
fn spans_a_period_of_a_doubly_curved_carrier(
surface: &NurbsSurface,
pcurve: &NurbsCurve,
) -> Result<bool, KernelRefusal> {
if matches!(
surface.analytic(),
Some(crate::AnalyticSurface::RuledRevolution { .. })
) {
return Ok(false);
}
let (closed_u, closed_v) = surface
.closed_directions()
.or_refuse(KernelStage::Intersect, "closed_directions")?;
if !closed_u && !closed_v {
return Ok(false);
}
let [u0, u1] = surface
.domain_u()
.or_refuse(KernelStage::Intersect, "domain_u")?;
let [v0, v1] = surface
.domain_v()
.or_refuse(KernelStage::Intersect, "domain_v")?;
let [t0, t1] = pcurve
.domain()
.or_refuse(KernelStage::Intersect, "domain")?;
let (mut u_low, mut u_high) = (f64::INFINITY, f64::NEG_INFINITY);
let (mut v_low, mut v_high) = (f64::INFINITY, f64::NEG_INFINITY);
for step in 0..=64 {
let uv = pcurve
.evaluate(t0 + (t1 - t0) * step as f64 / 64.0)
.or_refuse(KernelStage::Intersect, "evaluate")?;
u_low = u_low.min(uv.x);
u_high = u_high.max(uv.x);
v_low = v_low.min(uv.y);
v_high = v_high.max(uv.y);
}
let spans = |low: f64, high: f64, start: f64, end: f64| {
let period = end - start;
period > 0.0 && (high - low) >= period * (1.0 - 1e-3)
};
Ok((closed_u && spans(u_low, u_high, u0, u1)) || (closed_v && spans(v_low, v_high, v0, v1)))
}
fn join_trims(
surface: &NurbsSurface,
first: &NurbsCurve,
second: &NurbsCurve,
split: f64,
curve: &NurbsCurve,
) -> Result<Option<NurbsCurve>, KernelRefusal> {
let (closed_u, closed_v) = surface
.closed_directions()
.or_refuse(KernelStage::Intersect, "closed_directions")?;
let [u0, u1] = surface.domain_u().or_refuse(KernelStage::Intersect, "domain_u")?;
let [v0, v1] = surface.domain_v().or_refuse(KernelStage::Intersect, "domain_v")?;
let [f0, f1] = first.domain().or_refuse(KernelStage::Intersect, "domain")?;
let [s0, _] = second.domain().or_refuse(KernelStage::Intersect, "domain")?;
let end = first.evaluate(f1).or_refuse(KernelStage::Intersect, "evaluate")?;
let start = second.evaluate(s0).or_refuse(KernelStage::Intersect, "evaluate")?;
let shift = |gap: f64, closed: bool, period: f64| {
if closed && period > 0.0 {
(gap / period).round() * period
} else {
0.0
}
};
let du = shift(end.x - start.x, closed_u, u1 - u0);
let dv = shift(end.y - start.y, closed_v, v1 - v0);
let mut moved = second.clone();
for control in &mut moved.control_points {
control.x += du * control.w;
control.y += dv * control.w;
}
let span = (u1 - u0).abs().max((v1 - v0).abs()).max(1.0);
let Some(joined) = crate::concatenate_exact_curve_pieces(first, &moved, split, 1e-9 * span)
.map_err(|refusal| KernelRefusal { stage: KernelStage::Intersect, ..refusal })?
else {
return Ok(None);
};
let [c0, c1] = curve.domain().or_refuse(KernelStage::Intersect, "domain")?;
let [j0, j1] = joined.domain().or_refuse(KernelStage::Intersect, "domain")?;
let off = |trim: &NurbsCurve, s: f64, at: f64| -> Result<f64, KernelRefusal> {
let [d0, d1] = trim.domain().or_refuse(KernelStage::Intersect, "domain")?;
let uv = trim
.evaluate(d0 + (d1 - d0) * s)
.or_refuse(KernelStage::Intersect, "evaluate")?;
let on_surface = surface
.evaluate(uv.x, uv.y)
.or_refuse(KernelStage::Intersect, "evaluate")?;
let on_curve = curve.evaluate(at).or_refuse(KernelStage::Intersect, "evaluate")?;
Ok(on_surface.sub(on_curve).length())
};
let mut original = 0.0f64;
let mut after = 0.0f64;
for station in 1..10 {
let fraction = station as f64 / 10.0;
let at = c0 + (c1 - c0) * fraction;
after = after.max(off(&joined, (j0 + (j1 - j0) * fraction - j0) / (j1 - j0), at)?);
original = original.max(if fraction <= split {
off(first, fraction / split, at)?
} else {
off(&moved, (fraction - split) / (1.0 - split), at)?
});
}
if after > original * 1.5 + 1e-9 * span {
return Ok(None);
}
Ok(Some(joined))
}