use super::*;
pub(super) fn extend_offset_carriers_past_open_hole_rims(
carriers: &mut [Carrier],
source: &BrepSolid,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
smooth_pairs: &HashSet<(usize, usize)>,
scale: f64,
) -> Result<HashSet<usize>, String> {
let weld_band = 2e-3f64.max(scale * 5e-5);
let source_edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut extended = HashSet::default();
'carrier: for index in 0..carriers.len() {
if !matches!(carriers[index].kind, OffsetFaceRole::Offset) {
continue;
}
if smooth_pairs
.iter()
.any(|(first, second)| *first == index || *second == index)
{
continue;
}
let Some(source_face) = source_faces
.iter()
.find(|face| face.id == carriers[index].source_face_id)
else {
continue;
};
let mut local_uses = HashMap::<u64, usize>::default();
for coedge in source_face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
*local_uses.entry(coedge.edge_id).or_default() += 1;
}
let mut rims = Vec::new();
for (loop_index, loop_record) in source_face.loops.iter().enumerate() {
for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
let Some(edge) = source_edge_by_id.get(&coedge.edge_id) else {
continue 'carrier;
};
if edge.degenerate {
continue;
}
match local_uses[&coedge.edge_id] {
2 => {}
1 if edge.start_vertex_id == edge.end_vertex_id => {
rims.push((loop_index, coedge_index, (*edge).clone()));
}
_ => continue 'carrier,
}
}
}
if rims.len() != 2 {
continue;
}
let carrier_face = carriers[index].solid.shells[0].faces[0].clone();
let surface = carrier_face.surface.clone();
let [su0, su1] = surface.domain_u()?;
let [sv0, sv1] = surface.domain_v()?;
let v_mid = (sv0 + sv1) * 0.5;
if surface
.evaluate(su0, v_mid)?
.sub(surface.evaluate(su1, v_mid)?)
.length()
> weld_band
{
continue;
}
let mut rim_planes = Vec::new();
for (loop_index, coedge_index, rim_edge) in &rims {
let source_coedge = &source_face.loops[*loop_index].coedges[*coedge_index];
let [p0, p1] = source_coedge.pcurve.domain()?;
let mut u_low = f64::MAX;
let mut u_high = f64::MIN;
let mut v_mean = 0.0f64;
for sample in 0..=32 {
let uv = source_coedge
.pcurve
.evaluate(p0 + (p1 - p0) * sample as f64 / 32.0)?;
u_low = u_low.min(uv.x);
u_high = u_high.max(uv.x);
v_mean += uv.y / 33.0;
}
let u_span = su1 - su0;
if (u_low - su0).abs() > u_span * 1e-3 || (u_high - su1).abs() > u_span * 1e-3 {
continue 'carrier;
}
let Some(opening) = source_faces.iter().find(|face| {
opening_set.contains(&face.id)
&& face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.any(|coedge| coedge.edge_id == rim_edge.id)
}) else {
continue 'carrier;
};
let Some((plane_point, plane_normal)) =
planar_surface_frame(&opening.surface, weld_band)?
else {
continue 'carrier;
};
let mut rim_loop_area = None;
let mut largest_other = 0.0f64;
for loop_record in &opening.loops {
let area = parameter_space_area(&FaceRecord {
id: 0,
surface: opening.surface.clone(),
same_sense: true,
loops: vec![loop_record.clone()],
name: None,
})?
.abs();
if loop_record
.coedges
.iter()
.any(|coedge| coedge.edge_id == rim_edge.id)
{
rim_loop_area = Some(area);
} else {
largest_other = largest_other.max(area);
}
}
let Some(rim_loop_area) = rim_loop_area else {
continue 'carrier;
};
if rim_loop_area >= largest_other {
continue 'carrier;
}
let image_coedge = &carrier_face.loops[*loop_index].coedges[*coedge_index];
let Some(image_edge) = carriers[index]
.solid
.edges
.iter()
.find(|edge| edge.id == image_coedge.edge_id)
else {
continue 'carrier;
};
let mut off_plane = 0.0f64;
for sample in 0..=32 {
let point = image_edge.curve.evaluate(
image_edge.t0 + (image_edge.t1 - image_edge.t0) * sample as f64 / 32.0,
)?;
off_plane = off_plane.max(point.sub(plane_point).dot(plane_normal).abs());
}
if off_plane <= weld_band {
continue 'carrier;
}
rim_planes.push((v_mean, plane_point, plane_normal));
}
rim_planes.sort_by(|first, second| first.0.total_cmp(&second.0));
let interior = surface.evaluate((su0 + su1) * 0.5, v_mid)?;
for (v_end, (_, plane_point, plane_normal)) in [(sv0, rim_planes[0]), (sv1, rim_planes[1])]
{
let interior_sign = interior.sub(plane_point).dot(plane_normal).signum();
let iso = surface.iso_curve_v(v_end)?;
let [t0, t1] = iso.domain()?;
for sample in 0..=32 {
let point = iso.evaluate(t0 + (t1 - t0) * sample as f64 / 32.0)?;
if point.sub(plane_point).dot(plane_normal) * interior_sign > -weld_band {
continue 'carrier;
}
}
}
let winding = parameter_space_area(&carrier_face)?;
let bottom_rim = surface.iso_curve_v(sv0)?;
let top_rim = surface.iso_curve_v(sv1)?;
let seam = surface.iso_curve_u(su0)?;
let [bottom_t0, bottom_t1] = bottom_rim.domain()?;
let [top_t0, top_t1] = top_rim.domain()?;
let corner_bottom = surface.evaluate(su0, sv0)?;
let corner_top = surface.evaluate(su0, sv1)?;
let flat = |u: f64, v: f64| Vec3::new(u, v, 0.0);
let mut coedges = vec![
CoedgeRecord {
id: 1,
edge_id: 1,
forward: true,
pcurve: crate::make_line(flat(su0, sv0), flat(su1, sv0))?,
},
CoedgeRecord {
id: 2,
edge_id: 3,
forward: true,
pcurve: crate::make_line(flat(su1, sv0), flat(su1, sv1))?,
},
CoedgeRecord {
id: 3,
edge_id: 2,
forward: false,
pcurve: crate::make_line(flat(su1, sv1), flat(su0, sv1))?,
},
CoedgeRecord {
id: 4,
edge_id: 3,
forward: false,
pcurve: crate::make_line(flat(su0, sv1), flat(su0, sv0))?,
},
];
if winding < 0.0 {
coedges.reverse();
for coedge in &mut coedges {
coedge.forward = !coedge.forward;
coedge.pcurve = coedge.pcurve.reversed()?;
}
}
os_debug!(
"carrier[{index}] src={} extended past opening planes: band v=({sv0:.4},{sv1:.4})",
carriers[index].source_face_id,
);
carriers[index].solid = BrepSolid {
id: carriers[index].solid.id,
vertices: vec![
VertexRecord {
id: 1,
point: corner_bottom,
},
VertexRecord {
id: 2,
point: corner_top,
},
],
edges: vec![
EdgeRecord {
id: 1,
curve: bottom_rim,
t0: bottom_t0,
t1: bottom_t1,
start_vertex_id: 1,
end_vertex_id: 1,
degenerate: false,
name: None,
},
EdgeRecord {
id: 2,
curve: top_rim,
t0: top_t0,
t1: top_t1,
start_vertex_id: 2,
end_vertex_id: 2,
degenerate: false,
name: None,
},
EdgeRecord {
id: 3,
curve: seam,
t0: sv0,
t1: sv1,
start_vertex_id: 1,
end_vertex_id: 2,
degenerate: false,
name: None,
},
],
shells: vec![ShellRecord {
id: 1,
faces: vec![FaceRecord {
id: carrier_face.id,
surface,
same_sense: carrier_face.same_sense,
loops: vec![LoopRecord { id: 1, coedges }],
name: carrier_face.name.clone(),
}],
}],
genus: 0,
};
extended.insert(index);
}
Ok(extended)
}
pub(super) fn extend_fallshort_curved_carriers(
carriers: &mut [Carrier],
source: &BrepSolid,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
smooth_pairs: &HashSet<(usize, usize)>,
already_extended: &HashSet<usize>,
scale: f64,
distance: f64,
) -> Result<usize, String> {
let weld_band = 2e-3f64.max(scale * 5e-5);
let source_edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut extended = 0usize;
'carrier: for index in 0..carriers.len() {
if !matches!(carriers[index].kind, OffsetFaceRole::Offset)
|| already_extended.contains(&index)
{
continue;
}
if smooth_pairs
.iter()
.any(|(first, second)| *first == index || *second == index)
{
continue;
}
let Some(source_face) = source_faces
.iter()
.find(|face| face.id == carriers[index].source_face_id)
else {
continue;
};
if source_face.surface.is_affine()? {
continue;
}
let carrier_face = carriers[index].solid.shells[0].faces[0].clone();
let surface = carrier_face.surface.clone();
let [su0, su1] = surface.domain_u()?;
let [sv0, sv1] = surface.domain_v()?;
let v_mid = (sv0 + sv1) * 0.5;
if surface
.evaluate(su0, v_mid)?
.sub(surface.evaluate(su1, v_mid)?)
.length()
> weld_band
{
continue;
}
let mut qualifies = false;
'rims: for (loop_index, loop_record) in source_face.loops.iter().enumerate() {
for (coedge_index, coedge) in loop_record.coedges.iter().enumerate() {
let Some(edge) = source_edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some(opening) = source_faces.iter().find(|face| {
opening_set.contains(&face.id)
&& face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.any(|other| other.edge_id == coedge.edge_id)
}) else {
continue;
};
let Some((plane_point, mut plane_normal)) =
planar_surface_frame(&opening.surface, weld_band)?
else {
continue;
};
let Some(outward) = opening
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.find_map(|opening_coedge| {
let [p0, p1] = opening_coedge.pcurve.domain().ok()?;
let opening_uv =
opening_coedge.pcurve.evaluate((p0 + p1) * 0.5).ok()?;
face_normal(opening, opening_uv.x, opening_uv.y).ok()
})
else {
continue;
};
if plane_normal.dot(outward) < 0.0 {
plane_normal = plane_normal.scale(-1.0);
}
if edge.start_vertex_id == edge.end_vertex_id {
let mut rim_loop_area = None;
let mut largest_other = 0.0f64;
for loop_record in &opening.loops {
let area = parameter_space_area(&FaceRecord {
id: 0,
surface: opening.surface.clone(),
same_sense: true,
loops: vec![loop_record.clone()],
name: None,
})?
.abs();
if loop_record
.coedges
.iter()
.any(|other| other.edge_id == coedge.edge_id)
{
rim_loop_area = Some(area);
} else {
largest_other = largest_other.max(area);
}
}
let Some(rim_loop_area) = rim_loop_area else {
continue;
};
if rim_loop_area >= largest_other {
continue;
}
}
let Some(image_coedge) = carrier_face
.loops
.get(loop_index)
.and_then(|loop_record| loop_record.coedges.get(coedge_index))
else {
continue 'carrier;
};
let Some(image_edge) = carriers[index]
.solid
.edges
.iter()
.find(|edge| edge.id == image_coedge.edge_id)
else {
continue 'carrier;
};
let mut falls_short = true;
for sample in 0..=16 {
let point = image_edge.curve.evaluate(
image_edge.t0 + (image_edge.t1 - image_edge.t0) * sample as f64 / 16.0,
)?;
if point.sub(plane_point).dot(plane_normal) > -weld_band {
falls_short = false;
break;
}
}
if !falls_short {
continue;
}
let mut crosses = false;
'grid: for iu in 0..=16 {
for iv in 0..=16 {
let point = surface.evaluate(
su0 + (su1 - su0) * iu as f64 / 16.0,
sv0 + (sv1 - sv0) * iv as f64 / 16.0,
)?;
if point.sub(plane_point).dot(plane_normal) > weld_band {
crosses = true;
break 'grid;
}
}
}
if crosses {
qualifies = true;
break 'rims;
}
}
}
if !qualifies {
continue;
}
if rebuild_carrier_full_domain(
&mut carriers[index],
source_face,
weld_band,
distance,
index,
"a fall-short opening rim",
)? {
extended += 1;
}
}
Ok(extended)
}
fn rebuild_carrier_full_domain(
carrier: &mut Carrier,
source_face: &FaceRecord,
weld_band: f64,
distance: f64,
index: usize,
reason: &str,
) -> Result<bool, String> {
let carrier_face = carrier.solid.shells[0].faces[0].clone();
let mut surface = carrier_face.surface.clone();
let [mut su0, mut su1] = surface.domain_u()?;
let [mut sv0, mut sv1] = surface.domain_v()?;
let v_mid = (sv0 + sv1) * 0.5;
if surface
.evaluate(su0, v_mid)?
.sub(surface.evaluate(su1, v_mid)?)
.length()
> weld_band
{
return Ok(false);
}
if let Some(crate::AnalyticSurface::Sphere { frame, radius }) =
crate::analytic_surface::recognize(&source_face.surface)
{
let mid = surface.evaluate((su0 + su1) * 0.5, (sv0 + sv1) * 0.5)?;
let measured = mid.sub(frame.origin).length();
let grown = radius + distance.abs();
let shrunk = (radius - distance.abs()).abs();
let target = if (measured - grown).abs() <= (measured - shrunk).abs() {
grown
} else {
shrunk
};
if (measured - target).abs() <= weld_band.max(distance.abs() * 0.5) && target > weld_band {
let full = crate::make_sphere_surface(frame.origin, target, frame.axis)?;
let [fu0, fu1] = full.domain_u()?;
let [fv0, fv1] = full.domain_v()?;
let old_normal = surface.normal((su0 + su1) * 0.5, (sv0 + sv1) * 0.5)?;
let radial = mid.sub(frame.origin).normalized()?;
let sample = full.evaluate((fu0 + fu1) * 0.5, (fv0 + fv1) * 0.5)?;
let new_normal = full.normal((fu0 + fu1) * 0.5, (fv0 + fv1) * 0.5)?;
let new_radial = sample.sub(frame.origin).normalized()?;
if (old_normal.dot(radial) > 0.0) == (new_normal.dot(new_radial) > 0.0) {
os_debug!("carrier[{index}] rebuilt on the exact full offset sphere r={target:.6}");
surface = full;
(su0, su1, sv0, sv1) = (fu0, fu1, fv0, fv1);
} else {
os_debug!("carrier[{index}] sphere rebuild skipped: orientation mismatch");
}
} else {
os_debug!(
"carrier[{index}] sphere rebuild skipped: measured radius {measured:.6} \
matches neither grown nor shrunk offset of {radius:.6}"
);
}
}
let winding = parameter_space_area(&carrier_face)?;
let pole_point = |iso: &crate::NurbsCurve| -> Result<Option<Vec3>, String> {
let [t0, t1] = iso.domain()?;
let anchor = iso.evaluate(t0)?;
let mut deviation = 0.0f64;
for sample in 1..=8 {
let point = iso.evaluate(t0 + (t1 - t0) * sample as f64 / 8.0)?;
deviation = deviation.max(point.sub(anchor).length());
}
os_debug!(" pole probe: max deviation {deviation:.6}");
Ok((deviation <= weld_band).then_some(anchor))
};
let bottom_iso = surface.iso_curve_v(sv0)?;
let top_iso = surface.iso_curve_v(sv1)?;
let bottom_pole = pole_point(&bottom_iso)?;
let top_pole = pole_point(&top_iso)?;
let corner_bottom = surface.evaluate(su0, sv0)?;
let corner_top = surface.evaluate(su0, sv1)?;
let seam = surface.iso_curve_u(su0)?;
let v_end_edge = |id: u64,
iso: crate::NurbsCurve,
pole: Option<Vec3>,
vertex: u64|
-> Result<EdgeRecord, String> {
Ok(match pole {
Some(point) => EdgeRecord {
id,
curve: crate::make_line(point, point)?,
t0: 0.0,
t1: 1.0,
start_vertex_id: vertex,
end_vertex_id: vertex,
degenerate: true,
name: None,
},
None => {
let [t0, t1] = iso.domain()?;
EdgeRecord {
id,
curve: iso,
t0,
t1,
start_vertex_id: vertex,
end_vertex_id: vertex,
degenerate: false,
name: None,
}
}
})
};
let flat = |u: f64, v: f64| Vec3::new(u, v, 0.0);
let mut coedges = vec![
CoedgeRecord {
id: 1,
edge_id: 1,
forward: true,
pcurve: crate::make_line(flat(su0, sv0), flat(su1, sv0))?,
},
CoedgeRecord {
id: 2,
edge_id: 3,
forward: true,
pcurve: crate::make_line(flat(su1, sv0), flat(su1, sv1))?,
},
CoedgeRecord {
id: 3,
edge_id: 2,
forward: false,
pcurve: crate::make_line(flat(su1, sv1), flat(su0, sv1))?,
},
CoedgeRecord {
id: 4,
edge_id: 3,
forward: false,
pcurve: crate::make_line(flat(su0, sv1), flat(su0, sv0))?,
},
];
if winding < 0.0 {
coedges.reverse();
for coedge in &mut coedges {
coedge.forward = !coedge.forward;
coedge.pcurve = coedge.pcurve.reversed()?;
}
}
os_debug!(
"carrier[{index}] src={} extended to full domain past {reason} \
(poles: bottom={} top={})",
carrier.source_face_id,
bottom_pole.is_some(),
top_pole.is_some(),
);
carrier.solid = BrepSolid {
id: carrier.solid.id,
vertices: vec![
VertexRecord {
id: 1,
point: corner_bottom,
},
VertexRecord {
id: 2,
point: corner_top,
},
],
edges: vec![
v_end_edge(1, bottom_iso, bottom_pole, 1)?,
v_end_edge(2, top_iso, top_pole, 2)?,
EdgeRecord {
id: 3,
curve: seam,
t0: sv0,
t1: sv1,
start_vertex_id: 1,
end_vertex_id: 2,
degenerate: false,
name: None,
},
],
shells: vec![ShellRecord {
id: 1,
faces: vec![FaceRecord {
id: carrier_face.id,
surface,
same_sense: carrier_face.same_sense,
loops: vec![LoopRecord { id: 1, coedges }],
name: carrier_face.name.clone(),
}],
}],
genus: 0,
};
Ok(true)
}
pub(super) fn rebuild_reflex_rim_carriers(
carriers: &mut [Carrier],
source: &BrepSolid,
source_faces: &[&FaceRecord],
smooth_pairs: &HashSet<(usize, usize)>,
scale: f64,
distance: f64,
) -> Result<HashSet<usize>, String> {
let weld_band = 2e-3f64.max(scale * 5e-5);
let mut rebuilt = HashSet::default();
for index in 0..carriers.len() {
if !matches!(carriers[index].kind, OffsetFaceRole::Offset) {
continue;
}
if smooth_pairs
.iter()
.any(|(first, second)| *first == index || *second == index)
{
continue;
}
let Some(source_face) = source_faces
.iter()
.find(|face| face.id == carriers[index].source_face_id)
else {
continue;
};
if source_face.surface.is_affine()? {
continue;
}
if face_reflex_miter_tan(source, source_face)?.is_none() {
continue;
}
if rebuild_carrier_full_domain(
&mut carriers[index],
source_face,
weld_band,
distance,
index,
"a reflex junction rim",
)? {
rebuilt.insert(index);
}
}
Ok(rebuilt)
}
pub(super) fn face_reflex_miter_tan(source: &BrepSolid, face: &FaceRecord) -> Result<Option<f64>, String> {
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let faces = source
.shells
.iter()
.flat_map(|shell| &shell.faces)
.collect::<Vec<_>>();
let mut worst: Option<f64> = None;
for loop_record in &face.loops {
for coedge in &loop_record.coedges {
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((mate, mate_coedge)) = faces.iter().find_map(|other| {
if other.id == face.id {
return None;
}
other
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.find(|other_coedge| other_coedge.edge_id == coedge.edge_id)
.map(|other_coedge| (*other, other_coedge))
}) else {
continue;
};
for fraction in [0.1f64, 0.3, 0.5, 0.7, 0.9] {
let t_sample = edge.t0 + (edge.t1 - edge.t0) * fraction;
let step = ((edge.t1 - edge.t0) * 1e-3).max(1e-9);
let before = edge.curve.evaluate(t_sample - step)?;
let after = edge.curve.evaluate(t_sample + step)?;
let mut tangent = after.sub(before);
if tangent.length() <= 1e-12 {
continue;
}
if !coedge.forward {
tangent = tangent.scale(-1.0);
}
let this_fraction = if coedge.forward { fraction } else { 1.0 - fraction };
let mate_fraction = if mate_coedge.forward { fraction } else { 1.0 - fraction };
let uv_this = {
let [a, b] = coedge.pcurve.domain()?;
coedge.pcurve.evaluate(a + (b - a) * this_fraction)?
};
let uv_mate = {
let [a, b] = mate_coedge.pcurve.domain()?;
mate_coedge.pcurve.evaluate(a + (b - a) * mate_fraction)?
};
let n_this = face_normal(face, uv_this.x, uv_this.y)?;
let n_mate = face_normal(mate, uv_mate.x, uv_mate.y)?;
let cross = n_this.cross(n_mate);
if cross.length() <= 1e-6 {
continue;
}
if cross.dot(tangent) < 0.0 {
let cos_normals = n_this.dot(n_mate).clamp(-1.0, 1.0);
let half = cos_normals.acos() * 0.5;
worst = Some(worst.unwrap_or(0.0).max(half.tan()));
}
}
}
}
Ok(worst)
}
pub(super) fn source_by_id_lookup<'a>(
source_faces: &[&'a FaceRecord],
face_id: u64,
) -> Option<&'a FaceRecord> {
source_faces.iter().find(|face| face.id == face_id).copied()
}
pub(super) fn sample_separation(first: &[Vec3], second: &[Vec3]) -> f64 {
first
.iter()
.flat_map(|a| second.iter().map(move |b| a.sub(*b).length()))
.fold(f64::INFINITY, f64::min)
}
pub(super) fn opening_wall_seeds(
opening: &FaceRecord,
carrier: &FaceRecord,
source: &BrepSolid,
opening_set: &HashSet<u64>,
distance: f64,
) -> Result<Vec<Vec2>, String> {
let source_faces = source
.shells
.iter()
.flat_map(|shell| &shell.faces)
.collect::<Vec<_>>();
let mut seeds = Vec::new();
for opening_use in opening
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
let Some((neighbor, neighbor_use)) = source_faces.iter().find_map(|face| {
if face.id == opening.id || opening_set.contains(&face.id) {
return None;
}
face.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.find(|coedge| coedge.edge_id == opening_use.edge_id)
.map(|coedge| (*face, coedge))
}) else {
continue;
};
let [opening_start, opening_end] = opening_use.pcurve.domain()?;
let opening_uv = opening_use
.pcurve
.evaluate((opening_start + opening_end) * 0.5)?;
let original = opening.surface.evaluate(opening_uv.x, opening_uv.y)?;
let [neighbor_start, neighbor_end] = neighbor_use.pcurve.domain()?;
let neighbor_uv = neighbor_use
.pcurve
.evaluate((neighbor_start + neighbor_end) * 0.5)?;
let expected =
original.add(face_normal(neighbor, neighbor_uv.x, neighbor_uv.y)?.scale(-distance));
let halfway = original.add(expected.sub(original).scale(0.5));
let projection = project_point_to_surface(&carrier.surface, halfway)?;
seeds.push(Vec2 {
x: projection.u,
y: projection.v,
});
}
Ok(seeds)
}