use crate::topology::BrepSolid;
use crate::{KernelRefusal, Vec3};
pub(crate) fn split_rim_merge_on() -> bool {
switch!("BREP_SPLIT_RIM_MERGE", true)
}
pub(crate) struct SplitRimMerge {
pub(crate) solid: BrepSolid,
pub(crate) points: Vec<Vec3>,
pub(crate) names: Option<Vec<String>>,
}
fn faces_of_edge(solid: &BrepSolid, edge_id: u64) -> Vec<u64> {
let mut faces = Vec::with_capacity(2);
for face in solid.shells.iter().flat_map(|shell| &shell.faces) {
let uses = face.loops.iter().flat_map(|loop_record| &loop_record.coedges).filter(|coedge| coedge.edge_id == edge_id).count();
for _ in 0..uses {
faces.push(face.id);
}
}
faces
}
fn closes_one_cycle(solid: &BrepSolid, edge_ids: &[u64]) -> bool {
let mut ends: Vec<(u64, u64)> = Vec::with_capacity(edge_ids.len());
for id in edge_ids {
let Some(edge) = solid.edges.iter().find(|edge| edge.id == *id) else { return false };
if edge.degenerate || edge.start_vertex_id == edge.end_vertex_id {
return false;
}
ends.push((edge.start_vertex_id, edge.end_vertex_id));
}
let mut uses: rustc_hash::FxHashMap<u64, usize> = Default::default();
for (a, b) in &ends {
*uses.entry(*a).or_default() += 1;
*uses.entry(*b).or_default() += 1;
}
if uses.values().any(|count| *count != 2) {
return false;
}
let mut visited = vec![false; ends.len()];
visited[0] = true;
let mut at = ends[0].1;
for _ in 1..ends.len() {
let Some(next) = (0..ends.len()).find(|index| !visited[*index] && (ends[*index].0 == at || ends[*index].1 == at)) else {
return false;
};
visited[next] = true;
at = if ends[next].0 == at { ends[next].1 } else { ends[next].0 };
}
at == ends[0].0 && visited.iter().all(|seen| *seen)
}
pub(crate) fn merge_split_rims(
solid: &BrepSolid,
edge_points: &[Vec3],
edge_names: Option<&[String]>,
resolve: &dyn Fn(&BrepSolid, Vec3) -> Result<u64, KernelRefusal>,
) -> Option<SplitRimMerge> {
if edge_points.len() < 2 {
return None;
}
let mut selected: Vec<u64> = Vec::with_capacity(edge_points.len());
for point in edge_points {
let edge_id = resolve(solid, *point).ok()?;
if !selected.contains(&edge_id) {
selected.push(edge_id);
}
}
if selected.len() < 2 || !closes_one_cycle(solid, &selected) {
return None;
}
let pairs: Vec<Vec<u64>> = selected.iter().map(|id| faces_of_edge(solid, *id)).collect();
if pairs.iter().any(|faces| faces.len() != 2 || faces[0] == faces[1]) {
return None;
}
let common = [pairs[0][0], pairs[0][1]].into_iter().find(|face| pairs.iter().all(|faces| faces.contains(face)))?;
let far: Vec<u64> = pairs.iter().map(|faces| if faces[0] == common { faces[1] } else { faces[0] }).collect();
if far.iter().enumerate().any(|(index, face)| far[..index].contains(face)) {
return None;
}
let tolerance = crate::KernelTolerances::for_solid(solid, crate::BooleanOptions::default().tolerance).model;
let (merged, carried_by) = crate::merge_faces_within(solid, tolerance, &far).ok()?;
let survivor = carried_by.first()?.1;
if carried_by.iter().any(|(_, carrier)| *carrier != survivor) {
return None;
}
let mut joined: Option<u64> = None;
for point in edge_points {
let edge_id = resolve(&merged, *point).ok()?;
if joined.is_some_and(|id| id != edge_id) {
return None;
}
joined = Some(edge_id);
}
let edge = merged.edges.iter().find(|edge| Some(edge.id) == joined)?;
if edge.degenerate || edge.start_vertex_id != edge.end_vertex_id {
return None;
}
if !merged.validate().is_empty() {
return None;
}
crate::blend::record_count("count.split_rim.merged", 1);
Some(SplitRimMerge {
solid: merged,
points: vec![edge_points[0]],
names: edge_names.and_then(|names| names.first()).map(|name| vec![name.clone()]),
})
}