use crate::geometry::{edge_length, vertex_normal};
use crate::ids::{HalfEdgeId, VertexId};
use crate::storage::MeshStorage;
use crate::topology_ops::{collapse_edge_at, flip_edge, split_edge};
use crate::traversal::{FaceHalfEdges, VertexAdjacentVerts, VertexRing, is_boundary_vertex};
fn tangential_smooth(mesh: &mut MeshStorage, v: VertexId) {
let neighbors: Vec<[f64; 3]> = VertexAdjacentVerts::new(mesh, v)
.filter_map(|n| mesh.get_vertex(n).map(|vt| vt.position))
.collect();
if neighbors.is_empty() {
return;
}
let n = neighbors.len() as f64;
let mut avg = [0.0; 3];
for p in &neighbors {
avg[0] += p[0];
avg[1] += p[1];
avg[2] += p[2];
}
avg[0] /= n;
avg[1] /= n;
avg[2] /= n;
let pos = mesh.get_vertex(v).unwrap().position;
let normal = vertex_normal(mesh, v).unwrap_or([0.0, 1.0, 0.0]);
let diff = [avg[0] - pos[0], avg[1] - pos[1], avg[2] - pos[2]];
let dot_n = diff[0] * normal[0] + diff[1] * normal[1] + diff[2] * normal[2];
let tangent = [
diff[0] - normal[0] * dot_n,
diff[1] - normal[1] * dot_n,
diff[2] - normal[2] * dot_n,
];
let lambda = 0.5; let new_pos = [
pos[0] + lambda * tangent[0],
pos[1] + lambda * tangent[1],
pos[2] + lambda * tangent[2],
];
mesh.get_vertex_mut(v).unwrap().position = new_pos;
}
#[derive(Debug, Clone, Default)]
pub struct RemeshStats {
pub iterations: usize,
pub splits: usize,
pub collapses: usize,
pub flips: usize,
pub target_length: f64,
}
pub fn isotropic_remesh(
mesh: &mut MeshStorage,
target_length: Option<f64>,
iterations: usize,
reproject: bool,
) -> RemeshStats {
let target_len = target_length.unwrap_or_else(|| compute_target_length(mesh));
if target_len <= 0.0 {
return RemeshStats {
target_length: target_len,
..Default::default()
};
}
let min_len = target_len * 0.8; let max_len = target_len * 1.333;
let mut stats = RemeshStats {
iterations,
target_length: target_len,
..Default::default()
};
for _iter in 0..iterations {
let split_count = split_long_edges(mesh, max_len);
stats.splits += split_count;
let collapse_count = collapse_short_edges(mesh, min_len);
stats.collapses += collapse_count;
let flip_count = flip_for_valence(mesh);
stats.flips += flip_count;
smooth_vertices(mesh);
if reproject {
}
}
stats
}
fn compute_target_length(mesh: &MeshStorage) -> f64 {
let mut lengths: Vec<f64> = mesh
.halfedge_ids()
.filter_map(|he| edge_length(mesh, he))
.collect();
if lengths.is_empty() {
return 0.0;
}
lengths.sort_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let n = lengths.len();
let mid = n / 4; let end = (3 * n) / 4;
if end <= mid {
return lengths[n / 2]; }
let slice = &lengths[mid..end];
slice.iter().sum::<f64>() / slice.len() as f64
}
fn split_long_edges(mesh: &mut MeshStorage, max_len: f64) -> usize {
let mut count = 0;
let to_split: Vec<HalfEdgeId> = mesh
.halfedge_ids()
.filter(|&he| {
let h = mesh.get_halfedge(he).unwrap();
if let Some(twin) = h.twin
&& he > twin
{
return false;
}
edge_length(mesh, he).is_some_and(|l| l > max_len)
})
.collect();
for he in to_split {
if !mesh.contains_halfedge(he) {
continue;
}
if edge_length(mesh, he).is_some_and(|l| l > max_len) && split_edge(mesh, he).is_ok() {
count += 1;
}
}
count
}
fn collapse_short_edges(mesh: &mut MeshStorage, min_len: f64) -> usize {
let mut count = 0;
let to_collapse: Vec<HalfEdgeId> = mesh
.halfedge_ids()
.filter(|&he| {
let h = mesh.get_halfedge(he).unwrap();
if let Some(twin) = h.twin
&& he > twin
{
return false;
}
edge_length(mesh, he).is_some_and(|l| l < min_len && l > 1e-10)
})
.collect();
for he in to_collapse {
if !mesh.contains_halfedge(he) {
continue;
}
if let Some(len) = edge_length(mesh, he)
&& len < min_len
&& len > 1e-10
{
let h = mesh.get_halfedge(he).unwrap();
let v_dst = h.vertex;
let mid = if let Some(twin) = h.twin {
let v_src = mesh.get_halfedge(twin).unwrap().vertex;
let p0 = mesh.get_vertex(v_src).unwrap().position;
let p1 = mesh.get_vertex(v_dst).unwrap().position;
[
(p0[0] + p1[0]) / 2.0,
(p0[1] + p1[1]) / 2.0,
(p0[2] + p1[2]) / 2.0,
]
} else {
mesh.get_vertex(v_dst).unwrap().position
};
if collapse_edge_at(mesh, he, mid).is_ok() {
count += 1;
}
}
}
count
}
fn flip_for_valence(mesh: &mut MeshStorage) -> usize {
let mut count = 0;
let to_check: Vec<HalfEdgeId> = mesh.halfedge_ids().collect();
for he in to_check {
if !mesh.contains_halfedge(he) {
continue;
}
let h = match mesh.get_halfedge(he) {
Some(h) if h.twin.is_some() => h,
_ => continue,
};
let twin = h.twin.unwrap();
if he > twin {
continue;
}
let v0 = mesh.get_halfedge(twin).unwrap().vertex; let v1 = h.vertex; let face_hes: Vec<_> = FaceHalfEdges::new(mesh, h.face.unwrap()).collect();
if face_hes.len() != 3 {
continue;
}
let v2 = face_hes
.iter()
.find(|&&eh| {
let v = mesh.get_halfedge(eh).unwrap().vertex;
v != v0 && v != v1
})
.map(|&eh| mesh.get_halfedge(eh).unwrap().vertex);
let twin_face = mesh.get_halfedge(twin).unwrap().face;
let twin_hes: Vec<_> = FaceHalfEdges::new(mesh, twin_face.unwrap()).collect();
if twin_hes.len() != 3 {
continue;
}
let v3 = twin_hes
.iter()
.find(|&&eh| {
let v = mesh.get_halfedge(eh).unwrap().vertex;
v != v0 && v != v1
})
.map(|&eh| mesh.get_halfedge(eh).unwrap().vertex);
let (Some(v2), Some(v3)) = (v2, v3) else {
continue;
};
let val_a_before = VertexRing::new(mesh, v0).count();
let val_b_before = VertexRing::new(mesh, v1).count();
let val_c_before = VertexRing::new(mesh, v2).count();
let val_d_before = VertexRing::new(mesh, v3).count();
let target_a = target_valence(v0, mesh);
let target_b = target_valence(v1, mesh);
let target_c = target_valence(v2, mesh);
let target_d = target_valence(v3, mesh);
let before = (val_a_before as isize - target_a as isize).unsigned_abs()
+ (val_b_before as isize - target_b as isize).unsigned_abs()
+ (val_c_before as isize - target_c as isize).unsigned_abs()
+ (val_d_before as isize - target_d as isize).unsigned_abs();
let after = (val_a_before.saturating_sub(1) as isize - target_a as isize).unsigned_abs()
+ (val_b_before.saturating_sub(1) as isize - target_b as isize).unsigned_abs()
+ ((val_c_before + 1) as isize - target_c as isize).unsigned_abs()
+ ((val_d_before + 1) as isize - target_d as isize).unsigned_abs();
if after < before && flip_edge(mesh, he).is_ok() {
count += 1;
}
}
count
}
fn target_valence(v: VertexId, mesh: &MeshStorage) -> usize {
if is_boundary_vertex(mesh, v) { 4 } else { 6 }
}
fn smooth_vertices(mesh: &mut MeshStorage) {
let verts: Vec<VertexId> = mesh.vertex_ids().collect();
for v in verts {
if is_boundary_vertex(mesh, v) {
continue;
}
tangential_smooth(mesh, v);
}
}
pub fn quick_remesh(mesh: &mut MeshStorage) -> RemeshStats {
isotropic_remesh(mesh, None, 3, false)
}
pub fn remesh_to_length(mesh: &mut MeshStorage, target_length: f64) -> RemeshStats {
isotropic_remesh(mesh, Some(target_length), 5, false)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::topology_ops::validate_mesh;
#[test]
fn compute_target_length_icosphere() {
let mesh = crate::test_util::build_icosphere(1);
let l = compute_target_length(&mesh);
assert!(l > 0.0);
assert!(l < 2.0); }
#[test]
fn remesh_icosphere_preserves_topology() {
let mut mesh = crate::test_util::build_icosphere(1);
let f_before = mesh.face_count();
let stats = quick_remesh(&mut mesh);
validate_mesh(&mesh).unwrap();
let f_after = mesh.face_count();
let ratio = f_after as f64 / f_before as f64;
assert!(
ratio > 0.5 && ratio < 2.0,
"面数变化在合理范围内: {} -> {}",
f_before,
f_after
);
let _ = stats;
}
#[test]
fn remesh_preserves_closedness() {
let mut mesh = crate::test_util::build_icosphere(1);
assert!(crate::traversal::is_closed(&mesh));
let _ = quick_remesh(&mut mesh);
assert!(crate::traversal::is_closed(&mesh));
}
#[test]
fn target_valence_interior_is_6() {
let mesh = crate::test_util::build_icosphere(1);
for v in mesh.vertex_ids() {
if !is_boundary_vertex(&mesh, v) {
assert_eq!(target_valence(v, &mesh), 6);
}
}
}
#[test]
fn remesh_to_specific_length() {
let mut mesh = crate::test_util::build_icosphere(1);
let stats = remesh_to_length(&mut mesh, 0.5);
assert!(stats.target_length > 0.0);
validate_mesh(&mesh).unwrap();
}
}