use crate::ids::{HalfEdgeId, VertexId};
use crate::linalg::vec3::{Vec3, add, scale, sub};
use crate::storage::MeshStorage;
use crate::traversal::VertexAdjacentVerts;
pub fn laplacian_smooth_vertex(mesh: &MeshStorage, v: VertexId) -> Option<Vec3> {
let p = mesh.get_vertex(v)?.position;
let neighbors: Vec<Vec3> = VertexAdjacentVerts::new(mesh, v)
.filter_map(|n| mesh.get_vertex(n).map(|vt| vt.position))
.collect();
if neighbors.is_empty() {
return Some(p);
}
let mut sum = [0.0f64; 3];
for q in &neighbors {
sum = add(sum, *q);
}
Some(scale(sum, 1.0 / neighbors.len() as f64))
}
pub fn laplacian_smooth_mesh(mesh: &mut MeshStorage, lambda: f64, iterations: usize) {
if lambda <= 0.0 || iterations == 0 {
return;
}
let clamped_lambda = lambda.min(1.0);
for _ in 0..iterations {
let new_positions: Vec<(VertexId, Vec3)> = mesh
.vertex_ids()
.filter_map(|v| {
let old_p = mesh.get_vertex(v)?.position;
let target = laplacian_smooth_vertex(mesh, v)?;
let blended = add(
scale(old_p, 1.0 - clamped_lambda),
scale(target, clamped_lambda),
);
Some((v, blended))
})
.collect();
for (v, p) in new_positions {
if let Some(vt) = mesh.get_vertex_mut(v) {
vt.position = p;
}
}
}
}
pub fn taubin_smooth_mesh(mesh: &mut MeshStorage, lambda: f64, mu: f64, iterations: usize) {
if iterations == 0 || lambda <= 0.0 || mu >= 0.0 {
return;
}
let lambda = lambda.min(1.0);
let mu = mu.max(-1.0);
for _ in 0..iterations {
laplacian_step_signed(mesh, lambda);
laplacian_step_signed(mesh, mu);
}
}
fn laplacian_step_signed(mesh: &mut MeshStorage, lambda: f64) {
let new_positions: Vec<(VertexId, Vec3)> = mesh
.vertex_ids()
.filter_map(|v| {
let old_p = mesh.get_vertex(v)?.position;
let target = laplacian_smooth_vertex(mesh, v)?;
let blended = add(scale(old_p, 1.0 - lambda), scale(target, lambda));
Some((v, blended))
})
.collect();
for (v, p) in new_positions {
if let Some(vt) = mesh.get_vertex_mut(v) {
vt.position = p;
}
}
}
pub fn bilateral_smooth_mesh(
mesh: &mut MeshStorage,
sigma_c: f64,
sigma_s: f64,
iterations: usize,
) {
if iterations == 0 || sigma_c <= 0.0 || sigma_s <= 0.0 {
return;
}
let sigma_c2 = 2.0 * sigma_c * sigma_c;
let sigma_s2 = 2.0 * sigma_s * sigma_s;
for _ in 0..iterations {
let normals: std::collections::HashMap<VertexId, Vec3> = mesh
.vertex_ids()
.filter_map(|v| super::query::vertex_normal(mesh, v).map(|n| (v, n)))
.collect();
let verts: Vec<VertexId> = mesh.vertex_ids().collect();
let updates: Vec<(VertexId, Vec3)> = verts
.iter()
.filter_map(|&v| {
let p_i = mesh.get_vertex(v)?.position;
let n_i = normals.get(&v).copied().unwrap_or([0.0; 3]);
let mut sum_disp = [0.0; 3];
let mut sum_w = 0.0;
for n in VertexAdjacentVerts::new(mesh, v) {
if n == v {
continue;
}
let p_j = mesh.get_vertex(n)?.position;
let n_j = normals.get(&n).copied().unwrap_or([0.0; 3]);
let d = sub(p_j, p_i);
let dist = (d[0] * d[0] + d[1] * d[1] + d[2] * d[2]).sqrt();
if dist < 1e-20 {
continue;
}
let w_c = (-dist * dist / sigma_c2).exp();
let n_diff = sub(n_i, n_j);
let n_dot = n_diff[0].powi(2) + n_diff[1].powi(2) + n_diff[2].powi(2);
let w_s = (-n_dot / sigma_s2).exp();
let w = w_c * w_s;
sum_disp[0] += w * d[0];
sum_disp[1] += w * d[1];
sum_disp[2] += w * d[2];
sum_w += w;
}
if sum_w < 1e-20 {
return None;
}
let delta = scale(sum_disp, 1.0 / sum_w);
Some((v, add(p_i, delta)))
})
.collect();
for (v, p) in updates {
if let Some(vt) = mesh.get_vertex_mut(v) {
vt.position = p;
}
}
}
}
pub fn laplacian_smooth_mesh_par(mesh: &mut MeshStorage, lambda: f64, iterations: usize) {
use rayon::prelude::*;
if lambda <= 0.0 || iterations == 0 {
return;
}
let clamped_lambda = lambda.min(1.0);
for _ in 0..iterations {
let verts: Vec<VertexId> = mesh.vertex_ids().collect();
let new_positions: Vec<(VertexId, Vec3)> = verts
.par_iter()
.filter_map(|&v| {
let old_p = mesh.get_vertex(v)?.position;
let target = laplacian_smooth_vertex(mesh, v)?;
let blended = add(
scale(old_p, 1.0 - clamped_lambda),
scale(target, clamped_lambda),
);
Some((v, blended))
})
.collect();
for (v, p) in new_positions {
if let Some(vt) = mesh.get_vertex_mut(v) {
vt.position = p;
}
}
}
}
pub fn feature_edges_par(mesh: &MeshStorage, angle_threshold: f64) -> Vec<HalfEdgeId> {
use rayon::prelude::*;
let hes: Vec<HalfEdgeId> = mesh.halfedge_ids().collect();
hes.par_iter()
.filter(|&&he| super::query::is_feature_edge(mesh, he, angle_threshold).unwrap_or(false))
.copied()
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ids::VertexId;
use crate::storage::{Face, HalfEdge, Vertex};
fn build_unit_triangle() -> (MeshStorage, [VertexId; 3], crate::ids::FaceId) {
let mut mesh = MeshStorage::new();
let a = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let b = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let c = mesh.add_vertex(Vertex::new([0.0, 1.0, 0.0]));
let h_ab = mesh.add_halfedge(HalfEdge::new(b)); let h_bc = mesh.add_halfedge(HalfEdge::new(c)); let h_ca = mesh.add_halfedge(HalfEdge::new(a)); let t_ab = mesh.add_halfedge(HalfEdge::new(a)); let t_bc = mesh.add_halfedge(HalfEdge::new(b)); let t_ca = mesh.add_halfedge(HalfEdge::new(c));
let f = mesh.add_face(Face::new());
for (he, twin, next, prev) in [
(h_ab, t_ab, h_bc, h_ca),
(h_bc, t_bc, h_ca, h_ab),
(h_ca, t_ca, h_ab, h_bc),
] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(f);
}
for (t, he) in [(t_ab, h_ab), (t_bc, h_bc), (t_ca, h_ca)] {
mesh.get_halfedge_mut(t).unwrap().twin = Some(he);
}
mesh.get_vertex_mut(a).unwrap().halfedge = Some(h_ab);
mesh.get_vertex_mut(b).unwrap().halfedge = Some(h_bc);
mesh.get_vertex_mut(c).unwrap().halfedge = Some(h_ca);
mesh.get_face_mut(f).unwrap().halfedge = Some(h_ab);
(mesh, [a, b, c], f)
}
#[test]
fn laplacian_smooth_vertex_isolated_returns_original() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([1.0, 2.0, 3.0]));
let p = laplacian_smooth_vertex(&mesh, v).unwrap();
assert_eq!(p, [1.0, 2.0, 3.0]);
}
#[test]
fn laplacian_smooth_vertex_two_neighbors() {
let (mesh, v, _f) = build_unit_triangle();
let p = laplacian_smooth_vertex(&mesh, v[0]).unwrap();
assert!((p[0] - 0.5).abs() < 1e-9);
assert!((p[1] - 0.5).abs() < 1e-9);
assert!((p[2] - 0.0).abs() < 1e-9);
}
#[test]
fn laplacian_smooth_mesh_preserves_centroid() {
let (mut mesh, _v, _f) = build_unit_triangle();
let original_centroid = [1.0 / 3.0, 1.0 / 3.0, 0.0];
laplacian_smooth_mesh(&mut mesh, 1.0, 1);
let positions: Vec<_> = mesh
.vertex_ids()
.map(|v| mesh.get_vertex(v).unwrap().position)
.collect();
let new_centroid = [
positions.iter().map(|p| p[0]).sum::<f64>() / positions.len() as f64,
positions.iter().map(|p| p[1]).sum::<f64>() / positions.len() as f64,
positions.iter().map(|p| p[2]).sum::<f64>() / positions.len() as f64,
];
for i in 0..3 {
assert!(
(new_centroid[i] - original_centroid[i]).abs() < 1e-9,
"重心分量 {} 应保留: 实际 {} vs 期望 {}",
i,
new_centroid[i],
original_centroid[i]
);
}
}
#[test]
fn taubin_smooth_preserves_volume_better_than_laplacian() {
let mesh0 = crate::test_util::build_icosphere(1);
let area0 = super::super::query::surface_area(&mesh0);
let mut mesh_lap = crate::test_util::build_icosphere(1);
laplacian_smooth_mesh(&mut mesh_lap, 0.5, 20);
let area_lap = super::super::query::surface_area(&mesh_lap);
let laplacian_shrink = (area0 - area_lap).abs() / area0;
let mut mesh_tau = crate::test_util::build_icosphere(1);
taubin_smooth_mesh(&mut mesh_tau, 0.5, -0.53, 20);
let area_tau = super::super::query::surface_area(&mesh_tau);
let taubin_shrink = (area0 - area_tau).abs() / area0;
assert!(
taubin_shrink < laplacian_shrink,
"Taubin 收缩 ({}) 应小于 Laplacian ({})",
taubin_shrink,
laplacian_shrink
);
}
#[test]
fn bilateral_smooth_runs_on_icosphere() {
let mesh0 = crate::test_util::build_icosphere(1);
let mut mesh = crate::test_util::build_icosphere(1);
let stats = super::super::quality::edge_length_stats(&mesh);
bilateral_smooth_mesh(&mut mesh, stats.mean, 0.1, 3);
assert_eq!(mesh.vertex_count(), mesh0.vertex_count());
assert_eq!(mesh.face_count(), mesh0.face_count());
}
#[test]
fn taubin_smooth_zero_iterations_is_noop() {
let mut mesh = crate::test_util::build_icosphere(1);
let p0 = mesh.vertex_ids().next().unwrap();
let pos_before = mesh.get_vertex(p0).unwrap().position;
taubin_smooth_mesh(&mut mesh, 0.5, -0.53, 0);
let pos_after = mesh.get_vertex(p0).unwrap().position;
assert_eq!(pos_before, pos_after);
}
#[test]
fn bilateral_smooth_zero_iterations_is_noop() {
let mut mesh = crate::test_util::build_icosphere(1);
let p0 = mesh.vertex_ids().next().unwrap();
let pos_before = mesh.get_vertex(p0).unwrap().position;
bilateral_smooth_mesh(&mut mesh, 0.1, 0.1, 0);
let pos_after = mesh.get_vertex(p0).unwrap().position;
assert_eq!(pos_before, pos_after);
}
#[test]
fn feature_edges_par_matches_serial() {
let mesh = crate::test_util::build_icosphere(1);
let threshold = 0.3_f64;
let mut s = super::super::query::feature_edges(&mesh, threshold);
let mut p = feature_edges_par(&mesh, threshold);
s.sort();
p.sort();
assert_eq!(s, p, "feature_edges 串/并行结果不一致");
}
}