use crate::topology::{BrepSolid, CoedgeRecord, EdgeRecord, FaceRecord, LoopRecord};
use crate::{KernelRefusal, Vec3};
pub(crate) fn sheet_free_end_on() -> bool {
switch!("BREP_SHEET_FREE_END", true)
}
const TEMPORARY_FACE: &str = "__sheet_free_end_";
fn uses_of(solid: &BrepSolid, edge_id: u64) -> usize {
solid
.shells
.iter()
.flat_map(|shell| &shell.faces)
.flat_map(|face| &face.loops)
.flat_map(|loop_record| &loop_record.coedges)
.filter(|coedge| coedge.edge_id == edge_id)
.count()
}
fn coedges_of(solid: &BrepSolid, edge_id: u64) -> Vec<(&FaceRecord, &CoedgeRecord)> {
let mut found = Vec::new();
for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
for coedge in face.loops.iter().flat_map(|loop_record| &loop_record.coedges) {
if coedge.edge_id == edge_id {
found.push((face, coedge));
}
}
}
found
}
fn vertex_point(solid: &BrepSolid, id: u64) -> Option<Vec3> {
solid.vertices.iter().find(|vertex| vertex.id == id).map(|vertex| vertex.point)
}
struct FreeEnd {
vertex: u64,
boundaries: [u64; 2],
}
fn free_end_at(solid: &BrepSolid, edge_id: u64, vertex: u64, selected: &[u64]) -> Option<FreeEnd> {
let mates = coedges_of(solid, edge_id);
if mates.len() != 2 || mates[0].0.id == mates[1].0.id {
return None;
}
let on_other = selected.iter().filter(|id| **id != edge_id).any(|id| {
solid.edges.iter().find(|edge| edge.id == *id).is_some_and(|edge| edge.start_vertex_id == vertex || edge.end_vertex_id == vertex)
});
if on_other {
return None;
}
let mut boundaries = [0u64; 2];
for (slot, (face, _)) in mates.iter().enumerate() {
let mut at_vertex = face
.loops
.iter()
.flat_map(|loop_record| &loop_record.coedges)
.filter(|coedge| coedge.edge_id != edge_id)
.filter_map(|coedge| solid.edges.iter().find(|edge| edge.id == coedge.edge_id))
.filter(|edge| !edge.degenerate && (edge.start_vertex_id == vertex || edge.end_vertex_id == vertex));
let boundary = at_vertex.next()?;
if at_vertex.next().is_some() || uses_of(solid, boundary.id) != 1 || boundary.start_vertex_id == boundary.end_vertex_id {
return None;
}
boundary.curve.straight_segment(1e-9)?;
boundaries[slot] = boundary.id;
}
if boundaries[0] == boundaries[1] {
return None;
}
Some(FreeEnd { vertex, boundaries })
}
fn far_vertex(edge: &EdgeRecord, vertex: u64) -> u64 {
if edge.start_vertex_id == vertex {
edge.end_vertex_id
} else {
edge.start_vertex_id
}
}
pub(crate) fn close_free_ends(solid: &BrepSolid, edge_ids: &[u64]) -> Option<BrepSolid> {
if solid.kind != crate::BodyKind::Sheet {
return None;
}
let mut ends: Vec<FreeEnd> = Vec::new();
for edge_id in edge_ids {
let edge = solid.edges.iter().find(|edge| edge.id == *edge_id)?;
if edge.start_vertex_id == edge.end_vertex_id {
continue;
}
for vertex in [edge.start_vertex_id, edge.end_vertex_id] {
if let Some(end) = free_end_at(solid, *edge_id, vertex, edge_ids) {
ends.push(end);
}
}
}
if ends.is_empty() {
return None;
}
let mut claimed: Vec<u64> = ends.iter().flat_map(|end| end.boundaries).collect();
claimed.sort_unstable();
if claimed.windows(2).any(|pair| pair[0] == pair[1]) {
return None;
}
let mut next_id = max_id(solid) + 1;
let mut take_id = || {
let id = next_id;
next_id += 1;
id
};
let mut body = solid.clone();
for (index, end) in ends.iter().enumerate() {
let first = solid.edges.iter().find(|edge| edge.id == end.boundaries[0])?;
let second = solid.edges.iter().find(|edge| edge.id == end.boundaries[1])?;
let first_far = far_vertex(first, end.vertex);
let second_far = far_vertex(second, end.vertex);
if first_far == second_far {
return None;
}
let corner = vertex_point(solid, end.vertex)?;
let a = vertex_point(solid, first_far)?;
let b = vertex_point(solid, second_far)?;
let (_, first_use) = coedges_of(solid, first.id).into_iter().next()?;
let (_, second_use) = coedges_of(solid, second.id).into_iter().next()?;
let first_reverse_forward = !first_use.forward;
let first_reverse_arrives = (if first_reverse_forward { first.end_vertex_id } else { first.start_vertex_id }) == end.vertex;
let second_reverse_forward = !second_use.forward;
let second_reverse_leaves = (if second_reverse_forward { second.start_vertex_id } else { second.end_vertex_id }) == end.vertex;
let (lead, lead_forward, trail, trail_forward, from, to) = if first_reverse_arrives && second_reverse_leaves {
(first, first_reverse_forward, second, second_reverse_forward, b, a)
} else if !first_reverse_arrives && !second_reverse_leaves {
(second, second_reverse_forward, first, first_reverse_forward, a, b)
} else {
return None;
};
let chord = crate::make_line(from, to).ok()?;
let polygon = [lead_start(lead, lead_forward, solid)?, corner, from];
let normal = newell(&polygon);
let length = normal.length();
if !(length > 1e-9 * a.sub(corner).length() * b.sub(corner).length()) {
return None;
}
let normal = normal.scale(1.0 / length);
let x_axis = a.sub(corner).normalized().ok()?;
let y_axis = normal.cross(x_axis).normalized().ok()?;
let points = [corner, a, b];
let (mut min_x, mut max_x, mut min_y, mut max_y) = (f64::INFINITY, f64::NEG_INFINITY, f64::INFINITY, f64::NEG_INFINITY);
for point in points {
let delta = point.sub(corner);
min_x = min_x.min(delta.dot(x_axis));
max_x = max_x.max(delta.dot(x_axis));
min_y = min_y.min(delta.dot(y_axis));
max_y = max_y.max(delta.dot(y_axis));
}
let padding = (max_x - min_x).max(max_y - min_y) * 0.05 + 1e-6;
let origin = corner.add(x_axis.scale(min_x - padding)).add(y_axis.scale(min_y - padding));
let surface = crate::make_plane(origin, x_axis, y_axis, max_x - min_x + 2.0 * padding, max_y - min_y + 2.0 * padding).ok()?;
let plane_pcurve = |curve: &crate::NurbsCurve, forward: bool| -> Option<crate::NurbsCurve> {
let pcurve = crate::curve::curve_to_plane_parameters(curve, origin, x_axis, y_axis).ok()?;
if forward {
Some(pcurve)
} else {
pcurve.reversed().ok()
}
};
let chord_domain = chord.domain().ok()?;
let chord_id = take_id();
let (from_vertex, to_vertex) = if std::ptr::eq(lead, first) { (second_far, first_far) } else { (first_far, second_far) };
let coedges = vec![
CoedgeRecord { id: take_id(), edge_id: lead.id, forward: lead_forward, pcurve: plane_pcurve(&lead.curve, lead_forward)? },
CoedgeRecord { id: take_id(), edge_id: trail.id, forward: trail_forward, pcurve: plane_pcurve(&trail.curve, trail_forward)? },
CoedgeRecord { id: take_id(), edge_id: chord_id, forward: true, pcurve: plane_pcurve(&chord, true)? },
];
body.edges.push(EdgeRecord {
id: chord_id,
curve: chord,
t0: chord_domain[0],
t1: chord_domain[1],
start_vertex_id: from_vertex,
end_vertex_id: to_vertex,
degenerate: false,
name: None,
});
let loop_id = take_id();
let face_id = take_id();
body.shells.first_mut()?.faces.push(FaceRecord {
id: face_id,
surface,
same_sense: true,
loops: vec![LoopRecord { id: loop_id, coedges }],
name: Some(format!("{TEMPORARY_FACE}{index}")),
});
}
body.id = take_id();
Some(body)
}
fn lead_start(edge: &EdgeRecord, forward: bool, solid: &BrepSolid) -> Option<Vec3> {
vertex_point(solid, if forward { edge.start_vertex_id } else { edge.end_vertex_id })
}
fn newell(points: &[Vec3]) -> Vec3 {
let mut normal = Vec3::new(0.0, 0.0, 0.0);
for (index, current) in points.iter().enumerate() {
let next = points[(index + 1) % points.len()];
normal = normal.add(Vec3::new(
(current.y - next.y) * (current.z + next.z),
(current.z - next.z) * (current.x + next.x),
(current.x - next.x) * (current.y + next.y),
));
}
normal
}
fn max_id(solid: &BrepSolid) -> u64 {
let mut largest = solid.id;
for vertex in &solid.vertices {
largest = largest.max(vertex.id);
}
for edge in &solid.edges {
largest = largest.max(edge.id);
}
for shell in &solid.shells {
largest = largest.max(shell.id);
for face in &shell.faces {
largest = largest.max(face.id);
for loop_record in &face.loops {
largest = largest.max(loop_record.id);
for coedge in &loop_record.coedges {
largest = largest.max(coedge.id);
}
}
}
}
largest
}
pub(crate) fn open_free_ends(mut body: BrepSolid, count: usize) -> Option<BrepSolid> {
let mut removed = 0usize;
for shell in &mut body.shells {
shell.faces.retain(|face| {
let temporary = face.name.as_deref().is_some_and(|name| name.starts_with(TEMPORARY_FACE));
removed += temporary as usize;
!temporary
});
}
if removed != count {
return None;
}
let used: rustc_hash::FxHashSet<u64> = body
.shells
.iter()
.flat_map(|shell| &shell.faces)
.flat_map(|face| &face.loops)
.flat_map(|loop_record| &loop_record.coedges)
.map(|coedge| coedge.edge_id)
.collect();
body.edges.retain(|edge| used.contains(&edge.id));
let vertices: rustc_hash::FxHashSet<u64> = body.edges.iter().flat_map(|edge| [edge.start_vertex_id, edge.end_vertex_id]).collect();
body.vertices.retain(|vertex| vertices.contains(&vertex.id));
body.kind = crate::BodyKind::Sheet;
body.mass_properties_cache = Default::default();
body.validate().is_empty().then_some(body)
}
pub(crate) fn temporary_faces(body: &BrepSolid) -> usize {
body.shells
.iter()
.flat_map(|shell| &shell.faces)
.filter(|face| face.name.as_deref().is_some_and(|name| name.starts_with(TEMPORARY_FACE)))
.count()
}