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) struct JunctionStation {
pub normal: Vec3,
pub mate_normal: Vec3,
pub tangent: Vec3,
}
impl JunctionStation {
pub fn is_convex(&self) -> bool {
self.normal.cross(self.mate_normal).dot(self.tangent) > 0.0
}
pub fn miter_tan(&self) -> f64 {
let cos_normals = self.normal.dot(self.mate_normal).clamp(-1.0, 1.0);
(cos_normals.acos() * 0.5).tan()
}
}
pub(super) fn junction_stations(
face: &FaceRecord,
coedge: &CoedgeRecord,
edge: &EdgeRecord,
mate: &FaceRecord,
) -> Result<Vec<JunctionStation>, String> {
let mut stations = Vec::new();
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 station = edge.curve.evaluate(t_sample)?;
let uv_this = {
let projection = project_point_to_surface(&face.surface, station)?;
Vec2 {
x: projection.u,
y: projection.v,
}
};
let uv_mate = {
let projection = project_point_to_surface(&mate.surface, station)?;
Vec2 {
x: projection.u,
y: projection.v,
}
};
let normal = face_normal(face, uv_this.x, uv_this.y)?;
let mate_normal = face_normal(mate, uv_mate.x, uv_mate.y)?;
let cross = normal.cross(mate_normal);
if cross.length() <= 1e-6 || normal.dot(mate_normal) >= SMOOTH_JUNCTION_COS {
continue;
}
stations.push(JunctionStation {
normal,
mate_normal,
tangent,
});
}
Ok(stations)
}
pub(super) fn junction_mate<'a>(
faces: &[&'a FaceRecord],
face: &FaceRecord,
edge_id: u64,
) -> Option<(&'a FaceRecord, &'a CoedgeRecord)> {
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 == edge_id)
.map(|other_coedge| (*other, other_coedge))
})
}
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, _)) = junction_mate(&faces, face, coedge.edge_id) else {
continue;
};
for station in junction_stations(face, coedge, edge, mate)? {
if !station.is_convex() {
worst = Some(worst.unwrap_or(0.0).max(station.miter_tan()));
}
}
}
}
Ok(worst)
}
pub(super) fn outward_miter_scale(worst_tan: Option<f64>) -> f64 {
match worst_tan {
Some(tan) if tan > 1.0 + 1e-6 => (tan * 1.5).min(12.0),
_ => 1.0,
}
}
pub(super) fn outward_carrier_extension(
source: &BrepSolid,
face: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
amount: f64,
) -> Result<crate::CarrierExtension, String> {
let [u0, u1] = face.surface.domain_u()?;
let [v0, v1] = face.surface.domain_v()?;
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut uses: Vec<(Vec<Vec2>, bool, Option<f64>)> = Vec::new();
let mut low = Vec2 {
x: f64::INFINITY,
y: f64::INFINITY,
};
let mut high = Vec2 {
x: f64::NEG_INFINITY,
y: f64::NEG_INFINITY,
};
for coedge in face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
if edge_by_id
.get(&coedge.edge_id)
.is_some_and(|edge| edge.degenerate)
{
continue;
}
let [p0, p1] = coedge.pcurve.domain()?;
let samples = (0..=24)
.map(|sample| {
coedge
.pcurve
.evaluate(p0 + (p1 - p0) * sample as f64 / 24.0)
.map(|uv| Vec2 { x: uv.x, y: uv.y })
})
.collect::<Result<Vec<_>, String>>()?;
for uv in &samples {
low.x = low.x.min(uv.x);
low.y = low.y.min(uv.y);
high.x = high.x.max(uv.x);
high.y = high.y.max(uv.y);
}
let mate = junction_mate(source_faces, face, coedge.edge_id)
.filter(|(mate, _)| !opening_set.contains(&mate.id));
let (smooth, convex_tan) = match mate {
Some((mate, mate_use)) => {
let smooth = uses_are_tangent(face, coedge, mate, mate_use)?;
let convex_tan = match edge_by_id.get(&coedge.edge_id) {
Some(edge) if !smooth => junction_stations(face, coedge, edge, mate)?
.iter()
.filter(|station| station.is_convex())
.map(JunctionStation::miter_tan)
.fold(None, |worst: Option<f64>, tan| {
Some(worst.unwrap_or(0.0).max(tan))
}),
_ => None,
};
(smooth, convex_tan)
}
None => (false, None),
};
uses.push((samples, smooth, convex_tan));
}
if uses.is_empty() || !low.x.is_finite() || !high.x.is_finite() {
return Ok(crate::CarrierExtension::uniform(amount));
}
let worst_of = |first: Option<f64>, tan: Option<f64>| -> Option<f64> {
match (first, tan) {
(Some(a), Some(b)) => Some(a.max(b)),
(a, b) => a.or(b),
}
};
let face_worst = uses.iter().fold(None, |worst, (_, _, convex_tan)| {
worst_of(worst, *convex_tan)
});
let side = |axis: fn(&Vec2) -> f64, value: f64, span: f64| -> f64 {
let band = (span.abs() * 1e-5).max(1e-9);
let mut along = 0usize;
let mut smooth_along = 0usize;
let mut worst_along: Option<f64> = None;
for (samples, smooth, convex_tan) in &uses {
let on = samples
.iter()
.filter(|uv| (axis(uv) - value).abs() <= band)
.count();
if on * 10 >= samples.len() * 9 {
along += 1;
if *smooth {
smooth_along += 1;
}
worst_along = worst_of(worst_along, *convex_tan);
}
}
if along > 0 && along == smooth_along {
return 0.0;
}
let worst = if along > 0 { worst_along } else { face_worst };
amount * outward_miter_scale(worst)
};
Ok(crate::CarrierExtension {
u_min: side(|uv| uv.x, low.x, u1 - u0),
u_max: side(|uv| uv.x, high.x, u1 - u0),
v_min: side(|uv| uv.y, low.y, v1 - v0),
v_max: side(|uv| uv.y, high.y, v1 - v0),
})
}
pub(super) fn outward_wall_pad(
source: &BrepSolid,
opening: &FaceRecord,
source_faces: &[&FaceRecord],
opening_set: &HashSet<u64>,
amount: f64,
) -> Result<f64, String> {
let edge_by_id = source
.edges
.iter()
.map(|edge| (edge.id, edge))
.collect::<HashMap<_, _>>();
let mut worst: Option<f64> = None;
for coedge in opening
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
{
let Some(edge) = edge_by_id.get(&coedge.edge_id) else {
continue;
};
if edge.degenerate {
continue;
}
let Some((mate, _)) = junction_mate(source_faces, opening, coedge.edge_id)
.filter(|(mate, _)| !opening_set.contains(&mate.id))
else {
continue;
};
for station in junction_stations(opening, coedge, edge, mate)? {
if !station.is_convex() {
continue;
}
let sin_dihedral = station.normal.cross(station.mate_normal).length();
if sin_dihedral <= 1e-6 {
continue;
}
worst = Some(worst.unwrap_or(0.0).max(1.0 / sin_dihedral));
}
}
Ok(amount * outward_miter_scale(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_offsets(neighbor)
.normal(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)
}