use crate::{KernelRefusal, KernelStage, OrRefuse};
use super::*;
use super::types::{Chain, ChainSource};
use super::sampling::{enforce_boundary_crossing_parity, refine_near_hints, sag_refine_chain, sample_boundary_chain, sample_chain, trimmed_curve};
use super::loops::{build_loop, edge_for, interior_points, pcurve_for, point_in_polygon, segment_tag, tag_chain, vertex_point};
use super::loop_split::{fragments_agree, loop_split_census, loop_split_face, loop_split_mode, LoopSplitMode};
fn boundary_vertex_hints_enabled() -> bool {
switch!("BREP_BOUNDARY_VERTEX_HINTS", true)
}
pub(crate) fn small_ring_enabled() -> bool {
switch!("BREP_FRAGMENT_SMALL_RING", true)
}
fn bridge_t_junction_anchor_enabled() -> bool {
switch!("BREP_BRIDGE_T_JUNCTION_ANCHOR", true)
}
fn bridge_boundary_t_junction_anchor_enabled() -> bool {
switch!("BREP_BRIDGE_BOUNDARY_T_JUNCTION_ANCHOR", true)
}
fn bridge_skip_crossing_pairs_enabled() -> bool {
switch!("BREP_BRIDGE_SKIP_CROSSING_PAIRS", true)
}
pub(super) struct FragmentIndex<'a> {
pub(super) by_face: HashMap<(u8, u64), &'a [u64]>,
pub(super) pieces_by_id: HashMap<u64, &'a ImprintPieceRecord>,
pub(super) vertex_points: HashMap<u64, Vec3>,
pub(super) cosurface_pairs: HashSet<(FaceKey, FaceKey)>,
}
impl<'a> FragmentIndex<'a> {
pub(super) fn build(imprint: &'a ImprintResultRecord) -> Self {
let mut by_face: HashMap<(u8, u64), &'a [u64]> = HashMap::default();
for entry in &imprint.by_face {
by_face
.entry((entry.operand, entry.face_id))
.or_insert_with(|| entry.piece_ids.as_slice());
}
FragmentIndex {
by_face,
cosurface_pairs: imprint.cosurface_pairs.iter()
.flat_map(|&(a,b)| [(a,b),(b,a)]).collect(),
pieces_by_id: imprint
.pieces
.iter()
.map(|piece| (piece.id, piece))
.collect(),
vertex_points: imprint
.vertices
.iter()
.map(|vertex| (vertex.id, vertex.point))
.collect(),
}
}
}
pub fn fragment_face(
solid: &BrepSolid,
operand: u8,
face: &FaceRecord,
imprint: &ImprintResultRecord,
) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
let index = FragmentIndex::build(imprint);
let mut profile = FragmentProfile::new();
let fragments = fragment_face_indexed(solid, operand, face, &index, &mut profile)?;
profile.report(operand);
Ok(fragments)
}
fn source_loop_winding_negative(face: &FaceRecord) -> Result<Option<bool>, KernelRefusal> {
let closed = face
.surface
.closed_directions()
.or_refuse(KernelStage::Fragment, "closed_directions")?;
if closed.0 || closed.1 {
return Ok(None);
}
let mut best: Option<f64> = None;
for loop_record in &face.loops {
let mut points: Vec<Vec2> = Vec::new();
for coedge in &loop_record.coedges {
let [d0, d1] = coedge
.pcurve
.domain()
.or_refuse(KernelStage::Fragment, "domain")?;
const SAMPLES: usize = 24;
for step in 0..SAMPLES {
let fraction = step as f64 / SAMPLES as f64;
let t = d0 + (d1 - d0) * fraction;
let point = coedge
.pcurve
.evaluate(t)
.or_refuse(KernelStage::Fragment, "evaluate")?;
points.push(Vec2 { x: point.x, y: point.y });
}
}
if points.len() < 3 {
continue;
}
let mut area = 0.0;
for (index, point) in points.iter().enumerate() {
let next = points[(index + 1) % points.len()];
area += 0.5 * (crate::polygon::area_term([point.x, point.y], [next.x, next.y]));
}
if best.map(|current: f64| area.abs() > current.abs()).unwrap_or(true) {
best = Some(area);
}
}
let u_domain = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u)
.or_refuse(KernelStage::Fragment, "new")?
.domain();
let v_domain = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v)
.or_refuse(KernelStage::Fragment, "new")?
.domain();
let chart = (u_domain[1] - u_domain[0]) * (v_domain[1] - v_domain[0]);
let floor = (chart.abs() * 1e-9).max(f64::MIN_POSITIVE);
Ok(match best {
Some(area) if area.abs() > floor => Some(area < 0.0),
_ => None,
})
}
fn boundary_has_cosurface_partner(
solid: &BrepSolid,
face: FaceKey,
boundary_edge: u64,
piece: &ImprintPieceRecord,
index: &FragmentIndex,
) -> bool {
piece.support_faces.iter().filter(|other| other.operand != face.operand).any(|other| {
solid.shells.iter().flat_map(|shell| &shell.faces).any(|neighbour| {
neighbour.id != face.face_id
&& index.cosurface_pairs.contains(&(FaceKey { operand:face.operand, face_id:neighbour.id }, *other))
&& neighbour.loops.iter().flat_map(|lp| &lp.coedges)
.any(|coedge| coedge.edge_id == boundary_edge)
})
})
}
fn thin_boundary_resolution(
solid: &BrepSolid,
face: &FaceRecord,
face_key: FaceKey,
piece_ids: &[u64],
index: &FragmentIndex,
search: f64,
) -> (Option<(f64, f64)>, bool) {
let mut resolution: Option<(f64, f64)> = None;
let mut feature_found = false;
let mut take = |strip: Option<(f64, f64)>| {
if let Some((arrangement, sampling)) = strip {
resolution = Some(match resolution {
Some((a, s)) => (a.min(arrangement), s.min(sampling)),
None => (arrangement, sampling),
});
}
};
let cuts: Vec<&NurbsCurve> = piece_ids
.iter()
.filter_map(|piece_id| index.pieces_by_id.get(piece_id).and_then(|piece| pcurve_for(piece, face_key).ok()))
.collect();
for piece_id in piece_ids {
let Some(piece) = index.pieces_by_id.get(piece_id) else { continue };
let Ok(cut) = pcurve_for(piece, face_key) else { continue };
for boundary in face.loops.iter().flat_map(|lp| &lp.coedges) {
if boundary_has_cosurface_partner(solid, face_key, boundary.edge_id, piece, index) {
continue;
}
let strip = strip_between(face, &cut, &boundary.pcurve, search);
take(match strip {
None if crate::feature_scale::fragment_enabled() => {
let touch = near_touch(face, &cut, &boundary.pcurve, search);
if touch.is_some() && env_read!("BREP_DEBUG_FEATURE_SCALE").is_some() {
eprintln!("feature_scale: face {} near touch cut p{piece_id} / boundary e{} -> {touch:?}", face.id, boundary.edge_id);
}
feature_found |= touch.is_some();
touch
}
strip => strip,
});
if end_gap_enabled() {
let gap = end_gap(face, &cut, &boundary.pcurve, search);
if gap.is_some() && env_read!("BREP_DEBUG_FEATURE_SCALE").is_some() {
eprintln!("feature_scale: face {} end gap cut p{piece_id} / boundary e{} -> {gap:?}", face.id, boundary.edge_id);
}
feature_found |= gap.is_some();
take(gap);
}
}
}
if crate::feature_scale::fragment_enabled() {
for (i, cut) in cuts.iter().enumerate() {
for other in &cuts[i + 1..] {
let strip = strip_between(face, cut, other, search);
let found = strip.or_else(|| near_touch(face, cut, other, search));
if found.is_some() && env_read!("BREP_DEBUG_FEATURE_SCALE").is_some() {
eprintln!("feature_scale: face {} cut pair {i} {} -> {} {found:?}", face.id, if strip.is_some() { "strip" } else { "near touch" }, i);
}
feature_found |= found.is_some();
take(found);
}
}
}
(resolution, feature_found)
}
pub(crate) fn end_gap_enabled() -> bool {
crate::switch!("BREP_FEATURE_SCALE_END_GAP", true)
}
fn end_gap(face: &FaceRecord, cut: &NurbsCurve, other: &NurbsCurve, search: f64) -> Option<(f64, f64)> {
let straight = |curve: &NurbsCurve| curve.degree == 1 && curve.control_points.len() == 2;
if !face.surface.is_affine().ok()? || !straight(cut) || !straight(other) {
return None;
}
let [lo, hi] = cut.domain().ok()?;
let start = cut.evaluate(lo).ok()?;
let end = cut.evaluate(hi).ok()?;
if start.sub(end).length() <= search {
return None;
}
let u_domain = face.surface.domain_u().ok()?;
let v_domain = face.surface.domain_v().ok()?;
let diagonal = ((u_domain[1] - u_domain[0]).powi(2) + (v_domain[1] - v_domain[0]).powi(2)).sqrt();
let reach = (search * 100.0).max(diagonal * 1.5e-3);
let mut found: Option<(f64, f64)> = None;
for point in [start, end] {
let Ok(foot) = project_point_to_curve(other, point) else { continue };
if foot.distance > reach {
continue;
}
let (Ok(here), Ok(there)) = (face.surface.evaluate(point.x, point.y), face.surface.evaluate(foot.point.x, foot.point.y)) else {
continue;
};
if here.sub(there).length() <= crate::tolerance::WELD_FLOOR {
continue;
}
let identity = crate::classification::surface_uv_band(&face.surface, point.x, point.y, 1e-7);
if foot.distance <= identity * 8.0 {
continue;
}
let resolution = (search.min((foot.distance / 128.0).max(identity)), search.min(foot.distance / 8.0));
found = Some(match found {
Some((a, s)) => (a.min(resolution.0), s.min(resolution.1)),
None => resolution,
});
}
found
}
fn near_touch(face: &FaceRecord, cut: &NurbsCurve, other: &NurbsCurve, search: f64) -> Option<(f64, f64)> {
let hull = |curve: &NurbsCurve| crate::spatial::Aabb::from_curve_controls(curve).ok();
if !hull(cut)?.intersects(hull(other)?, search * 8.0) {
return None;
}
let [lo, hi] = cut.domain().ok()?;
let [other_lo, other_hi] = other.domain().ok()?;
let ends_on_other = [lo, hi].into_iter().any(|t| {
let Ok(point) = cut.evaluate(t) else { return false };
let Ok(foot) = project_point_to_curve(other, point) else { return false };
let identity = crate::classification::surface_uv_band(&face.surface, point.x, point.y, 1e-7);
if foot.distance > (search * 1e-3).min(identity) {
return false;
}
match (face.surface.evaluate(point.x, point.y), face.surface.evaluate(foot.point.x, foot.point.y)) {
(Ok(here), Ok(there)) => here.sub(there).length() <= 1e-9,
_ => false,
}
});
if ends_on_other {
return None;
}
let gap = |parameter: f64| -> Option<(f64, Vec3, Vec3, f64)> {
let point = cut.evaluate(parameter).ok()?;
let projected = project_point_to_curve(other, point).ok()?;
Some((projected.distance, point, projected.point, projected.u))
};
let closed = |curve: &NurbsCurve, a: f64, b: f64| -> bool {
match (curve.evaluate(a), curve.evaluate(b)) {
(Ok(start), Ok(end)) => start.sub(end).length() <= search,
_ => false,
}
};
let (cut_closed, other_closed) = (closed(cut, lo, hi), closed(other, other_lo, other_hi));
const SAMPLES: usize = 256;
let step = (hi - lo) / SAMPLES as f64;
let mut best: Option<(f64, f64)> = None;
for i in 0..=SAMPLES {
let parameter = lo + step * i as f64;
if let Some((distance, _, _, _)) = gap(parameter) {
if best.is_none_or(|(_, d)| distance < d) {
best = Some((parameter, distance));
}
}
}
let (centre, _) = best?;
let (mut a, mut b) = ((centre - step).max(lo), (centre + step).min(hi));
let ratio = 0.5 * (5f64.sqrt() - 1.0);
for _ in 0..80 {
let c = b - ratio * (b - a);
let d = a + ratio * (b - a);
if gap(c)?.0 < gap(d)?.0 { b = d } else { a = c }
}
let parameter = 0.5 * (a + b);
let (low_gap, point, foot, foot_parameter) = gap(parameter)?;
if low_gap > search * 8.0 {
return None;
}
let at_end = |t: f64, a: f64, b: f64| (t - a).abs() <= 0.01 * (b - a) || (b - t).abs() <= 0.01 * (b - a);
if (!cut_closed && at_end(parameter, lo, hi)) || (!other_closed && at_end(foot_parameter, other_lo, other_hi)) {
return None;
}
let actual = face.surface.evaluate(point.x, point.y).ok()?;
let rim = face.surface.evaluate(foot.x, foot.y).ok()?;
if actual.sub(rim).length() <= 8e-7 {
return None;
}
let identity = crate::classification::surface_uv_band(&face.surface, point.x, point.y, 1e-7);
if low_gap <= identity * 8.0 {
return None;
}
Some((search.min((low_gap / 128.0).max(identity)), search.min(low_gap / 8.0)))
}
fn strip_between(face: &FaceRecord, cut: &NurbsCurve, other: &NurbsCurve, search: f64) -> Option<(f64, f64)> {
let [lo, hi] = cut.domain().ok()?;
let middle = cut.evaluate((lo + hi) * 0.5).ok()?;
let nearest = project_point_to_curve(other, middle).ok()?;
if nearest.distance > search * 8.0 { return None; }
let mut low_gap = f64::INFINITY;
let mut high_gap = 0.0f64;
let mut identity = 0.0f64;
for fraction in [0.125, 0.25, 0.5, 0.75, 0.875] {
let parameter = lo + (hi - lo) * fraction;
let cut_sample = cut.derivatives(parameter, 1).ok()?;
let point = cut_sample[0];
let projected = project_point_to_curve(other, point).ok()?;
if projected.distance > search * 8.0 { return None; }
let other_sample = other.derivatives(projected.u, 1).ok()?;
let a = cut_sample[1];
let b = other_sample[1];
let lengths = a.length() * b.length();
if lengths <= 1e-30 || a.dot(b).abs() < 0.995 * lengths { return None; }
let actual = face.surface.evaluate(point.x, point.y).ok()?;
let rim = face.surface.evaluate(projected.point.x, projected.point.y).ok()?;
if actual.sub(rim).length() <= 8e-7 { return None; }
let floor = crate::classification::surface_uv_band(
&face.surface, point.x, point.y, 1e-7,
);
if projected.distance <= floor * 8.0 { return None; }
low_gap = low_gap.min(projected.distance);
high_gap = high_gap.max(projected.distance);
identity = identity.max(floor);
}
if high_gap > low_gap * 4.0 { return None; }
Some((search.min((low_gap / 128.0).max(identity)), search.min(low_gap / 8.0)))
}
fn fragment_face_indexed(
solid: &BrepSolid,
operand: u8,
face: &FaceRecord,
index: &FragmentIndex,
profile: &mut FragmentProfile,
) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
let face_started = profile.enabled.then(Instant::now);
let mut lap_start = face_started;
let result = fragment_face_stages(solid, operand, face, index, profile, &mut lap_start);
profile.face_done(face.id, face_started);
result
}
fn fragment_face_stages(
solid: &BrepSolid,
operand: u8,
face: &FaceRecord,
index: &FragmentIndex,
profile: &mut FragmentProfile,
lap_start: &mut Option<Instant>,
) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
let face_key = FaceKey {
operand,
face_id: face.id,
};
let piece_ids = index
.by_face
.get(&(operand, face.id))
.copied()
.unwrap_or(&[]);
if piece_ids.is_empty() {
profile.uncut_probe_faces += 1;
let u = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u).or_refuse(KernelStage::Fragment, "new")?.domain();
let v = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v).or_refuse(KernelStage::Fragment, "new")?.domain();
let uv = Vec2 {
x: (u[0] + u[1]) / 2.0,
y: (v[0] + v[1]) / 2.0,
};
let probe_started = profile.enabled.then(Instant::now);
let centre_inside = parameter_point_in_face(face, uv, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside;
if let Some(started) = probe_started { profile.uncut_probe += started.elapsed(); }
if !centre_inside {
} else {
let mut extra_test_points = Vec::new();
for (fu, fv) in [(0.35, 0.35), (0.65, 0.65)] {
let candidate = Vec2 {
x: u[0] + (u[1] - u[0]) * fu,
y: v[0] + (v[1] - v[0]) * fv,
};
let probe_started = profile.enabled.then(Instant::now);
let inside = parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside;
if let Some(started) = probe_started { profile.uncut_probe += started.elapsed(); }
if inside {
extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
}
}
let untouched = vec![untouched_fragment(face, operand, uv, extra_test_points)?];
profile.untouched += profile.lap(lap_start);
return Ok(untouched);
}
}
if !piece_ids.is_empty() {
profile.cut_faces += 1;
}
let mode = loop_split_mode();
let mut split_result: Option<Result<Vec<FaceFragmentRecord>, super::loop_split::Decline>> = None;
if mode != LoopSplitMode::Off && !piece_ids.is_empty() {
let started = profile.enabled.then(Instant::now);
let outcome = loop_split_face(solid, operand, face, face_key, piece_ids, index)?;
if let Some(started) = started {
profile.loop_split += started.elapsed();
}
match &outcome {
Ok(_) => profile.loop_split_faces += 1,
Err(_) => profile.loop_split_declined += 1,
}
if mode == LoopSplitMode::On {
if let Ok(fragments) = outcome {
*lap_start = profile.enabled.then(Instant::now);
return Ok(fragments);
}
}
split_result = Some(outcome);
*lap_start = profile.enabled.then(Instant::now);
}
let arranged = fragment_face_arranged(solid, operand, face, face_key, piece_ids, index, profile, lap_start);
if piece_ids.is_empty() && trim_probe_enabled() && matches!(&arranged, Ok(fragments) if fragments.is_empty()) {
let u = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u).or_refuse(KernelStage::Fragment, "new")?.domain();
let v = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v).or_refuse(KernelStage::Fragment, "new")?.domain();
if let Some((uv, extra_test_points)) = trim_interior_probe(face, u, v)? {
return Ok(vec![untouched_fragment(face, operand, uv, extra_test_points)?]);
}
}
if let Some(split) = split_result {
let verdict = match (&split, &arranged) {
(Err(decline), _) => format!("declined {decline}"),
(Ok(_), Err(refusal)) => format!("MISMATCH split built {} fragments, arrangement refused: {refusal}", split.as_ref().map_or(0, Vec::len)),
(Ok(split), Ok(arranged)) => match fragments_agree(split, arranged) {
Ok(agreement) => agreement.to_string(),
Err(why) => format!("MISMATCH {why}"),
},
};
loop_split_census(|| format!("operand {operand} face {} pieces {} loops {} {verdict}", face.id, piece_ids.len(), face.loops.len()));
}
arranged
}
#[allow(clippy::too_many_arguments)]
fn fragment_face_arranged(
solid: &BrepSolid,
operand: u8,
face: &FaceRecord,
face_key: FaceKey,
piece_ids: &[u64],
index: &FragmentIndex,
profile: &mut FragmentProfile,
lap_start: &mut Option<Instant>,
) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
let u_domain = KnotVector::new(face.surface.knots_u.clone(), face.surface.degree_u).or_refuse(KernelStage::Fragment, "new")?.domain();
let v_domain = KnotVector::new(face.surface.knots_v.clone(), face.surface.degree_v).or_refuse(KernelStage::Fragment, "new")?.domain();
let diagonal =
((u_domain[1] - u_domain[0]).powi(2) + (v_domain[1] - v_domain[0]).powi(2)).sqrt();
let original_resolution = 1e-7f64.max(diagonal * 1e-6);
let (strip_resolution, feature_strip) = thin_boundary_resolution(solid, face, face_key, piece_ids, index, original_resolution);
let (arrangement_tolerance, sampling_bound) = strip_resolution
.unwrap_or((original_resolution, original_resolution));
let strip_snap = feature_strip;
let (image_band, image_band_3d) = if strip_snap {
(sampling_bound, 1e-7)
} else {
(arrangement_tolerance * 100.0, 1e-5)
};
let snap_threshold = 0.1 * diagonal;
let junction_radius = (arrangement_tolerance * 50.0).max(diagonal * 1.5e-3);
let band_rebase: [Option<(f64, f64)>; 2] = {
let mut rebase = [None, None];
let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
if (closed.0 || closed.1) && std::env::var("BREP_BAND_REBASE").as_deref() != Ok("0") {
let mut coverage: [Vec<[f64; 2]>; 2] = [Vec::new(), Vec::new()];
let mut edge_rim = [false, false];
let mut hull = [
[f64::INFINITY, f64::NEG_INFINITY],
[f64::INFINITY, f64::NEG_INFINITY],
];
for coedge in face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
let mut extent = [
[f64::INFINITY, f64::NEG_INFINITY],
[f64::INFINITY, f64::NEG_INFINITY],
];
for control in &coedge.pcurve.control_points {
if control.w.abs() <= 1e-300 {
continue;
}
let point = [control.x / control.w, control.y / control.w];
for axis in 0..2 {
extent[axis][0] = extent[axis][0].min(point[axis]);
extent[axis][1] = extent[axis][1].max(point[axis]);
hull[axis][0] = hull[axis][0].min(point[axis]);
hull[axis][1] = hull[axis][1].max(point[axis]);
}
}
for axis in 0..2 {
if !extent[axis][0].is_finite() {
continue;
}
let domain = if axis == 0 { u_domain } else { v_domain };
let span = domain[1] - domain[0];
coverage[axis].push(extent[axis]);
let flat = extent[axis][1] - extent[axis][0] <= 1e-3 * span;
let at_edge = (extent[axis][0] - domain[0]).abs() <= 1e-6 * span
|| (extent[axis][1] - domain[1]).abs() <= 1e-6 * span;
if flat && at_edge {
edge_rim[axis] = true;
}
}
}
for axis in 0..2 {
if !(if axis == 0 { closed.0 } else { closed.1 }) || !edge_rim[axis] {
continue;
}
let domain = if axis == 0 { u_domain } else { v_domain };
let span = domain[1] - domain[0];
let mut intervals: Vec<[f64; 2]> = coverage[axis]
.iter()
.map(|window| {
[window[0].max(domain[0]), window[1].min(domain[1])]
})
.filter(|window| window[1] >= window[0])
.collect();
intervals.sort_by(|a, b| a[0].total_cmp(&b[0]));
let mut gaps: Vec<[f64; 2]> = Vec::new();
let mut reach = domain[0];
for window in &intervals {
if window[0] > reach {
gaps.push([reach, window[0]]);
}
reach = reach.max(window[1]);
}
if reach < domain[1] {
gaps.push([reach, domain[1]]);
}
let other_mid = (hull[1 - axis][0].max(if axis == 0 {
v_domain[0]
} else {
u_domain[0]
}) + hull[1 - axis][1].min(if axis == 0 {
v_domain[1]
} else {
u_domain[1]
})) / 2.0;
let mut void: Option<[f64; 2]> = None;
for gap in gaps {
if gap[1] - gap[0] <= 1e-6 * span {
continue;
}
let mid = (gap[0] + gap[1]) / 2.0;
let sample = if axis == 0 {
Vec2 {
x: mid,
y: other_mid,
}
} else {
Vec2 {
x: other_mid,
y: mid,
}
};
if parameter_point_in_face(face, sample, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? != PolygonClass::Inside
&& void
.map(|best| gap[1] - gap[0] > best[1] - best[0])
.unwrap_or(true)
{
void = Some(gap);
}
}
if let Some([g0, g1]) = void {
let touches_low = intervals
.iter()
.any(|window| window[0] <= domain[0] + 1e-6 * span);
let touches_high = intervals
.iter()
.any(|window| window[1] >= domain[1] - 1e-6 * span);
if touches_low && touches_high {
rebase[axis] = Some(((g0 + g1) / 2.0, span));
}
}
}
}
rebase
};
let rebase_point = |mut point: Vec2| -> Vec2 {
if let Some((cutoff, period)) = band_rebase[0] {
if point.x < cutoff {
point.x += period;
}
}
if let Some((cutoff, period)) = band_rebase[1] {
if point.y < cutoff {
point.y += period;
}
}
point
};
let wrap_back = {
let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
move |mut point: Vec2| -> Vec2 {
if closed.0 {
let span = u_domain[1] - u_domain[0];
if point.x < u_domain[0] || point.x > u_domain[1] {
point.x = u_domain[0] + (point.x - u_domain[0]).rem_euclid(span);
}
}
if closed.1 {
let span = v_domain[1] - v_domain[0];
if point.y < v_domain[0] || point.y > v_domain[1] {
point.y = v_domain[0] + (point.y - v_domain[0]).rem_euclid(span);
}
}
point
}
};
profile.setup += profile.lap(lap_start);
let pieces_by_id = &index.pieces_by_id;
let mut cut_hints = Vec::new();
let mut active_pieces = Vec::new();
let mut cut_keys = HashMap::default();
for piece_id in piece_ids {
let piece = pieces_by_id
.get(piece_id)
.ok_or_else(|| KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", "fragment_face: missing imprint piece"))?;
let pcurve = pcurve_for(piece, face_key)?;
let [start, end] = pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
let a = pcurve.evaluate(start).or_refuse(KernelStage::Fragment, "evaluate")?;
let b = pcurve.evaluate(end).or_refuse(KernelStage::Fragment, "evaluate")?;
let middle = pcurve.evaluate((start + end) / 2.0).or_refuse(KernelStage::Fragment, "evaluate")?;
for point in [a, b] {
cut_hints.push(Vec2 {
x: point.x,
y: point.y,
});
}
let quantize = |point: Vec3| {
(
(point.x / arrangement_tolerance).round() as i64,
(point.y / arrangement_tolerance).round() as i64,
)
};
let mut endpoints = [quantize(a), quantize(b)];
endpoints.sort();
let key = (endpoints[0], quantize(middle), endpoints[1]);
if cut_keys.insert(key, *piece_id).is_none() {
active_pieces.push(*piece_id);
}
}
let mut segments = Vec::new();
let mut chains = Vec::new();
let mut vertex_images: std::collections::HashMap<String, Vec<Vec2>> =
std::collections::HashMap::new();
let mut endpoint_images: Vec<(Vec3, Vec2)> = Vec::new();
let mut boundary_images: Vec<(Vec2, Vec3)> = Vec::new();
let boundary_vertex_anchor_off =
std::env::var("BREP_BOUNDARY_VERTEX_ANCHOR").as_deref() == Ok("0");
let boundary_side = |point: Vec2| {
let distances = [
(point.x - u_domain[0]).abs(),
(point.x - u_domain[1]).abs(),
(point.y - v_domain[0]).abs(),
(point.y - v_domain[1]).abs(),
];
let index = distances
.iter()
.enumerate()
.min_by(|a, b| a.1.total_cmp(b.1))
.map(|entry| entry.0)
.unwrap();
(distances[index] <= arrangement_tolerance * 50.0).then_some(index)
};
let bridge_keys: HashSet<String> = if rim_bridge_enabled() {
let weld = crate::tolerance::assembler_weld(1e-7);
let mut rim_points = Vec::new();
for coedge in face.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
if let Some(edge) = solid.edges.iter().find(|edge| edge.id == coedge.edge_id) {
for vertex_id in [edge.start_vertex_id, edge.end_vertex_id] {
rim_points.push(vertex_point(solid, vertex_id)?);
}
}
}
let on_rim = |point: &Vec3| rim_points.iter().any(|rim| rim.sub(*point).length() <= weld);
piece_ids
.iter()
.filter_map(|piece_id| pieces_by_id.get(piece_id))
.filter(|piece| {
piece.start_vertex_id != piece.end_vertex_id
&& piece.curve.degree == 1
&& piece.curve.control_points.len() == 2
&& match (index.vertex_points.get(&piece.start_vertex_id), index.vertex_points.get(&piece.end_vertex_id)) {
(Some(a), Some(b)) => {
let gap = a.sub(*b).length();
gap > weld && gap <= 1e-3 && (on_rim(a) || on_rim(b))
}
_ => false,
}
})
.flat_map(|piece| [format!("i:{}", piece.start_vertex_id), format!("i:{}", piece.end_vertex_id)])
.collect()
} else {
HashSet::default()
};
let mut add_chain = |mut points: Vec<Vec2>,
source: ChainSource,
vertex_start_key: String,
vertex_start_point: Vec3,
vertex_end_key: String,
vertex_end_point: Vec3,
is_boundary: bool|
-> Result<(), KernelRefusal> {
for (at, key, point3) in [
(0usize, vertex_start_key, vertex_start_point),
(points.len() - 1, vertex_end_key, vertex_end_point),
] {
let point = points[at];
let bridged = bridge_keys.contains(&key);
let images = vertex_images.entry(key).or_default();
let canonical = images
.iter()
.copied()
.find(|image| image.sub(point).length() <= snap_threshold)
.or_else(|| {
endpoint_images
.iter()
.find(|(candidate3, candidate)| {
candidate3.sub(point3).length() <= image_band_3d
&& candidate.sub(point).length() <= image_band
})
.map(|candidate| candidate.1)
})
.or_else(|| {
if bridged {
return None;
}
boundary_side(point).and_then(|side| {
boundary_images
.iter()
.find(|(candidate, candidate3)| {
boundary_side(*candidate) == Some(side)
&& candidate.sub(point).length()
<= image_band
&& (boundary_vertex_anchor_off
|| !is_boundary
|| candidate3.sub(point3).length() <= 1e-5)
})
.map(|(candidate, _)| *candidate)
})
});
if let Some(canonical) = canonical {
points[at] = canonical;
images.push(canonical);
} else {
images.push(point);
endpoint_images.push((point3, point));
if is_boundary && boundary_side(point).is_some() {
boundary_images.push((point, point3));
}
}
}
if points.len() > 2 {
let start = points[0];
let end = points[points.len() - 1];
let thin = |radius: f64| {
let mut kept = vec![start];
kept.extend(points[1..points.len() - 1].iter().copied().filter(|point| {
point.sub(start).length() > radius && point.sub(end).length() > radius
}));
kept.push(end);
kept
};
let mut kept = thin(junction_radius);
let closed = start.sub(end).length() <= arrangement_tolerance;
if kept.len() < if closed { 4 } else { 3 } && small_ring_enabled() {
let chord = end.sub(start);
let span = chord.length();
let bulge = points
.iter()
.map(|point| {
let offset = point.sub(start);
if closed || span <= arrangement_tolerance {
offset.length()
} else {
(offset.x * chord.y - offset.y * chord.x).abs() / span
}
})
.fold(0.0, f64::max);
if bulge > arrangement_tolerance * 10.0 {
kept = thin(bulge * 0.1);
}
}
points = kept;
}
let chain_index = chains.len();
let chain_segments = points
.windows(2)
.map(|pair| (pair[0], pair[1]))
.collect::<Vec<_>>();
for (segment_index, pair) in points.windows(2).enumerate() {
segments.push(Segment2 {
a: pair[0],
b: pair[1],
tag: segment_tag(chain_index, segment_index),
});
}
chains.push(Chain {
source,
count: points.len() - 1,
start: points[0],
end: points[points.len() - 1],
segments: chain_segments,
});
Ok(())
};
profile.hints += profile.lap(lap_start);
let loop_hull_u: Vec<[f64; 2]> = face
.loops
.iter()
.map(|loop_record| {
let mut hull = [f64::INFINITY, f64::NEG_INFINITY];
for coedge in &loop_record.coedges {
for control in &coedge.pcurve.control_points {
if control.w.abs() <= 1e-300 {
continue;
}
hull[0] = hull[0].min(control.x / control.w);
hull[1] = hull[1].max(control.x / control.w);
}
}
hull
})
.collect();
let mut full_span_loop_area: Option<f64> = None;
let mut boundary_segments: Vec<(Vec2, Vec2)> = Vec::new();
let mut band_hull = [[f64::INFINITY, f64::NEG_INFINITY]; 2];
let boundary_vertex_hints: Vec<Vec2> = if boundary_vertex_hints_enabled() {
let mut hints = Vec::new();
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
let [start, end] = coedge.pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
for parameter in [start, end] {
let point = coedge.pcurve.evaluate(parameter).or_refuse(KernelStage::Fragment, "evaluate")?;
hints.push(Vec2 { x: point.x, y: point.y });
}
}
}
hints
} else {
Vec::new()
};
for (loop_index, loop_record) in face.loops.iter().enumerate() {
let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
let offsets = if (closed.0 || closed.1)
&& std::env::var("BREP_LOOP_UNWRAP").as_deref() != Ok("0")
{
crate::topology::loop_seam_offsets(
&loop_record.coedges,
closed.0,
closed.1,
u_domain[1] - u_domain[0],
v_domain[1] - v_domain[0],
).or_refuse(KernelStage::Fragment, "csg.fragment.face_split")?
} else {
vec![[0.0, 0.0]; loop_record.coedges.len()]
};
let mut sampled: Vec<(usize, Vec<Vec2>, NurbsCurve, u64, u64)> =
Vec::with_capacity(loop_record.coedges.len());
for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
let edge = edge_for(solid, coedge.edge_id)?;
let (start_vertex, end_vertex) = if coedge.forward {
(edge.start_vertex_id, edge.end_vertex_id)
} else {
(edge.end_vertex_id, edge.start_vertex_id)
};
let offset = offsets
.get(coedge_index)
.copied()
.unwrap_or([0.0, 0.0]);
let boundary_points = if strip_resolution.is_some() {
let initial = sample_chain(&coedge.pcurve)?;
sag_refine_chain(&coedge.pcurve, initial, sampling_bound)?.0
} else {
let initial = sample_boundary_chain(&coedge.pcurve, (diagonal * 1e-4).max(sampling_bound), 1024)?;
if boundary_vertex_hints.is_empty() || initial.len() < 3 {
refine_near_hints(&initial, &coedge.pcurve, &cut_hints)?
} else {
let own = [initial[0], initial[initial.len() - 1]];
let hints: Vec<Vec2> = cut_hints
.iter()
.copied()
.chain(boundary_vertex_hints.iter().copied().filter(|hint| {
own.iter().all(|end| hint.sub(*end).length() > arrangement_tolerance)
}))
.collect();
refine_near_hints(&initial, &coedge.pcurve, &hints)?
}
};
let points: Vec<Vec2> = boundary_points
.into_iter()
.map(|point| {
rebase_point(Vec2 {
x: point.x + offset[0],
y: point.y + offset[1],
})
})
.collect();
let curve = trimmed_curve(&edge.curve, edge.t0, edge.t1)?;
let curve = if coedge.forward {
curve
} else {
curve.reversed().or_refuse(KernelStage::Fragment, "reversed")?
};
sampled.push((coedge_index, points, curve, start_vertex, end_vertex));
}
let image_shift: Option<f64> = {
let span = u_domain[1] - u_domain[0];
let slack = arrangement_tolerance * 50.0;
if !closed.0
|| face.loops.len() < 2
|| span <= 0.0
|| std::env::var("BREP_SEAM_IMAGE_LOOPS").as_deref() == Ok("0")
{
None
} else {
let mut min_u = f64::INFINITY;
let mut max_u = f64::NEG_INFINITY;
let mut area = 0.0;
let mut flat: Vec<Vec2> = sampled
.iter()
.flat_map(|(_, points, ..)| points.iter().copied())
.collect();
if flat.len() < 3 {
flat.clear();
}
for (index, point) in flat.iter().enumerate() {
min_u = min_u.min(point.x);
max_u = max_u.max(point.x);
let next = flat[(index + 1) % flat.len()];
area += crate::polygon::area_term([point.x, point.y], [next.x, next.y]);
}
let spans_full_domain =
!flat.is_empty() && max_u - min_u >= span * (1.0 - 1e-6);
if spans_full_domain && full_span_loop_area.is_none() {
full_span_loop_area = Some(area);
}
let straddles_low = min_u < u_domain[0] - slack;
let straddles_high = max_u > u_domain[1] + slack;
let compact = max_u - min_u <= 0.5 * span;
let is_hole = full_span_loop_area
.map(|outer| !spans_full_domain && area * outer < 0.0)
.unwrap_or(false);
let has_full_span_other = loop_hull_u.iter().enumerate().any(|(other, hull)| {
other != loop_index
&& hull[0] <= u_domain[0] + 1e-6 * span
&& hull[1] >= u_domain[1] - 1e-6 * span
});
if compact && is_hole && has_full_span_other && (straddles_low ^ straddles_high) {
if env_read!("BREP_DEBUG_FRAG")
.map(|value| value == face.id.to_string())
.unwrap_or(false)
{
eprintln!(
" seam-image: face {} loop {loop_index} straddles u-wall (u=[{min_u:.6},{max_u:.6}]) — inserting {} period image",
face.id,
if straddles_low { "+1" } else { "-1" }
);
}
Some(if straddles_low { span } else { -span })
} else {
None
}
}
};
for (coedge_index, points, curve, start_vertex, end_vertex) in sampled {
let coedge = &loop_record.coedges[coedge_index];
for point in &points {
band_hull[0] = [band_hull[0][0].min(point.x), band_hull[0][1].max(point.x)];
band_hull[1] = [band_hull[1][0].min(point.y), band_hull[1][1].max(point.y)];
}
boundary_segments.extend(points.windows(2).map(|pair| (pair[0], pair[1])));
add_chain(
points.clone(),
ChainSource::Boundary {
coedge: coedge.clone(),
curve: curve.clone(),
},
format!("b:{operand}:{start_vertex}"),
vertex_point(solid, start_vertex)?,
format!("b:{operand}:{end_vertex}"),
vertex_point(solid, end_vertex)?,
true,
)?;
if let Some(shift) = image_shift {
let shifted: Vec<Vec2> = points
.iter()
.map(|point| Vec2 {
x: point.x + shift,
y: point.y,
})
.collect();
boundary_segments
.extend(shifted.windows(2).map(|pair| (pair[0], pair[1])));
add_chain(
shifted,
ChainSource::Boundary {
coedge: coedge.clone(),
curve,
},
format!("b:{operand}:{start_vertex}"),
vertex_point(solid, start_vertex)?,
format!("b:{operand}:{end_vertex}"),
vertex_point(solid, end_vertex)?,
true,
)?;
}
}
}
let (clamp_u, clamp_v) = {
let mut clamp_u = u_domain;
let mut clamp_v = v_domain;
let (closed_u, closed_v) = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
if (closed_u || closed_v) && std::env::var("BREP_FRAG_ENV_CLAMP").as_deref() != Ok("0") {
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
for point in &coedge.pcurve.control_points {
if point.w.abs() <= 1e-300 {
continue;
}
if closed_u {
clamp_u[0] = clamp_u[0].min(point.x / point.w);
clamp_u[1] = clamp_u[1].max(point.x / point.w);
}
if closed_v {
clamp_v[0] = clamp_v[0].min(point.y / point.w);
clamp_v[1] = clamp_v[1].max(point.y / point.w);
}
}
}
}
}
(clamp_u, clamp_v)
};
profile.boundary += profile.lap(lap_start);
for piece_id in active_pieces {
let piece = pieces_by_id[&piece_id];
let pcurve = pcurve_for(piece, face_key)?.clone();
let (mut points, original_flags) = sag_refine_chain(
&pcurve,
sample_chain(&pcurve)?,
sampling_bound,
)?;
for point in &mut points {
if point.x < clamp_u[0] {
point.x = clamp_u[0] + arrangement_tolerance * 10.0;
} else if point.x > clamp_u[1] {
point.x = clamp_u[1] - arrangement_tolerance * 10.0;
}
if point.y < clamp_v[0] {
point.y = clamp_v[0] + arrangement_tolerance * 10.0;
} else if point.y > clamp_v[1] {
point.y = clamp_v[1] - arrangement_tolerance * 10.0;
}
}
let snap_endpoint_to_domain = |point: Vec2| {
let candidates = [
(
(point.x - u_domain[0]).abs(),
Vec2 {
x: u_domain[0],
y: point.y,
},
),
(
(point.x - u_domain[1]).abs(),
Vec2 {
x: u_domain[1],
y: point.y,
},
),
(
(point.y - v_domain[0]).abs(),
Vec2 {
x: point.x,
y: v_domain[0],
},
),
(
(point.y - v_domain[1]).abs(),
Vec2 {
x: point.x,
y: v_domain[1],
},
),
];
let nearest = candidates
.into_iter()
.min_by(|first, second| first.0.total_cmp(&second.0))
.unwrap();
if nearest.0 <= arrangement_tolerance * 50.0 {
nearest.1
} else {
point
}
};
let last = points.len() - 1;
if !bridge_keys.contains(&format!("i:{}", piece.start_vertex_id)) {
points[0] = snap_endpoint_to_domain(points[0]);
}
if !bridge_keys.contains(&format!("i:{}", piece.end_vertex_id)) {
points[last] = snap_endpoint_to_domain(points[last]);
}
for point in &mut points {
*point = rebase_point(*point);
}
let mut lift = Vec2 { x: 0.0, y: 0.0 };
if std::env::var("BREP_CUT_PERIOD_LIFT").as_deref() != Ok("0") {
let closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
for axis in 0..2 {
let (is_closed, domain) = if axis == 0 { (closed.0, u_domain) } else { (closed.1, v_domain) };
let [low, high] = band_hull[axis];
let span = domain[1] - domain[0];
if !is_closed || !(span > 0.0) || !(low < domain[0] || high > domain[1]) {
continue;
}
let coordinate = |point: &Vec2| if axis == 0 { point.x } else { point.y };
let middle = coordinate(&points[points.len() / 2]);
let inside = |value: f64| value > low && value < high;
if inside(middle) {
continue;
}
let images: Vec<f64> = [-2.0, -1.0, 1.0, 2.0]
.into_iter()
.map(|periods| periods * span)
.filter(|shift| inside(middle + shift))
.collect();
let [shift] = images[..] else {
continue;
};
let slack = arrangement_tolerance * 50.0;
let (reach_low, reach_high) = points.iter().map(coordinate).fold(
(f64::INFINITY, f64::NEG_INFINITY),
|(lo, hi), value| (lo.min(value), hi.max(value)),
);
if reach_low + shift < low - slack || reach_high + shift > high + slack {
let [first, second] = piece.support_faces;
return Err(KernelRefusal::internal(
KernelStage::Fragment,
"fragment.face_split",
format!(
"fragment_face: section piece {piece_id} of faces {}:{} x {}:{} crosses \
the seam wall of face {}:{}'s unwrapped band ({} in [{low:.9}, {high:.9}], \
the section's period image [{:.9}, {:.9}]) without a vertex there",
first.operand,
first.face_id,
second.operand,
second.face_id,
operand,
face.id,
if axis == 0 { "u" } else { "v" },
reach_low + shift,
reach_high + shift,
),
));
}
for point in &mut points {
if axis == 0 {
point.x += shift;
} else {
point.y += shift;
}
}
if axis == 0 {
lift.x = shift;
} else {
lift.y = shift;
}
if env_read!("BREP_DEBUG_FRAG")
.map(|value| value == face.id.to_string())
.unwrap_or(false)
{
eprintln!(
" period lift: face {} cut p{piece_id} {} {shift:+} into the band [{low:.6},{high:.6}]",
face.id,
if axis == 0 { "u" } else { "v" }
);
}
}
}
points = enforce_boundary_crossing_parity(
points,
&original_flags,
&boundary_segments,
arrangement_tolerance,
env_read!("BREP_DEBUG_FRAG")
.map(|value| value == face.id.to_string())
.unwrap_or(false),
face.id,
);
let start_point = index
.vertex_points
.get(&piece.start_vertex_id)
.copied()
.ok_or_else(|| KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", "fragment_face: missing imprint start vertex"))?;
let end_point = index
.vertex_points
.get(&piece.end_vertex_id)
.copied()
.ok_or_else(|| KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", "fragment_face: missing imprint end vertex"))?;
add_chain(
points,
ChainSource::Cut {
piece_id,
pcurve,
lift,
curve: trimmed_curve(&piece.curve, piece.t0, piece.t1)?,
shared_ring: piece.shared_edge.filter(|_| {
piece
.curve
.evaluate(piece.t0)
.ok()
.zip(piece.curve.evaluate(piece.t1).ok())
.is_some_and(|(a, b)| a.sub(b).length() <= 1e-6)
}),
},
format!("i:{}", piece.start_vertex_id),
start_point,
format!("i:{}", piece.end_vertex_id),
end_point,
false,
)?;
}
drop(add_chain);
let pole_gap_closed = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
if (pole_gap_closed.0 || pole_gap_closed.1)
&& std::env::var("BREP_POLE_GAP_BRIDGE").as_deref() != Ok("0")
{
let node_tol = arrangement_tolerance * 10.0;
let mut nodes: Vec<(Vec2, usize)> = Vec::new();
for chain in &chains {
if !matches!(chain.source, ChainSource::Boundary { .. }) {
continue;
}
for point in [chain.start, chain.end] {
if let Some(entry) = nodes.iter_mut().find(|(q, _)| q.sub(point).length() <= node_tol)
{
entry.1 += 1;
} else {
nodes.push((point, 1));
}
}
}
let odd: Vec<Vec2> = nodes
.iter()
.filter(|(_, count)| count % 2 == 1)
.map(|(point, _)| *point)
.collect();
let vertex_of = |point: Vec2| -> Option<u64> {
vertex_images.iter().find_map(|(key, images)| {
images
.iter()
.any(|image| image.sub(point).length() <= node_tol)
.then(|| key.rsplit(':').next().and_then(|id| id.parse::<u64>().ok()))
.flatten()
})
};
let mut by_vertex: HashMap<u64, Vec<Vec2>> = HashMap::default();
for point in &odd {
if let Some(vertex_id) = vertex_of(*point) {
by_vertex.entry(vertex_id).or_default().push(*point);
}
}
let curve_degenerate = |curve: &NurbsCurve| -> bool {
let Ok(domain) = curve.domain() else {
return false;
};
let Ok(base) = curve.evaluate(domain[0]) else {
return false;
};
[0.5, 1.0].iter().all(|fraction| {
curve
.evaluate(domain[0] + (domain[1] - domain[0]) * fraction)
.map(|point| point.sub(base).length() <= 1e-5)
.unwrap_or(false)
})
};
for (vertex_id, images) in by_vertex {
if images.len() != 2 {
continue;
}
let (a, b) = (images[0], images[1]);
let mut base: Option<Vec3> = None;
let mut degenerate = true;
for step in 0..=8 {
let fraction = step as f64 / 8.0;
let uv = a.add(b.sub(a).scale(fraction));
match face.surface.evaluate(uv.x, uv.y) {
Ok(point) => match base {
None => base = Some(point),
Some(reference) => {
if point.sub(reference).length() > 1e-5 {
degenerate = false;
break;
}
}
},
Err(_) => {
degenerate = false;
break;
}
}
}
if !degenerate {
continue;
}
let Some(source) = chains.iter().find_map(|chain| match &chain.source {
ChainSource::Boundary { coedge, curve }
if chain.start.sub(chain.end).length() <= node_tol
&& chain.count <= 1
&& (chain.start.sub(a).length() <= node_tol
|| chain.start.sub(b).length() <= node_tol)
&& curve_degenerate(curve) =>
{
Some(ChainSource::Boundary {
coedge: coedge.clone(),
curve: curve.clone(),
})
}
_ => None,
}) else {
continue;
};
let chain_index = chains.len();
segments.push(Segment2 {
a,
b,
tag: segment_tag(chain_index, 0),
});
chains.push(Chain {
source,
count: 1,
start: a,
end: b,
segments: vec![(a, b)],
});
boundary_segments.push((a, b));
if env_read!("BREP_DEBUG_FRAG")
.map(|value| value == face.id.to_string())
.unwrap_or(false)
{
eprintln!(
" pole-gap bridge: face {} vertex {vertex_id} joins \
({:.6},{:.6})->({:.6},{:.6}) along a degenerate iso-line",
face.id, a.x, a.y, b.x, b.y
);
}
}
}
let coincidence_tolerance = arrangement_tolerance * 50.0;
let point_segment_distance = |point: Vec2, a: Vec2, b: Vec2| -> f64 {
let segment = b.sub(a);
let length_squared = segment.dot(segment);
if length_squared <= 1e-30 {
return point.sub(a).length();
}
let parameter = (point.sub(a).dot(segment) / length_squared).clamp(0.0, 1.0);
point.sub(a.add(segment.scale(parameter))).length()
};
let near_chain = |point: Vec2, chain: &Chain, tolerance: f64| {
chain
.segments
.iter()
.any(|(a, b)| point_segment_distance(point, *a, *b) <= tolerance)
};
let midpoint_tolerance = coincidence_tolerance * 8.0;
profile.cuts += profile.lap(lap_start);
let corner_veto_enabled = std::env::var("BREP_DUP_DROP_CORNER_VETO")
.map(|value| value != "0")
.unwrap_or(true);
let node_tolerance = arrangement_tolerance;
let terminates_at_distinct_boundary_vertex = |cut_end: Vec2, matched_end: Vec2| -> bool {
cut_end.sub(matched_end).length() > node_tolerance
&& chains.iter().any(|other| {
matches!(other.source, ChainSource::Boundary { .. })
&& (other.start.sub(cut_end).length() <= node_tolerance
|| other.end.sub(cut_end).length() <= node_tolerance)
})
};
let debug_drop = env_read!("BREP_DEBUG_FRAG")
.map(|value| value == face.id.to_string())
.unwrap_or(false);
let micro_boundary_skip = std::env::var("BREP_DUP_CUT_MICRO_BOUNDARY").as_deref() != Ok("0");
let boundary_extent = |boundary: &Chain| -> f64 {
boundary
.segments
.iter()
.map(|(a, b)| b.sub(*a).length())
.sum()
};
let pole_ends = std::env::var("BREP_DUP_DROP_POLE").as_deref() != Ok("0");
let degenerate_chains: Vec<usize> = chains
.iter()
.enumerate()
.filter(|(_, chain)| match &chain.source {
ChainSource::Boundary { coedge, .. } => solid
.edges
.iter()
.any(|edge| edge.id == coedge.edge_id && edge.degenerate),
ChainSource::Cut { .. } => false,
})
.map(|(index, _)| index)
.collect();
let on_one_pole = |a: Vec2, b: Vec2| -> bool {
degenerate_chains.iter().any(|&pole| {
near_chain(a, &chains[pole], coincidence_tolerance)
&& near_chain(b, &chains[pole], coincidence_tolerance)
})
};
let coincident_in_space = |cut: &Chain, boundary: &Chain, weld_scale: bool| -> bool {
let ChainSource::Boundary { curve, coedge } = &boundary.source else {
return false;
};
let ChainSource::Cut { curve: section, piece_id, .. } = &cut.source else {
return false;
};
let Ok([start, end]) = section.domain() else {
return false;
};
let mut chart_length = 0.0;
let mut space_length = 0.0;
for (a, b) in &boundary.segments {
let (Ok(pa), Ok(pb)) = (face.surface.evaluate(a.x, a.y), face.surface.evaluate(b.x, b.y)) else {
return false;
};
chart_length += b.sub(*a).length();
space_length += pb.sub(pa).length();
}
if chart_length <= 0.0 || space_length <= 0.0 {
return false;
}
let copy_boundary = index.pieces_by_id.get(piece_id).is_some_and(|piece| {
boundary_has_cosurface_partner(solid, face_key, coedge.edge_id, piece, index)
});
let bound = if weld_scale {
if copy_boundary { crate::tolerance::WELD_FLOOR } else { 1e-7 }
} else {
coincidence_tolerance * space_length / chart_length
};
let stations = cut.segments.len().max(16);
(0..=stations).all(|index| {
section
.evaluate(start + (end - start) * index as f64 / stations as f64)
.ok()
.and_then(|at| crate::project_point_to_curve(curve, at).ok())
.is_some_and(|projection| projection.distance <= bound)
})
};
let mut dropped_chains = HashSet::default();
for (index, chain) in chains.iter().enumerate() {
if !matches!(chain.source, ChainSource::Cut { .. }) {
continue;
}
let mut matched_boundary: Option<usize> = None;
for (boundary_index, boundary) in chains.iter().enumerate() {
if !matches!(boundary.source, ChainSource::Boundary { .. }) {
continue;
}
if micro_boundary_skip && boundary_extent(boundary) <= coincidence_tolerance {
continue;
}
let forward = boundary.start.sub(chain.start).length() <= coincidence_tolerance
&& boundary.end.sub(chain.end).length() <= coincidence_tolerance;
let reversed = boundary.start.sub(chain.end).length() <= coincidence_tolerance
&& boundary.end.sub(chain.start).length() <= coincidence_tolerance;
let same_end = |a: Vec2, b: Vec2| a.sub(b).length() <= coincidence_tolerance || on_one_pole(a, b);
let (forward, reversed, at_pole) = if forward || reversed || !pole_ends || degenerate_chains.is_empty() {
(forward, reversed, false)
} else {
let forward = same_end(boundary.start, chain.start) && same_end(boundary.end, chain.end);
let reversed = same_end(boundary.start, chain.end) && same_end(boundary.end, chain.start);
(forward, reversed, true)
};
if !(forward || reversed) {
continue;
}
let interior_coincident = if at_pole {
coincident_in_space(chain, boundary, false)
} else {
chain.segments.iter().all(|(a, b)| {
near_chain(*a, boundary, coincidence_tolerance)
&& near_chain(*b, boundary, coincidence_tolerance)
&& near_chain(a.add(*b).scale(0.5), boundary, midpoint_tolerance)
})
};
if !interior_coincident {
continue;
}
let identical_segment = chain.segments.len() == 1
&& boundary.segments.len() == 1
&& ((chain.start.sub(boundary.start).length() <= node_tolerance
&& chain.end.sub(boundary.end).length() <= node_tolerance)
|| (chain.start.sub(boundary.end).length() <= node_tolerance
&& chain.end.sub(boundary.start).length() <= node_tolerance));
if !identical_segment
&& std::env::var("BREP_DUP_CUT_GEOMETRY").as_deref() != Ok("0")
&& !coincident_in_space(chain, boundary, true)
{
continue;
}
if corner_veto_enabled {
let (bnd_for_start, bnd_for_end) = if forward {
(boundary.start, boundary.end)
} else {
(boundary.end, boundary.start)
};
if terminates_at_distinct_boundary_vertex(chain.start, bnd_for_start)
|| terminates_at_distinct_boundary_vertex(chain.end, bnd_for_end)
{
continue;
}
}
matched_boundary = Some(boundary_index);
break;
}
if let Some(boundary_index) = matched_boundary {
if debug_drop {
let boundary = &chains[boundary_index];
eprintln!(
" drop cut chain {} as duplicate of boundary chain {} (endpoint gaps {:.3e}/{:.3e})",
index,
boundary_index,
boundary.start.sub(chain.start).length(),
boundary.end.sub(chain.end).length(),
);
}
dropped_chains.insert(index);
}
}
if !dropped_chains.is_empty() {
segments.retain(|segment| {
tag_chain(&segment.tag)
.map(|chain| !dropped_chains.contains(&(chain as usize)))
.unwrap_or(true)
});
}
let debug = env_read!("BREP_DEBUG_FRAG")
.map(|value| value == face.id.to_string())
.unwrap_or(false);
if debug {
eprintln!(
"fragment_face {}: domain u={:?} v={:?} arr_tol={:.3e} chains={} segments={}",
face.id,
u_domain,
v_domain,
arrangement_tolerance,
chains.len(),
segments.len()
);
for (index, chain) in chains.iter().enumerate() {
let kind = match &chain.source {
ChainSource::Boundary { coedge, .. } => format!("boundary e{}", coedge.edge_id),
ChainSource::Cut { piece_id, .. } => format!("cut p{piece_id}"),
};
eprintln!(
" chain {index} {kind} count={} start=({:.9},{:.9}) end=({:.9},{:.9})",
chain.count, chain.start.x, chain.start.y, chain.end.x, chain.end.y
);
}
}
if chains.iter().any(|c| matches!(c.source, ChainSource::Cut { .. })) {
let endpoints: Vec<(usize, bool, Vec2)> = chains
.iter()
.enumerate()
.flat_map(|(i, c)| [(i, false, c.start), (i, true, c.end)])
.collect();
let anchored = |chain: usize, point: Vec2| -> bool {
boundary_side(point).is_some()
|| endpoints.iter().any(|(other_chain, _, other_point)| {
*other_chain != chain
&& other_point.sub(point).length() <= junction_radius
})
};
let cut_chain_has_interior_crossing = |i: usize| -> bool {
let chain = &chains[i];
chains.iter().enumerate().any(|(j, other)| {
if j == i
|| dropped_chains.contains(&j)
|| !matches!(other.source, ChainSource::Cut { .. })
{
return false;
}
chain.segments.iter().any(|&(a0, a1)| {
other.segments.iter().any(|&(b0, b1)| {
let Some([t, _]) = crate::arrangement::segment_intersection(
a0,
a1,
b0,
b1,
arrangement_tolerance,
) else {
return false;
};
let crossing = a0.add(a1.sub(a0).scale(t));
crossing.sub(chain.start).length() > junction_radius
&& crossing.sub(chain.end).length() > junction_radius
})
})
})
};
let boundary_anchor = bridge_boundary_t_junction_anchor_enabled();
let lies_on_another_cut_run = |i: usize, point: Vec2| -> bool {
bridge_t_junction_anchor_enabled()
&& chains.iter().enumerate().any(|(j, other)| {
j != i
&& !dropped_chains.contains(&j)
&& (boundary_anchor || matches!(other.source, ChainSource::Cut { .. }))
&& other.segments.iter().any(|&(a, b)| {
let along = b.sub(a);
let span = along.dot(along);
let t = if span > 0.0 {
(point.sub(a).dot(along) / span).clamp(0.0, 1.0)
} else {
0.0
};
a.add(along.scale(t)).sub(point).length() <= arrangement_tolerance
})
})
};
let mut loose: Vec<(usize, bool, Vec2)> = Vec::new();
for (i, c) in chains.iter().enumerate() {
if !matches!(c.source, ChainSource::Cut { .. }) {
continue;
}
for (is_end, this, other) in [(false, c.start, c.end), (true, c.end, c.start)] {
if !anchored(i, this) && anchored(i, other) {
if lies_on_another_cut_run(i, this) {
if debug {
eprintln!(" cut chain {i} end {} is a T-junction on another chain's run", if is_end { "end" } else { "start" });
}
continue;
}
if cut_chain_has_interior_crossing(i) {
if debug {
eprintln!(
" cut chain {i} loose {} excluded from junction bridging — \
the chain crosses another cut chain mid-run; arrangement \
pruning owns the overshoot stub",
if is_end { "end" } else { "start" },
);
}
continue;
}
loose.push((i, is_end, this));
}
}
}
let bridge_band = (diagonal * 0.02).max(junction_radius * 4.0);
if debug && !loose.is_empty() {
eprintln!(" junction_radius={junction_radius:.6} bridge_band={bridge_band:.6} loose={loose:?}");
}
let skip_crossing_pairs = bridge_skip_crossing_pairs_enabled();
let chains_cross = |i: usize, j: usize| -> bool {
chains[i].segments.iter().any(|&(a0, a1)| {
chains[j].segments.iter().any(|&(b0, b1)| {
crate::arrangement::segment_intersection(a0, a1, b0, b1, arrangement_tolerance).is_some()
})
})
};
let mut pairs: Vec<(f64, usize, usize)> = Vec::new();
for a in 0..loose.len() {
for b in (a + 1)..loose.len() {
if loose[a].0 == loose[b].0 {
continue; }
if skip_crossing_pairs && chains_cross(loose[a].0, loose[b].0) {
if debug {
eprintln!(" loose ends of chains {} and {} are not bridged: the chains cross", loose[a].0, loose[b].0);
}
continue;
}
let gap = loose[a].2.sub(loose[b].2).length();
if gap <= bridge_band {
pairs.push((gap, a, b));
}
}
}
pairs.sort_by(|x, y| x.0.total_cmp(&y.0));
let bridge_3d_gate = std::env::var("BREP_BRIDGE_3D_GATE").as_deref() != Ok("0");
let face_diameter3 = if bridge_3d_gate && !pairs.is_empty() {
let mut samples: Vec<Vec3> = Vec::new();
'grid: for i in 0..3 {
for j in 0..3 {
let u = u_domain[0] + (u_domain[1] - u_domain[0]) * i as f64 / 2.0;
let v = v_domain[0] + (v_domain[1] - v_domain[0]) * j as f64 / 2.0;
match face.surface.evaluate(u, v) {
Ok(point) => samples.push(point),
Err(_) => {
samples.clear();
break 'grid;
}
}
}
}
let mut diameter = 0.0f64;
for (index, a) in samples.iter().enumerate() {
for b in samples.iter().skip(index + 1) {
diameter = diameter.max(a.sub(*b).length());
}
}
(diameter > 0.0).then_some(diameter)
} else {
None
};
let mut consumed = vec![false; loose.len()];
let mut bridged = 0usize;
for (gap, a, b) in pairs {
if consumed[a] || consumed[b] {
continue;
}
consumed[a] = true;
consumed[b] = true;
let (chain_a, _, point_a) = loose[a];
let (chain_b, _, point_b) = loose[b];
if let Some(diameter) = face_diameter3 {
let wrapped_a = wrap_back(point_a);
let wrapped_b = wrap_back(point_b);
if let (Ok(position_a), Ok(position_b)) = (
face.surface.evaluate(wrapped_a.x, wrapped_a.y),
face.surface.evaluate(wrapped_b.x, wrapped_b.y),
) {
let gap3 = position_a.sub(position_b).length();
let limit = 0.01 * diameter;
if gap3 > limit {
return Err(KernelRefusal::internal(KernelStage::Fragment, "fragment.face_split", format!(
"fragment_face {}: cut junction bridge refused — 3D gap \
{gap3:.4} exceeds {limit:.4} (1% of face diameter \
{diameter:.4}) across uv gap {gap:.6}; a section piece \
is missing between chains {chain_a} and {chain_b}",
face.id
)));
}
}
}
let midpoint = point_a.add(point_b).scale(0.5);
if debug {
eprintln!(
" bridging chain {chain_a} at ({:.9},{:.9}) and chain {chain_b} at ({:.9},{:.9}), uv gap {gap:.6}",
point_a.x, point_a.y, point_b.x, point_b.y
);
}
for (chain, old) in [(chain_a, point_a), (chain_b, point_b)] {
if chains[chain].start.sub(old).length() <= 1e-15 {
chains[chain].start = midpoint;
}
if chains[chain].end.sub(old).length() <= 1e-15 {
chains[chain].end = midpoint;
}
for seg in &mut chains[chain].segments {
if seg.0.sub(old).length() <= 1e-15 {
seg.0 = midpoint;
}
if seg.1.sub(old).length() <= 1e-15 {
seg.1 = midpoint;
}
}
for segment in segments.iter_mut() {
if tag_chain(&segment.tag) != Some(chain as u64) {
continue;
}
if segment.a.sub(old).length() <= 1e-15 {
segment.a = midpoint;
}
if segment.b.sub(old).length() <= 1e-15 {
segment.b = midpoint;
}
}
}
bridged += 1;
}
if debug && bridged > 0 {
eprintln!(" bridged {bridged} broken cut junction(s)");
}
}
if std::env::var("BREP_GRAZE_HOOK_CLIP").as_deref() != Ok("0") {
let raw_extent = if solid.vertices.is_empty() {
0.0
} else {
let mut lo = Vec3::new(f64::INFINITY, f64::INFINITY, f64::INFINITY);
let mut hi = Vec3::new(f64::NEG_INFINITY, f64::NEG_INFINITY, f64::NEG_INFINITY);
for v in &solid.vertices {
lo.x = lo.x.min(v.point.x);
lo.y = lo.y.min(v.point.y);
lo.z = lo.z.min(v.point.z);
hi.x = hi.x.max(v.point.x);
hi.y = hi.y.max(v.point.y);
hi.z = hi.z.max(v.point.z);
}
hi.sub(lo).length()
};
let band = 2.0 * 1e-5 * raw_extent.max(1.0);
let sep_cap = (1.5e-3 * raw_extent).max(band);
let graze_debug = debug || std::env::var("BREP_DEBUG_GRAZE").as_deref() == Ok("1");
let mut plans: Vec<(usize, Vec<(Vec2, Vec2)>, f64)> = Vec::new();
for ci in 0..chains.len() {
if !matches!(chains[ci].source, ChainSource::Cut { .. }) {
continue;
}
if dropped_chains.contains(&ci) {
continue;
}
if chains[ci].segments.is_empty() {
continue;
}
let mut lo_cut: Option<usize> = None; let mut hi_cut: Option<usize> = None; let mut measured = 0.0f64;
let mut bail = false;
for &at_end in &[false, true] {
let p = if at_end {
chains[ci].end
} else {
chains[ci].start
};
let Ok(p3) = face.surface.evaluate(p.x, p.y) else {
continue;
};
for bi in 0..chains.len() {
if bi == ci || !matches!(chains[bi].source, ChainSource::Boundary { .. }) {
continue;
}
let shares = chains[bi].start.sub(p).length() <= node_tolerance
|| chains[bi].end.sub(p).length() <= node_tolerance;
if !shares {
continue;
}
for si in 0..chains[ci].segments.len() {
let (sa, sb) = chains[ci].segments[si];
for &(ba, bb) in &chains[bi].segments {
let Some([t1, t2]) = crate::arrangement::segment_intersection(
sa,
sb,
ba,
bb,
arrangement_tolerance,
) else {
continue;
};
if !(t1 > 1e-3 && t1 < 1.0 - 1e-3 && t2 > 1e-3 && t2 < 1.0 - 1e-3) {
continue;
}
let q = Vec2 {
x: sa.x + (sb.x - sa.x) * t1,
y: sa.y + (sb.y - sa.y) * t1,
};
let Ok(q3) = face.surface.evaluate(q.x, q.y) else {
continue;
};
let dist = q3.sub(p3).length();
if dist > sep_cap {
bail = true;
break;
}
measured = measured.max(dist);
if at_end {
hi_cut = Some(hi_cut.map_or(si, |m| m.min(si)));
} else {
lo_cut = Some(lo_cut.map_or(si, |m| m.max(si)));
}
}
if bail {
break;
}
}
if bail {
break;
}
}
if bail {
break;
}
}
if bail || (lo_cut.is_none() && hi_cut.is_none()) {
continue;
}
let segs = &chains[ci].segments;
let keep_lo = lo_cut.map(|m| m + 1).unwrap_or(0);
let keep_hi = hi_cut.unwrap_or(segs.len());
if keep_lo >= keep_hi {
continue;
}
let mut points: Vec<Vec2> = Vec::with_capacity(keep_hi - keep_lo + 3);
if lo_cut.is_some() {
points.push(chains[ci].start);
}
points.push(segs[keep_lo].0);
for si in keep_lo..keep_hi {
points.push(segs[si].1);
}
if hi_cut.is_some() {
points.push(chains[ci].end);
}
let new_segments: Vec<(Vec2, Vec2)> = points
.windows(2)
.filter(|w| w[0].sub(w[1]).length() > 0.0)
.map(|w| (w[0], w[1]))
.collect();
if new_segments.is_empty() {
continue;
}
if graze_debug {
eprintln!(
"graze-hook-clip: face {} cut chain {} measured_band={:.3e} sep_cap={:.3e} \
segs {} -> {} (lo_cut={:?} hi_cut={:?})",
face.id,
ci,
measured,
sep_cap,
chains[ci].segments.len(),
new_segments.len(),
lo_cut,
hi_cut
);
}
plans.push((ci, new_segments, measured));
}
for (ci, new_segments, _) in plans {
chains[ci].count = new_segments.len();
chains[ci].segments = new_segments.clone();
segments.retain(|s| tag_chain(&s.tag) != Some(ci as u64));
for (segment_index, (a, b)) in new_segments.into_iter().enumerate() {
segments.push(Segment2 {
a,
b,
tag: segment_tag(ci, segment_index),
});
}
}
}
profile.dedup_bridge += profile.lap(lap_start);
profile.arranged_faces += 1;
let regions = arrange_segments(&segments, arrangement_tolerance).or_refuse(KernelStage::Fragment, "arrange_segments")?;
profile.arrange += profile.lap(lap_start);
profile.region_faces += 1;
let source_winding = if std::env::var("BREP_FRAGMENT_SOURCE_WINDING").as_deref() == Ok("0") {
None
} else {
source_loop_winding_negative(face)?
};
let reverse_loops = source_winding.unwrap_or(!face.same_sense);
if debug && reverse_loops != !face.same_sense {
eprintln!(
" face {}: source loop winds {} against same_sense={} — orienting fragments to the source",
face.id,
if reverse_loops { "CW" } else { "CCW" },
face.same_sense
);
}
let mut fragments = Vec::new();
for mut region in regions {
super::loops::detach_touched_closed_boundaries(&mut region, &chains, arrangement_tolerance)?;
let order = FragmentOrder {
area: region.outer_area,
key: super::loops::region_topology_key(®ion.outer, ®ion.holes),
};
let mut region_lap = profile.enabled.then(Instant::now);
let arranged: Vec<Vec2> = interior_points(®ion.outer, ®ion.holes, region.area, 3);
let interior_ms = profile.lap(&mut region_lap).as_secs_f64() * 1000.0;
let candidates: Vec<Vec2> = arranged.iter().copied().map(wrap_back).collect();
let Some(&test_uv) = candidates.first() else {
if debug {
eprintln!(" region area={:.6e}: no interior point", region.area);
}
continue;
};
let mut containment = parameter_point_in_face(face, test_uv, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")?;
if containment != PolygonClass::Inside
&& band_rebase == [None, None]
&& (arranged[0].x != test_uv.x || arranged[0].y != test_uv.y)
&& std::env::var("BREP_REGION_TRIM_FRAME").as_deref() != Ok("0")
{
containment = parameter_point_in_face(face, arranged[0], 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")?;
if debug {
eprintln!(
" region area={:.6e} test=({:.9},{:.9}) asked in the trim's frame at ({:.9},{:.9}): {:?}",
region.area, test_uv.x, test_uv.y, arranged[0].x, arranged[0].y,
containment == PolygonClass::Inside
);
}
}
if containment != PolygonClass::Inside {
if debug {
eprintln!(
" region area={:.6e} test=({:.9},{:.9}): outside trimmed face",
region.area, test_uv.x, test_uv.y
);
}
continue;
}
if debug {
eprintln!(
" region area={:.6e} test=({:.9},{:.9}): KEPT",
region.area, test_uv.x, test_uv.y
);
}
let containment_ms = profile.lap(&mut region_lap).as_secs_f64() * 1000.0;
let Some(outer_loop) = build_loop(
®ion.outer,
reverse_loops,
&chains,
arrangement_tolerance,
&face.surface,
)?
else {
if debug {
eprintln!(" region area={:.6e}: dropped (degenerate antenna)", region.area);
}
continue;
};
let mut loops = vec![outer_loop];
for hole in ®ion.holes {
if let Some(hole_loop) = build_loop(
hole,
reverse_loops,
&chains,
arrangement_tolerance,
&face.surface,
)? {
loops.push(hole_loop);
}
}
for loop_record in &mut loops {
for coedge in &mut loop_record.coedges {
if let FragmentEdgeSource::Boundary {
operand: source_operand,
..
} = &mut coedge.source
{
*source_operand = operand;
}
}
}
let mut extra_test_points = Vec::new();
for &candidate in candidates.iter().skip(1) {
if parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside {
extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
}
}
if profile.enabled {
crate::imprint::profile_emit(format_args!("fragment.region face={} edges={} holes={} interior={interior_ms:.3} containment={containment_ms:.3} loops={:.3}",
face.id, region.outer.len(), region.holes.len(), profile.lap(&mut region_lap).as_secs_f64() * 1000.0));
}
fragments.push(FaceFragmentRecord {
operand,
source_face_id: face.id,
surface: face.surface.clone(),
same_sense: face.same_sense,
loops,
test_point: face.surface.evaluate(test_uv.x, test_uv.y).or_refuse(KernelStage::Fragment, "evaluate")?,
test_uv,
extra_test_points,
order: Some(order),
});
}
profile.regions += profile.lap(lap_start);
if fragments.is_empty() && piece_ids.is_empty() {
const GRID: usize = 12;
let mut test_uv: Option<Vec2> = None;
let mut extra_test_points: Vec<Vec3> = Vec::new();
'search: for iu in 1..GRID {
for iv in 1..GRID {
let candidate = Vec2 {
x: u_domain[0] + (u_domain[1] - u_domain[0]) * iu as f64 / GRID as f64,
y: v_domain[0] + (v_domain[1] - v_domain[0]) * iv as f64 / GRID as f64,
};
if parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside {
if test_uv.is_none() {
test_uv = Some(candidate);
} else {
extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
if extra_test_points.len() >= 2 {
break 'search;
}
}
}
}
}
if test_uv.is_none() {
let (closed_u, closed_v) = face.surface.closed_directions().or_refuse(KernelStage::Fragment, "closed_directions")?;
if closed_u || closed_v {
let u_period = u_domain[1] - u_domain[0];
let v_period = v_domain[1] - v_domain[0];
let mut loop_polygons: Vec<Vec<Vec2>> = Vec::new();
let mut straddled = false;
let (mut umin, mut umax, mut vmin, mut vmax) = (
f64::INFINITY,
f64::NEG_INFINITY,
f64::INFINITY,
f64::NEG_INFINITY,
);
for loop_record in &face.loops {
let offsets = crate::topology::loop_seam_offsets(
&loop_record.coedges,
closed_u,
closed_v,
u_period,
v_period,
).or_refuse(KernelStage::Fragment, "csg.fragment.face_split")?;
if offsets.iter().any(|o| o[0] != 0.0 || o[1] != 0.0) {
straddled = true;
}
let mut polygon = Vec::new();
for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
let [d0, d1] = coedge.pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
let samples = 32usize;
for k in 0..samples {
let t = d0 + (d1 - d0) * k as f64 / samples as f64;
let p = coedge.pcurve.evaluate(t).or_refuse(KernelStage::Fragment, "evaluate")?;
let uv = Vec2 {
x: p.x + offsets[coedge_index][0],
y: p.y + offsets[coedge_index][1],
};
umin = umin.min(uv.x);
umax = umax.max(uv.x);
vmin = vmin.min(uv.y);
vmax = vmax.max(uv.y);
polygon.push(uv);
}
}
loop_polygons.push(polygon);
}
if straddled && umax > umin && vmax > vmin {
let wrap = |value: f64, lo: f64, span: f64| {
if span > 0.0 {
lo + (value - lo).rem_euclid(span)
} else {
value
}
};
'seam: for iu in 1..GRID {
for iv in 1..GRID {
let cu = umin + (umax - umin) * iu as f64 / GRID as f64;
let cv = vmin + (vmax - vmin) * iv as f64 / GRID as f64;
let candidate = Vec2 { x: cu, y: cv };
let winding = loop_polygons
.iter()
.filter(|polygon| point_in_polygon(candidate, polygon))
.count();
if winding % 2 == 0 {
continue;
}
let mapped = Vec2 {
x: if closed_u {
wrap(cu, u_domain[0], u_period)
} else {
cu
},
y: if closed_v {
wrap(cv, v_domain[0], v_period)
} else {
cv
},
};
if test_uv.is_none() {
test_uv = Some(mapped);
} else {
extra_test_points
.push(face.surface.evaluate(mapped.x, mapped.y).or_refuse(KernelStage::Fragment, "evaluate")?);
if extra_test_points.len() >= 2 {
break 'seam;
}
}
}
}
}
}
}
if let Some(uv) = test_uv {
fragments.push(FaceFragmentRecord {
operand,
source_face_id: face.id,
surface: face.surface.clone(),
same_sense: face.same_sense,
loops: face
.loops
.iter()
.map(|loop_record| FragmentLoop {
coedges: loop_record
.coedges
.iter()
.map(|coedge| FragmentCoedge {
source: FragmentEdgeSource::Boundary {
operand,
edge_id: coedge.edge_id,
},
forward: coedge.forward,
pcurve: coedge.pcurve.clone(),
})
.collect(),
})
.collect(),
test_point: face.surface.evaluate(uv.x, uv.y).or_refuse(KernelStage::Fragment, "evaluate")?,
test_uv: uv,
extra_test_points,
order: None,
});
}
}
profile.rescue += profile.lap(lap_start);
Ok(fragments)
}
pub fn fragment_solid(
solid: &BrepSolid,
operand: u8,
imprint: &ImprintResultRecord,
) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
fragment_solid_against(solid, operand, imprint, None)
}
pub(crate) fn bounded_carriers(solid: &BrepSolid) -> bool {
solid.shells.iter().flat_map(|s| &s.faces).all(|face| bounded_carrier(face))
}
fn bounded_carrier(face: &FaceRecord) -> bool {
let (Ok(u), Ok(v)) = (face.surface.domain_u(), face.surface.domain_v()) else { return false };
let positive_finite = |p: &crate::Vec4| {
p.w > 0.0 && p.w.is_finite() && p.point().is_ok_and(|v|
v.x.is_finite() && v.y.is_finite() && v.z.is_finite())
};
face.surface.control_points.iter().flatten().all(positive_finite)
&& face.loops.iter().flat_map(|lp| &lp.coedges).all(|coedge| {
coedge.pcurve.control_points.iter().all(|p| {
positive_finite(p) && p.point().is_ok_and(|p|
p.x >= u[0] && p.x <= u[1] && p.y >= v[0] && p.y <= v[1])
})
})
}
pub(crate) fn face_separated(face: &FaceRecord, bounds: crate::Aabb, band: f64) -> bool {
bounded_carrier(face) && crate::Aabb::from_surface_controls(&face.surface)
.is_ok_and(|own| !own.intersects(bounds, band))
}
fn trim_probe_enabled() -> bool {
static ON: std::sync::OnceLock<bool> = std::sync::OnceLock::new();
*ON.get_or_init(|| !matches!(std::env::var("BREP_FRAGMENT_TRIM_PROBE").as_deref(), Ok("0") | Ok("off")))
}
fn untouched_fragment(
face: &FaceRecord,
operand: u8,
uv: Vec2,
extra_test_points: Vec<Vec3>,
) -> Result<FaceFragmentRecord, KernelRefusal> {
Ok(FaceFragmentRecord {
operand,
source_face_id: face.id,
surface: face.surface.clone(),
same_sense: face.same_sense,
loops: face
.loops
.iter()
.map(|loop_record| FragmentLoop {
coedges: loop_record
.coedges
.iter()
.map(|coedge| FragmentCoedge {
source: FragmentEdgeSource::Boundary {
operand,
edge_id: coedge.edge_id,
},
forward: coedge.forward,
pcurve: coedge.pcurve.clone(),
})
.collect(),
})
.collect(),
test_point: face.surface.evaluate(uv.x, uv.y).or_refuse(KernelStage::Fragment, "evaluate")?,
test_uv: uv,
extra_test_points,
order: None,
})
}
fn trim_interior_probe(
face: &FaceRecord,
u: [f64; 2],
v: [f64; 2],
) -> Result<Option<(Vec2, Vec<Vec3>)>, KernelRefusal> {
let diagonal = ((u[1] - u[0]).powi(2) + (v[1] - v[0]).powi(2)).sqrt();
if !(diagonal > 0.0) {
return Ok(None);
}
let mut found: Vec<Vec2> = Vec::new();
'loops: for loop_record in &face.loops {
for coedge in &loop_record.coedges {
let [a, b] = coedge.pcurve.domain().or_refuse(KernelStage::Fragment, "domain")?;
if !(b > a) {
continue;
}
for fraction in [0.5, 0.25, 0.75] {
let t = a + (b - a) * fraction;
let derivatives = coedge.pcurve.derivatives(t, 1).or_refuse(KernelStage::Fragment, "derivatives")?;
let (at, tangent) = (derivatives[0], derivatives[1]);
let speed = (tangent.x * tangent.x + tangent.y * tangent.y).sqrt();
if !(speed > 0.0) || !speed.is_finite() {
continue;
}
let normal = Vec2 { x: -tangent.y / speed, y: tangent.x / speed };
for scale in [1e-2, 1e-3, 1e-4, 1e-5, 1e-6] {
let step = diagonal * scale;
for sign in [1.0, -1.0] {
let candidate = Vec2 { x: at.x + normal.x * step * sign, y: at.y + normal.y * step * sign };
if candidate.x < u[0] || candidate.x > u[1] || candidate.y < v[0] || candidate.y > v[1] {
continue;
}
if parameter_point_in_face(face, candidate, 1e-9).or_refuse(KernelStage::Fragment, "parameter_point_in_face")? == PolygonClass::Inside {
found.push(candidate);
if found.len() >= 3 {
break 'loops;
}
continue 'loops;
}
}
}
}
}
}
let Some(uv) = found.first().copied() else {
return Ok(None);
};
let mut extra_test_points = Vec::new();
for candidate in found.iter().skip(1) {
extra_test_points.push(face.surface.evaluate(candidate.x, candidate.y).or_refuse(KernelStage::Fragment, "evaluate")?);
}
Ok(Some((uv, extra_test_points)))
}
fn separated_fragment(face: &FaceRecord, operand: u8) -> Option<FaceFragmentRecord> {
let coedge = face.loops.first()?.coedges.first()?;
let [a, b] = coedge.pcurve.domain().ok()?;
let point = coedge.pcurve.evaluate(a + (b - a) * 0.5).ok()?;
let uv = Vec2 { x: point.x, y: point.y };
let test_point = face.surface.evaluate(uv.x, uv.y).ok()?;
Some(FaceFragmentRecord {
operand, source_face_id: face.id, surface: face.surface.clone(),
same_sense: face.same_sense,
loops: face.loops.iter().map(|lp| FragmentLoop {
coedges: lp.coedges.iter().map(|coedge| FragmentCoedge {
source: FragmentEdgeSource::Boundary { operand, edge_id: coedge.edge_id },
forward: coedge.forward, pcurve: coedge.pcurve.clone(),
}).collect(),
}).collect(),
test_point, test_uv: uv, extra_test_points: Vec::new(), order: None,
})
}
pub(crate) fn fragment_solid_against(
solid: &BrepSolid,
operand: u8,
imprint: &ImprintResultRecord,
other: Option<(crate::Aabb, f64)>,
) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
let mut profile = FragmentProfile::new();
if profile.enabled {
crate::classification::containment_profile_begin();
}
let result = fragment_solid_profiled(solid, operand, imprint, other, &mut profile);
profile.report(operand);
if profile.enabled {
crate::classification::containment_profile_report("fragment");
}
result
}
pub(super) fn fragment_solid_profiled(
solid: &BrepSolid,
operand: u8,
imprint: &ImprintResultRecord,
other: Option<(crate::Aabb, f64)>,
profile: &mut FragmentProfile,
) -> Result<Vec<FaceFragmentRecord>, KernelRefusal> {
let index = FragmentIndex::build(imprint);
let mut fragments = Vec::new();
for face in solid.shells.iter().flat_map(|shell| shell.faces.iter()) {
let far = other.is_some_and(|(bounds, band)| face_separated(face, bounds, band));
if !far { profile.box_candidates += 1; }
let has_pieces = index.by_face.get(&(operand, face.id)).is_some_and(|ids| !ids.is_empty());
let cosurface = index.cosurface_pairs.iter().any(|(key, _)| key.operand == operand && key.face_id == face.id);
if far && !has_pieces && !cosurface && std::env::var("BREP_FRAGMENT_SCOPE").as_deref() != Ok("0") {
if let Some(fragment) = separated_fragment(face, operand) {
profile.separated_faces += 1;
profile.face_done(face.id, None);
fragments.push(fragment);
continue;
}
}
let before = (profile.arranged_faces, profile.region_faces);
fragments.extend(fragment_face_indexed(solid, operand, face, &index, profile)?);
if far {
profile.far_arranged += profile.arranged_faces - before.0;
profile.far_regions += profile.region_faces - before.1;
}
}
Ok(fragments)
}
fn rim_bridge_enabled() -> bool {
crate::switch!("BREP_RIM_BRIDGE", true)
}