pub mod catmull_clark;
pub mod sqrt3;
use std::collections::HashMap;
use crate::ids::VertexId;
use crate::io::build_mesh_from_vertices_and_faces;
use crate::storage::MeshStorage;
use crate::traversal::{FaceHalfEdges, VertexRing, is_boundary_edge, is_boundary_vertex};
pub fn loop_subdivide(mesh: &MeshStorage) -> MeshStorage {
let orig_v_ids: Vec<VertexId> = mesh.vertex_ids().collect();
let n_orig_verts = orig_v_ids.len();
let mut v_index: HashMap<VertexId, u32> = HashMap::new();
for (i, &v) in orig_v_ids.iter().enumerate() {
v_index.insert(v, i as u32);
}
let mut orig_faces: Vec<[u32; 3]> = Vec::new();
let mut skipped_non_triangle: u32 = 0;
for f_id in mesh.face_ids() {
let verts: Vec<u32> = FaceHalfEdges::new(mesh, f_id)
.filter_map(|he| mesh.get_halfedge(he))
.map(|h| h.vertex)
.filter_map(|v| v_index.get(&v).copied())
.collect();
match verts.len() {
3 => orig_faces.push([verts[0], verts[1], verts[2]]),
_ => skipped_non_triangle += 1,
}
}
if skipped_non_triangle > 0 {
log::warn!(
"[halfedge::loop_subdivide] 警告:输入网格含 {} 个非三角面,已跳过(Loop 细分仅支持三角形)。\
若需处理任意多边形,请使用 catmull_clark_subdivide。",
skipped_non_triangle
);
}
let mut new_positions: Vec<[f64; 3]> = orig_v_ids
.iter()
.filter_map(|&v| mesh.get_vertex(v))
.map(|vt| vt.position)
.collect();
while new_positions.len() < n_orig_verts {
new_positions.push([0.0; 3]);
}
let mut edge_midpoint: HashMap<(u32, u32), u32> = HashMap::new();
for he_id in mesh.halfedge_ids() {
let h = match mesh.get_halfedge(he_id) {
Some(h) => h,
None => continue,
};
let v1 = h.vertex; let v0 = match h.twin.and_then(|t| mesh.get_halfedge(t)) {
Some(t) => t.vertex, None => continue, };
let i0 = match v_index.get(&v0) {
Some(&i) => i,
None => continue,
};
let i1 = match v_index.get(&v1) {
Some(&i) => i,
None => continue,
};
if i0 == i1 {
continue; }
let key = edge_key(i0, i1);
if edge_midpoint.contains_key(&key) {
continue; }
let p0 = new_positions[i0 as usize];
let p1 = new_positions[i1 as usize];
let midpoint = if is_boundary_edge(mesh, he_id) {
[
0.5 * (p0[0] + p1[0]),
0.5 * (p0[1] + p1[1]),
0.5 * (p0[2] + p1[2]),
]
} else {
let v2 = h.next.and_then(|n| mesh.get_halfedge(n)).map(|n| n.vertex);
let v3 = h
.twin
.and_then(|t| mesh.get_halfedge(t))
.and_then(|t| t.next)
.and_then(|n| mesh.get_halfedge(n))
.map(|n| n.vertex);
match (v2, v3) {
(Some(v2), Some(v3)) => {
let p2 = mesh.get_vertex(v2).map(|v| v.position).unwrap_or([0.0; 3]);
let p3 = mesh.get_vertex(v3).map(|v| v.position).unwrap_or([0.0; 3]);
[
3.0 / 8.0 * (p0[0] + p1[0]) + 1.0 / 8.0 * (p2[0] + p3[0]),
3.0 / 8.0 * (p0[1] + p1[1]) + 1.0 / 8.0 * (p2[1] + p3[1]),
3.0 / 8.0 * (p0[2] + p1[2]) + 1.0 / 8.0 * (p2[2] + p3[2]),
]
}
_ => {
[
0.5 * (p0[0] + p1[0]),
0.5 * (p0[1] + p1[1]),
0.5 * (p0[2] + p1[2]),
]
}
}
};
let new_idx = new_positions.len() as u32;
new_positions.push(midpoint);
edge_midpoint.insert(key, new_idx);
}
let mut updated_orig: Vec<[f64; 3]> = Vec::with_capacity(n_orig_verts);
for (i, &v) in orig_v_ids.iter().enumerate() {
let p_old = new_positions[i];
let new_pos = if is_boundary_vertex(mesh, v) {
let boundary_neighbors: Vec<VertexId> = VertexRing::new(mesh, v)
.filter(|&he| is_boundary_edge(mesh, he))
.filter_map(|he| mesh.get_halfedge(he))
.map(|h| h.vertex)
.collect();
if boundary_neighbors.len() == 2 {
let p_prev = mesh
.get_vertex(boundary_neighbors[0])
.map(|v| v.position)
.unwrap_or([0.0; 3]);
let p_next = mesh
.get_vertex(boundary_neighbors[1])
.map(|v| v.position)
.unwrap_or([0.0; 3]);
[
0.125 * p_prev[0] + 0.75 * p_old[0] + 0.125 * p_next[0],
0.125 * p_prev[1] + 0.75 * p_old[1] + 0.125 * p_next[1],
0.125 * p_prev[2] + 0.75 * p_old[2] + 0.125 * p_next[2],
]
} else {
p_old }
} else {
let neighbors: Vec<[f64; 3]> = VertexRing::new(mesh, v)
.filter_map(|he| mesh.get_halfedge(he))
.map(|h| h.vertex)
.filter_map(|nb| mesh.get_vertex(nb))
.map(|vt| vt.position)
.collect();
let n = neighbors.len();
if n == 0 {
p_old } else {
let beta = loop_beta(n);
let mut sum = [0.0; 3];
for nb in &neighbors {
sum[0] += nb[0];
sum[1] += nb[1];
sum[2] += nb[2];
}
let weight = 1.0 - (n as f64) * beta;
[
weight * p_old[0] + beta * sum[0],
weight * p_old[1] + beta * sum[1],
weight * p_old[2] + beta * sum[2],
]
}
};
updated_orig.push(new_pos);
}
for (i, pos) in updated_orig.into_iter().enumerate() {
new_positions[i] = pos;
}
let mut new_faces: Vec<[u32; 3]> = Vec::with_capacity(orig_faces.len() * 4);
for face in &orig_faces {
let [a, b, c] = *face;
let ab = match edge_midpoint.get(&edge_key(a, b)) {
Some(&idx) => idx,
None => continue, };
let bc = match edge_midpoint.get(&edge_key(b, c)) {
Some(&idx) => idx,
None => continue,
};
let ca = match edge_midpoint.get(&edge_key(c, a)) {
Some(&idx) => idx,
None => continue,
};
new_faces.push([a, ab, ca]);
new_faces.push([b, bc, ab]);
new_faces.push([c, ca, bc]);
new_faces.push([ab, bc, ca]);
}
build_mesh_from_vertices_and_faces(&new_positions, &new_faces)
.expect("Loop subdivision output is always valid")
}
fn loop_beta(n: usize) -> f64 {
if n == 0 {
return 0.0; }
let n_f = n as f64;
let cos_term = (2.0 * std::f64::consts::PI / n_f).cos();
let inner = 3.0 / 8.0 + 1.0 / 4.0 * cos_term;
(1.0 / n_f) * (5.0 / 8.0 - inner * inner)
}
#[inline]
fn edge_key(a: u32, b: u32) -> (u32, u32) {
if a < b { (a, b) } else { (b, a) }
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::{Face, HalfEdge, Vertex};
use crate::test_util::build_icosphere;
use crate::validate::check_topology;
fn build_single_quad_mesh() -> MeshStorage {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let v1 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let v2 = mesh.add_vertex(Vertex::new([1.0, 1.0, 0.0]));
let v3 = mesh.add_vertex(Vertex::new([0.0, 1.0, 0.0]));
let h0 = mesh.add_halfedge(HalfEdge::new(v1)); let h1 = mesh.add_halfedge(HalfEdge::new(v2)); let h2 = mesh.add_halfedge(HalfEdge::new(v3)); let h3 = mesh.add_halfedge(HalfEdge::new(v0));
for (he, next, prev) in [(h0, h1, h3), (h1, h2, h0), (h2, h3, h1), (h3, h0, h2)] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.next = Some(next);
h.prev = Some(prev);
}
let face = mesh.add_face(Face::new());
mesh.get_face_mut(face).unwrap().halfedge = Some(h0);
for he in [h0, h1, h2, h3] {
mesh.get_halfedge_mut(he).unwrap().face = Some(face);
}
mesh.get_vertex_mut(v0).unwrap().halfedge = Some(h0);
mesh.get_vertex_mut(v1).unwrap().halfedge = Some(h1);
mesh.get_vertex_mut(v2).unwrap().halfedge = Some(h2);
mesh.get_vertex_mut(v3).unwrap().halfedge = Some(h3);
mesh
}
#[test]
fn loop_subdivide_skips_non_triangle_with_warning() {
let mesh = build_single_quad_mesh();
assert_eq!(mesh.face_count(), 1, "输入应含 1 个四边形面");
let refined = loop_subdivide(&mesh);
assert_eq!(refined.face_count(), 0, "非三角面应被跳过,输出 0 面");
assert_eq!(
refined.vertex_count(),
4,
"原始 4 个顶点应保留(无新边中点)"
);
}
#[test]
fn loop_subdivide_mixed_mesh_keeps_only_triangles() {
let tri_verts = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let tri_faces = [[0, 1, 2]];
let mut mesh = build_mesh_from_vertices_and_faces(&tri_verts, &tri_faces).unwrap();
let qv0 = mesh.add_vertex(Vertex::new([10.0, 0.0, 0.0]));
let qv1 = mesh.add_vertex(Vertex::new([11.0, 0.0, 0.0]));
let qv2 = mesh.add_vertex(Vertex::new([11.0, 1.0, 0.0]));
let qv3 = mesh.add_vertex(Vertex::new([10.0, 1.0, 0.0]));
let qh0 = mesh.add_halfedge(HalfEdge::new(qv1));
let qh1 = mesh.add_halfedge(HalfEdge::new(qv2));
let qh2 = mesh.add_halfedge(HalfEdge::new(qv3));
let qh3 = mesh.add_halfedge(HalfEdge::new(qv0));
for (he, next, prev) in [
(qh0, qh1, qh3),
(qh1, qh2, qh0),
(qh2, qh3, qh1),
(qh3, qh0, qh2),
] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.next = Some(next);
h.prev = Some(prev);
}
let qf = mesh.add_face(Face::new());
mesh.get_face_mut(qf).unwrap().halfedge = Some(qh0);
for he in [qh0, qh1, qh2, qh3] {
mesh.get_halfedge_mut(he).unwrap().face = Some(qf);
}
mesh.get_vertex_mut(qv0).unwrap().halfedge = Some(qh0);
mesh.get_vertex_mut(qv1).unwrap().halfedge = Some(qh1);
mesh.get_vertex_mut(qv2).unwrap().halfedge = Some(qh2);
mesh.get_vertex_mut(qv3).unwrap().halfedge = Some(qh3);
assert_eq!(mesh.face_count(), 2);
let refined = loop_subdivide(&mesh);
assert_eq!(refined.face_count(), 4, "仅三角面被细分为 4 面");
}
#[test]
fn loop_subdivide_icosphere1_vertex_face_count() {
let mesh = build_icosphere(1);
assert_eq!(mesh.vertex_count(), 42);
assert_eq!(mesh.face_count(), 80);
let refined = loop_subdivide(&mesh);
assert_eq!(refined.vertex_count(), 162, "顶点数应为 42+120=162");
assert_eq!(refined.face_count(), 320, "面数应为 4*80=320");
}
#[test]
fn loop_subdivide_icosphere0_vertex_face_count() {
let mesh = build_icosphere(0);
let refined = loop_subdivide(&mesh);
assert_eq!(refined.vertex_count(), 42);
assert_eq!(refined.face_count(), 80);
}
#[test]
fn loop_subdivide_icosphere2_vertex_face_count() {
let mesh = build_icosphere(2);
let refined = loop_subdivide(&mesh);
assert_eq!(refined.vertex_count(), 642);
assert_eq!(refined.face_count(), 1280);
}
#[test]
fn loop_subdivide_passes_topology_validation() {
let mesh = build_icosphere(1);
let refined = loop_subdivide(&mesh);
assert!(
check_topology(&refined).is_ok(),
"细分后的网格应通过完整拓扑校验: {:?}",
check_topology(&refined)
);
}
#[test]
fn loop_subdivide_icosphere2_passes_validation() {
let mesh = build_icosphere(2);
let refined = loop_subdivide(&mesh);
assert!(check_topology(&refined).is_ok());
}
#[test]
fn loop_subdivide_preserves_euler_characteristic() {
for n in 0..=2 {
let mesh = build_icosphere(n);
let v = mesh.vertex_count() as i64;
let e = (mesh.halfedge_count() / 2) as i64;
let f = mesh.face_count() as i64;
assert_eq!(v - e + f, 2, "细分前 icosphere({}) Euler 示性数应为 2", n);
let refined = loop_subdivide(&mesh);
let v2 = refined.vertex_count() as i64;
let e2 = (refined.halfedge_count() / 2) as i64;
let f2 = refined.face_count() as i64;
assert_eq!(
v2 - e2 + f2,
2,
"细分后 icosphere({}) Euler 示性数应保持 2",
n
);
}
}
#[test]
fn loop_subdivide_single_triangle() {
let vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = [[0, 1, 2]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let refined = loop_subdivide(&mesh);
assert_eq!(refined.vertex_count(), 6);
assert_eq!(refined.face_count(), 4);
assert!(check_topology(&refined).is_ok());
}
#[test]
fn loop_subdivide_open_quad() {
let vertices = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let faces = [[0, 1, 2], [0, 2, 3]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let refined = loop_subdivide(&mesh);
assert_eq!(refined.vertex_count(), 9); assert_eq!(refined.face_count(), 8); assert!(check_topology(&refined).is_ok());
}
#[test]
fn loop_subdivide_boundary_edge_midpoint() {
let vertices = [[0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [0.0, 2.0, 0.0]];
let faces = [[0, 1, 2]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let refined = loop_subdivide(&mesh);
let mut positions: Vec<[f64; 3]> = refined
.vertex_ids()
.filter_map(|v| refined.get_vertex(v))
.map(|vt| vt.position)
.collect();
positions.sort_by(|a, b| {
a[0].partial_cmp(&b[0])
.unwrap()
.then(a[1].partial_cmp(&b[1]).unwrap())
.then(a[2].partial_cmp(&b[2]).unwrap())
});
let expected = [
[0.25, 0.25, 0.0],
[0.25, 1.5, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[1.5, 0.25, 0.0],
[0.0, 1.0, 0.0],
];
let eps = 1e-9;
for e in &expected {
let found = positions.iter().any(|p| {
(p[0] - e[0]).abs() < eps && (p[1] - e[1]).abs() < eps && (p[2] - e[2]).abs() < eps
});
assert!(found, "未找到期望位置 {:?},实际位置 = {:?}", e, positions);
}
}
#[test]
fn loop_subdivide_interior_edge_midpoint() {
let vertices = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[1.0, 1.0, 0.0],
[0.0, 1.0, 0.0],
];
let faces = [[0, 1, 2], [0, 2, 3]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let refined = loop_subdivide(&mesh);
let target = [0.5, 0.5, 0.0];
let eps = 1e-9;
let found = refined
.vertex_ids()
.filter_map(|v| refined.get_vertex(v))
.any(|vt| {
let p = vt.position;
(p[0] - target[0]).abs() < eps
&& (p[1] - target[1]).abs() < eps
&& (p[2] - target[2]).abs() < eps
});
assert!(found, "内部边中点应位于 (0.5, 0.5, 0)");
}
#[test]
fn loop_beta_regular_valence_6() {
let beta = loop_beta(6);
assert!(
(beta - 1.0 / 16.0).abs() < 1e-12,
"β(6) 应为 1/16,实际 {}",
beta
);
}
#[test]
fn loop_beta_valence_3() {
let beta = loop_beta(3);
assert!(
(beta - 3.0 / 16.0).abs() < 1e-12,
"β(3) 应为 3/16,实际 {}",
beta
);
}
#[test]
fn loop_beta_zero_valence_returns_zero() {
assert_eq!(loop_beta(0), 0.0);
}
#[test]
fn loop_subdivide_multiple_iterations_stay_valid() {
let mut mesh = build_icosphere(0);
for i in 0..3 {
mesh = loop_subdivide(&mesh);
assert!(
check_topology(&mesh).is_ok(),
"第 {} 次细分后拓扑校验失败",
i + 1
);
}
assert_eq!(mesh.vertex_count(), 642);
assert_eq!(mesh.face_count(), 1280);
}
#[test]
fn loop_subdivide_empty_mesh() {
let mesh = MeshStorage::new();
let refined = loop_subdivide(&mesh);
assert_eq!(refined.vertex_count(), 0);
assert_eq!(refined.face_count(), 0);
}
}