use crate::ids::{FaceId, HalfEdgeId, VertexId};
use crate::predicates::is_triangle_degenerate_3d;
use crate::storage::{HalfEdge, MeshStorage};
use crate::topology_ops::{TopologyError, add_triangle};
use crate::traversal::{BoundaryLoop, FaceHalfEdges, boundary_loops};
use crate::triangulation::ear_clipping_3d;
pub fn remove_face(mesh: &mut MeshStorage, face: FaceId) -> Result<(), TopologyError> {
let he_ids: Vec<HalfEdgeId> = FaceHalfEdges::new(mesh, face).collect();
if he_ids.is_empty() {
mesh.remove_face(face);
return Ok(());
}
let he_data: Vec<_> = he_ids
.iter()
.map(|&he| mesh.get_halfedge(he).cloned())
.collect();
for he_opt in &he_data {
let Some(h) = he_opt else { continue };
if let Some(twid) = h.twin
&& let Some(t) = mesh.get_halfedge_mut(twid)
{
t.twin = None;
}
}
mesh.remove_face(face);
for he in he_ids {
mesh.remove_halfedge(he);
}
for he_opt in &he_data {
let Some(h) = he_opt else { continue };
let twin_id = h.twin;
let Some(twid) = twin_id else { continue };
if !mesh.contains_halfedge(twid) {
continue;
}
let twin_data = match mesh.get_halfedge(twid) {
Some(td) => td,
None => continue,
};
if twin_data.face.is_none() {
mesh.remove_halfedge(twid);
continue;
}
let dst = h.vertex;
let bhe = mesh.add_halfedge(HalfEdge::new(dst));
if let Some(t) = mesh.get_halfedge_mut(twid) {
t.twin = Some(bhe);
}
if let Some(b) = mesh.get_halfedge_mut(bhe) {
b.twin = Some(twid);
}
}
fix_all_vertex_entries(mesh);
Ok(())
}
fn fix_all_vertex_entries(mesh: &mut MeshStorage) {
for v in mesh.vertex_ids().collect::<Vec<_>>() {
let current = mesh.get_vertex(v).and_then(|vt| vt.halfedge);
let needs_fix = current.is_none()
|| current.is_some_and(|he| {
if !mesh.contains_halfedge(he) {
return true;
}
let origin = mesh
.get_halfedge(he)
.and_then(|h| h.twin)
.and_then(|t| mesh.get_halfedge(t))
.map(|t| t.vertex);
origin != Some(v)
});
if needs_fix {
let new_he = find_outgoing_halfedge(mesh, v);
if let Some(vt) = mesh.get_vertex_mut(v) {
vt.halfedge = new_he;
}
}
}
}
fn find_outgoing_halfedge(mesh: &MeshStorage, v: VertexId) -> Option<HalfEdgeId> {
for he in mesh.halfedge_ids() {
let h = mesh.get_halfedge(he)?;
let origin = h.twin.and_then(|t| mesh.get_halfedge(t)).map(|t| t.vertex);
if origin == Some(v) {
return Some(he);
}
}
None
}
pub fn fill_hole(
mesh: &mut MeshStorage,
boundary_he: HalfEdgeId,
) -> Result<Vec<FaceId>, TopologyError> {
let he_data = mesh
.get_halfedge(boundary_he)
.ok_or(TopologyError::InvalidHalfEdge(boundary_he))?;
if he_data.face.is_some() {
return Err(TopologyError::Inconsistent("给定的半边不是边界半边".into()));
}
let loop_halfedges: Vec<HalfEdgeId> = BoundaryLoop::new(mesh, boundary_he).collect();
let n = loop_halfedges.len();
if n < 3 {
return Err(TopologyError::Inconsistent(
"边界环顶点数不足 3,无法填充".into(),
));
}
let loop_vertices: Vec<VertexId> = loop_halfedges
.iter()
.filter_map(|&he| mesh.get_halfedge(he).map(|h| h.vertex))
.collect();
if loop_vertices.len() < 3 {
return Err(TopologyError::Inconsistent("边界环顶点数不足 3".into()));
}
let positions: Vec<[f64; 3]> = loop_vertices
.iter()
.map(|&v| {
mesh.get_vertex(v)
.map(|vt| vt.position)
.unwrap_or([0.0, 0.0, 0.0])
})
.collect();
let tris = ear_clipping_3d(&positions);
if tris.is_empty() {
return Err(TopologyError::Inconsistent(
"三角化失败,可能边界环退化".into(),
));
}
for &he in &loop_halfedges {
if let Some(h) = mesh.get_halfedge_mut(he) {
h.next = None;
h.prev = None;
}
}
let mut filled_faces = Vec::with_capacity(tris.len());
for &[i, j, k] in &tris {
let vi = loop_vertices[i];
let vj = loop_vertices[j];
let vk = loop_vertices[k];
let face = add_triangle(mesh, vi, vj, vk).or_else(|_| add_triangle(mesh, vk, vj, vi))?;
filled_faces.push(face);
}
fix_all_vertex_entries(mesh);
Ok(filled_faces)
}
pub fn fill_all_holes(mesh: &mut MeshStorage) -> Result<Vec<Vec<FaceId>>, TopologyError> {
let loops = boundary_loops(mesh);
let mut results = Vec::with_capacity(loops.len());
for boundary in &loops {
if boundary.is_empty() {
continue;
}
let start_he = boundary[0];
let faces = fill_hole(mesh, start_he)?;
results.push(faces);
}
Ok(results)
}
pub fn remove_isolated_vertices(mesh: &mut MeshStorage) -> usize {
let isolated: Vec<VertexId> = mesh
.vertex_ids()
.filter(|&v| mesh.get_vertex(v).and_then(|vt| vt.halfedge).is_none())
.collect();
let count = isolated.len();
for v in isolated {
mesh.remove_vertex(v);
}
count
}
pub fn remove_degenerate_faces(mesh: &mut MeshStorage) -> usize {
let degenerate: Vec<FaceId> = mesh
.face_ids()
.filter(|&f| {
let verts: Vec<VertexId> = FaceHalfEdges::new(mesh, f)
.filter_map(|he| mesh.get_halfedge(he).map(|h| h.vertex))
.collect();
if verts.len() != 3 {
return true;
}
let p0 = mesh.get_vertex(verts[0]).map(|v| v.position);
let p1 = mesh.get_vertex(verts[1]).map(|v| v.position);
let p2 = mesh.get_vertex(verts[2]).map(|v| v.position);
match (p0, p1, p2) {
(Some(a), Some(b), Some(c)) => is_triangle_degenerate_3d(a, b, c),
_ => true,
}
})
.collect();
let count = degenerate.len();
let mut failed: u32 = 0;
for f in degenerate {
if remove_face(mesh, f).is_err() {
failed += 1;
}
}
if failed > 0 {
log::warn!("[halfedge::remove_degenerate_faces] 警告:{failed} 个退化面删除失败");
}
count
}
pub fn detect_nonmanifold_edges(mesh: &MeshStorage) -> Vec<HalfEdgeId> {
let mut edge_count: std::collections::HashMap<(VertexId, VertexId), usize> =
std::collections::HashMap::new();
let mut edge_rep: std::collections::HashMap<(VertexId, VertexId), HalfEdgeId> =
std::collections::HashMap::new();
for he in mesh.halfedge_ids() {
let h = match mesh.get_halfedge(he) {
Some(h) => h,
None => continue,
};
if h.face.is_none() {
continue;
}
let tip = h.vertex;
let origin = match h.twin.and_then(|t| mesh.get_halfedge(t)) {
Some(t) => t.vertex,
None => continue,
};
let key = if origin < tip {
(origin, tip)
} else {
(tip, origin)
};
*edge_count.entry(key).or_insert(0) += 1;
edge_rep.entry(key).or_insert(he);
}
edge_count
.into_iter()
.filter(|(_, count)| *count > 2)
.filter_map(|(key, _)| edge_rep.get(&key).copied())
.collect()
}
pub fn detect_nonmanifold_vertices(mesh: &MeshStorage) -> Vec<VertexId> {
let mut result = Vec::new();
for v in mesh.vertex_ids().collect::<Vec<_>>() {
let he = match mesh.get_vertex(v).and_then(|vt| vt.halfedge) {
Some(h) => h,
None => continue,
};
let mut cur = he;
let mut closed = false;
let max_iter = mesh.halfedge_count() + 1;
for _ in 0..max_iter {
let next = mesh
.get_halfedge(cur)
.and_then(|h| h.prev)
.and_then(|p| mesh.get_halfedge(p))
.and_then(|h| h.twin);
match next {
Some(n) if n == he => {
closed = true;
break;
}
Some(n) => cur = n,
None => break,
}
}
if !closed {
let mut cur = he;
let mut cw_closed = false;
for _ in 0..max_iter {
let prev = mesh
.get_halfedge(cur)
.and_then(|h| h.twin)
.and_then(|t| mesh.get_halfedge(t))
.and_then(|h| h.next);
match prev {
Some(p) if p == he => {
cw_closed = true;
break;
}
Some(p) => cur = p,
None => break,
}
}
if !cw_closed {
let outgoing: Vec<HalfEdgeId> =
crate::traversal::VertexRing::new(mesh, v).collect();
let boundary_count = outgoing
.iter()
.filter(|&&he_id| {
mesh.get_halfedge(he_id).is_some_and(|h| h.face.is_none())
|| mesh
.get_halfedge(he_id)
.and_then(|h| h.twin)
.and_then(|t| mesh.get_halfedge(t))
.is_some_and(|t| t.face.is_none())
})
.count();
if boundary_count > 2 {
result.push(v);
}
}
}
}
result
}
#[derive(Debug, Clone, Default)]
pub struct RepairStats {
pub holes_filled: usize,
pub isolated_vertices_removed: usize,
pub degenerate_faces_removed: usize,
pub faces_flipped: usize,
}
pub fn repair_mesh(mesh: &mut MeshStorage) -> Result<RepairStats, TopologyError> {
let mut stats = RepairStats::default();
let hole_results = fill_all_holes(mesh)?;
stats.holes_filled = hole_results.len();
stats.degenerate_faces_removed = remove_degenerate_faces(mesh);
stats.isolated_vertices_removed = remove_isolated_vertices(mesh);
stats.faces_flipped = crate::orientation::fix_orientations(mesh);
Ok(stats)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::{MeshStorage, Vertex};
use crate::test_util;
fn build_mesh_with_hole() -> MeshStorage {
let mut mesh = test_util::build_icosphere(1);
let face = mesh.face_ids().next().unwrap();
remove_face(&mut mesh, face).unwrap();
mesh
}
#[test]
fn fill_hole_basic() {
let mut mesh = build_mesh_with_hole();
let loops = boundary_loops(&mesh);
assert_eq!(loops.len(), 1, "应有一个洞");
let faces = fill_hole(&mut mesh, loops[0][0]).unwrap();
assert_eq!(faces.len(), 1, "三角形洞应填充为 1 个面");
assert!(boundary_loops(&mesh).is_empty(), "填充后不应有洞");
}
#[test]
fn fill_all_holes_icosphere() {
let mut mesh = build_mesh_with_hole();
let results = fill_all_holes(&mut mesh).unwrap();
assert_eq!(results.len(), 1);
assert!(boundary_loops(&mesh).is_empty());
}
#[test]
fn fill_hole_on_closed_mesh_returns_empty() {
let mesh = test_util::build_icosphere(1);
assert!(boundary_loops(&mesh).is_empty(), "闭合网格不应有洞");
}
#[test]
fn fill_hole_non_boundary_is_error() {
let mut mesh = test_util::build_icosphere(1);
let face_he = mesh
.face_ids()
.next()
.and_then(|f| mesh.get_face(f)?.halfedge)
.unwrap();
assert!(fill_hole(&mut mesh, face_he).is_err());
}
#[test]
fn remove_isolated_vertices_basic() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0; 3]));
let v1 = mesh.add_vertex(Vertex::new([1.0; 3]));
let v2 = mesh.add_vertex(Vertex::new([2.0; 3]));
let v3 = mesh.add_vertex(Vertex::new([3.0; 3]));
add_triangle(&mut mesh, v0, v1, v2).unwrap();
assert_eq!(remove_isolated_vertices(&mut mesh), 1);
assert!(!mesh.contains_vertex(v3));
}
#[test]
fn remove_face_creates_boundary() {
let mut mesh = test_util::build_icosphere(1);
let face = mesh.face_ids().next().unwrap();
let before = mesh.face_count();
remove_face(&mut mesh, face).unwrap();
assert_eq!(mesh.face_count(), before - 1);
let loops = boundary_loops(&mesh);
assert_eq!(loops.len(), 1, "删除面后应有一个洞");
assert_eq!(loops[0].len(), 3, "三角形洞应有 3 条边界半边");
}
#[test]
fn remove_face_and_fill_restores() {
let mut mesh = test_util::build_icosphere(1);
let before_faces = mesh.face_count();
let face = mesh.face_ids().next().unwrap();
remove_face(&mut mesh, face).unwrap();
let faces = fill_all_holes(&mut mesh).unwrap();
assert_eq!(faces.len(), 1);
assert_eq!(mesh.face_count(), before_faces);
}
#[test]
fn detect_nonmanifold_edges_closed_mesh() {
let mesh = test_util::build_icosphere(1);
assert!(detect_nonmanifold_edges(&mesh).is_empty());
}
#[test]
fn repair_mesh_basic() {
let mut mesh = build_mesh_with_hole();
let stats = repair_mesh(&mut mesh).unwrap();
assert_eq!(stats.holes_filled, 1);
assert!(boundary_loops(&mesh).is_empty());
}
#[test]
fn fill_hole_quad_hole() {
let mesh = crate::primitives::build_grid(1.0, 1.0, 2, 2);
let mut mesh = mesh;
let loops = boundary_loops(&mesh);
if !loops.is_empty() {
let faces = fill_hole(&mut mesh, loops[0][0]).unwrap();
assert!(!faces.is_empty());
}
}
#[test]
fn remove_and_fill_multiple_faces() {
let mut mesh = test_util::build_icosphere(1);
let faces: Vec<FaceId> = mesh.face_ids().take(2).collect();
remove_face(&mut mesh, faces[0]).unwrap();
remove_face(&mut mesh, faces[1]).unwrap();
let loops = boundary_loops(&mesh);
assert!(!loops.is_empty());
let results = fill_all_holes(&mut mesh).unwrap();
assert_eq!(results.len(), loops.len());
}
#[test]
fn remove_degenerate_faces_on_good_mesh() {
let mut mesh = test_util::build_icosphere(1);
assert_eq!(remove_degenerate_faces(&mut mesh), 0);
}
}