use std::collections::HashMap;
use crate::ids::{FaceId, 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 catmull_clark_subdivide(mesh: &MeshStorage) -> MeshStorage {
let orig_v_ids: Vec<VertexId> = mesh.vertex_ids().collect();
let n_orig = 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<Vec<u32>> = Vec::new();
let mut face_id_to_idx: HashMap<FaceId, usize> = HashMap::new();
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();
if verts.len() >= 3 {
face_id_to_idx.insert(f_id, orig_faces.len());
orig_faces.push(verts);
}
}
let n_faces = orig_faces.len();
let face_points: Vec<[f64; 3]> = orig_faces
.iter()
.map(|face| {
let k = face.len() as f64;
let mut sum = [0.0; 3];
for &vi in face {
let pos = mesh
.get_vertex(orig_v_ids[vi as usize])
.map(|v| v.position)
.unwrap_or([0.0; 3]);
sum[0] += pos[0];
sum[1] += pos[1];
sum[2] += pos[2];
}
[sum[0] / k, sum[1] / k, sum[2] / k]
})
.collect();
let mut edge_face_points: HashMap<(u32, u32), Vec<[f64; 3]>> = HashMap::new();
for (f_idx, face) in orig_faces.iter().enumerate() {
let k = face.len();
for i in 0..k {
let a = face[i];
let b = face[(i + 1) % k];
let key = edge_key(a, b);
edge_face_points
.entry(key)
.or_default()
.push(face_points[f_idx]);
}
}
let mut edge_point_pos: HashMap<(u32, u32), [f64; 3]> = HashMap::new();
for he_id in mesh.halfedge_ids() {
let h = match mesh.get_halfedge(he_id) {
Some(h) => h,
None => continue,
};
let v_t = h.vertex;
let v_o = match h.twin.and_then(|t| mesh.get_halfedge(t)) {
Some(t) => t.vertex,
None => continue,
};
let i_o = match v_index.get(&v_o) {
Some(&i) => i,
None => continue,
};
let i_t = match v_index.get(&v_t) {
Some(&i) => i,
None => continue,
};
if i_o == i_t {
continue;
}
let key = edge_key(i_o, i_t);
if edge_point_pos.contains_key(&key) {
continue;
}
let p_o = mesh
.get_vertex(orig_v_ids[i_o as usize])
.map(|v| v.position)
.unwrap_or([0.0; 3]);
let p_t = mesh
.get_vertex(orig_v_ids[i_t as usize])
.map(|v| v.position)
.unwrap_or([0.0; 3]);
let fps = edge_face_points.get(&key).cloned().unwrap_or_default();
let ep = match fps.len() {
2 => {
[
(p_o[0] + p_t[0] + fps[0][0] + fps[1][0]) / 4.0,
(p_o[1] + p_t[1] + fps[0][1] + fps[1][1]) / 4.0,
(p_o[2] + p_t[2] + fps[0][2] + fps[1][2]) / 4.0,
]
}
_ => {
[
(p_o[0] + p_t[0]) / 2.0,
(p_o[1] + p_t[1]) / 2.0,
(p_o[2] + p_t[2]) / 2.0,
]
}
};
edge_point_pos.insert(key, ep);
}
let mut edge_keys: Vec<(u32, u32)> = edge_point_pos.keys().cloned().collect();
edge_keys.sort();
let n_edges = edge_keys.len();
let mut edge_point_idx: HashMap<(u32, u32), u32> = HashMap::new();
for (i, &key) in edge_keys.iter().enumerate() {
edge_point_idx.insert(key, (n_orig + i) as u32);
}
let face_point_start = n_orig + n_edges;
let mut new_positions: Vec<[f64; 3]> = Vec::with_capacity(n_orig + n_edges + n_faces);
for &v in &orig_v_ids {
new_positions.push(mesh.get_vertex(v).map(|v| v.position).unwrap_or([0.0; 3]));
}
for &key in &edge_keys {
new_positions.push(edge_point_pos[&key]);
}
for fp in &face_points {
new_positions.push(*fp);
}
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]);
[
(p_prev[0] + 6.0 * p_old[0] + p_next[0]) / 8.0,
(p_prev[1] + 6.0 * p_old[1] + p_next[1]) / 8.0,
(p_prev[2] + 6.0 * p_old[2] + p_next[2]) / 8.0,
]
} else {
p_old
}
} else {
let outgoing: Vec<crate::ids::HalfEdgeId> = VertexRing::new(mesh, v).collect();
let n = outgoing.len();
if n == 0 {
p_old
} else {
let n_f = n as f64;
let mut f_sum = [0.0; 3];
let mut f_count = 0usize;
let mut r_sum = [0.0; 3];
for &he in &outgoing {
let h = match mesh.get_halfedge(he) {
Some(h) => h,
None => continue,
};
if let Some(f_id) = h.face
&& let Some(&f_idx) = face_id_to_idx.get(&f_id)
{
let fp = face_points[f_idx];
f_sum[0] += fp[0];
f_sum[1] += fp[1];
f_sum[2] += fp[2];
f_count += 1;
}
let neighbor_pos = mesh
.get_vertex(h.vertex)
.map(|v| v.position)
.unwrap_or([0.0; 3]);
let mid = [
(p_old[0] + neighbor_pos[0]) / 2.0,
(p_old[1] + neighbor_pos[1]) / 2.0,
(p_old[2] + neighbor_pos[2]) / 2.0,
];
r_sum[0] += mid[0];
r_sum[1] += mid[1];
r_sum[2] += mid[2];
}
if f_count == 0 || f_count != n {
p_old
} else {
let fc = f_count as f64;
let f_avg = [f_sum[0] / fc, f_sum[1] / fc, f_sum[2] / fc];
let r_avg = [r_sum[0] / n_f, r_sum[1] / n_f, r_sum[2] / n_f];
[
(f_avg[0] + 2.0 * r_avg[0] + (n_f - 3.0) * p_old[0]) / n_f,
(f_avg[1] + 2.0 * r_avg[1] + (n_f - 3.0) * p_old[1]) / n_f,
(f_avg[2] + 2.0 * r_avg[2] + (n_f - 3.0) * p_old[2]) / n_f,
]
}
}
};
new_positions[i] = new_pos;
}
let mut new_faces: Vec<[u32; 3]> = Vec::new();
for (f_idx, face) in orig_faces.iter().enumerate() {
let k = face.len();
let fp_idx = (face_point_start + f_idx) as u32;
for i in 0..k {
let v_i = face[i];
let v_next = face[(i + 1) % k];
let v_prev = face[(i + k - 1) % k];
let ep_i = edge_point_idx[&edge_key(v_i, v_next)];
let ep_prev = edge_point_idx[&edge_key(v_prev, v_i)];
new_faces.push([v_i, ep_i, fp_idx]);
new_faces.push([v_i, fp_idx, ep_prev]);
}
}
build_mesh_from_vertices_and_faces(&new_positions, &new_faces)
.expect("Catmull-Clark subdivision output is always valid")
}
#[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::io::build_mesh_from_polygons;
use crate::validate::check_topology;
fn build_cube() -> MeshStorage {
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], [0.0, 0.0, 1.0], [1.0, 0.0, 1.0], [1.0, 1.0, 1.0], [0.0, 1.0, 1.0], ];
let faces = vec![
vec![0, 3, 2, 1], vec![4, 5, 6, 7], vec![0, 1, 5, 4], vec![3, 7, 6, 2], vec![0, 4, 7, 3], vec![1, 2, 6, 5], ];
build_mesh_from_polygons(&vertices, &faces).unwrap()
}
#[test]
fn catmull_clark_cube_vertex_count() {
let mesh = build_cube();
assert_eq!(mesh.vertex_count(), 8);
assert_eq!(mesh.face_count(), 6);
let refined = catmull_clark_subdivide(&mesh);
assert_eq!(
refined.vertex_count(),
26,
"立方体细分后顶点数应为 8+12+6=26"
);
}
#[test]
fn catmull_clark_cube_face_count() {
let mesh = build_cube();
let refined = catmull_clark_subdivide(&mesh);
assert_eq!(refined.face_count(), 48, "立方体细分后面数应为 6*4*2=48");
}
#[test]
fn catmull_clark_cube_halfedge_count() {
let mesh = build_cube();
let refined = catmull_clark_subdivide(&mesh);
assert_eq!(refined.halfedge_count(), 144);
}
#[test]
fn catmull_clark_cube_passes_validation() {
let mesh = build_cube();
let refined = catmull_clark_subdivide(&mesh);
assert!(
check_topology(&refined).is_ok(),
"细分后的网格应通过完整拓扑校验: {:?}",
check_topology(&refined)
);
}
#[test]
fn catmull_clark_cube_preserves_euler_characteristic() {
let mesh = build_cube();
let refined = catmull_clark_subdivide(&mesh);
let v = refined.vertex_count() as i64;
let e = (refined.halfedge_count() / 2) as i64;
let f = refined.face_count() as i64;
assert_eq!(v - e + f, 2, "细分后 Euler 示性数应为 2");
}
#[test]
fn catmull_clark_single_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 = vec![vec![0, 1, 2, 3]];
let mesh = build_mesh_from_polygons(&vertices, &faces).unwrap();
let refined = catmull_clark_subdivide(&mesh);
assert_eq!(
refined.vertex_count(),
9,
"单四边形细分后顶点数应为 4+4+1=9"
);
assert_eq!(refined.face_count(), 8, "单四边形细分后面数应为 2*4=8");
assert!(check_topology(&refined).is_ok());
}
#[test]
fn catmull_clark_triangle_input() {
let vertices = [[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = vec![vec![0, 1, 2]];
let mesh = build_mesh_from_polygons(&vertices, &faces).unwrap();
let refined = catmull_clark_subdivide(&mesh);
assert_eq!(refined.vertex_count(), 7, "三角形细分后顶点数应为 3+3+1=7");
assert_eq!(refined.face_count(), 6, "三角形细分后面数应为 2*3=6");
assert!(check_topology(&refined).is_ok());
}
#[test]
fn catmull_clark_closed_tetrahedron() {
let vertices = [
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[0.0, 0.0, 1.0],
];
let faces = vec![
vec![0, 2, 1], vec![0, 1, 3], vec![0, 3, 2], vec![1, 2, 3], ];
let mesh = build_mesh_from_polygons(&vertices, &faces).unwrap();
let refined = catmull_clark_subdivide(&mesh);
assert_eq!(
refined.vertex_count(),
14,
"四面体细分后顶点数应为 4+6+4=14"
);
assert_eq!(refined.face_count(), 24, "四面体细分后面数应为 2*3*4=24");
let v = refined.vertex_count() as i64;
let e = (refined.halfedge_count() / 2) as i64;
let f = refined.face_count() as i64;
assert_eq!(v - e + f, 2, "闭合曲面 Euler 示性数应为 2");
assert!(check_topology(&refined).is_ok());
}
#[test]
fn catmull_clark_cube_face_point_at_center() {
let mesh = build_cube();
let refined = catmull_clark_subdivide(&mesh);
let bottom_fp = refined
.vertex_ids()
.nth(20)
.and_then(|v| refined.get_vertex(v))
.map(|v| v.position)
.expect("面点应存在");
assert!(
(bottom_fp[0] - 0.5).abs() < 1e-12
&& (bottom_fp[1] - 0.5).abs() < 1e-12
&& bottom_fp[2].abs() < 1e-12,
"底面面点应在 (0.5, 0.5, 0),实际在 {:?}",
bottom_fp
);
}
#[test]
fn catmull_clark_cube_vertex_moves_inward() {
let mesh = build_cube();
let refined = catmull_clark_subdivide(&mesh);
let v6 = refined
.vertex_ids()
.nth(6)
.and_then(|v| refined.get_vertex(v))
.map(|v| v.position)
.expect("顶点应存在");
assert!(
v6[0] < 1.0 && v6[1] < 1.0 && v6[2] < 1.0,
"角点 (1,1,1) 应向中心移动,实际在 {:?}",
v6
);
}
#[test]
fn catmull_clark_double_subdivide() {
let mesh = build_cube();
let refined1 = catmull_clark_subdivide(&mesh);
let refined2 = catmull_clark_subdivide(&refined1);
assert!(check_topology(&refined2).is_ok(), "二次细分后应通过校验");
assert_eq!(
refined2.vertex_count(),
146,
"二次细分顶点数应为 26+72+48=146"
);
}
}