use std::collections::HashMap;
use crate::ids::VertexId;
use crate::storage::MeshStorage;
use crate::traversal::FaceHalfEdges;
pub fn mesh_to_vertex_index_buffers(mesh: &MeshStorage) -> (Vec<[f32; 3]>, Vec<u32>) {
let mut v_index: HashMap<VertexId, u32> = HashMap::new();
let mut vertices: Vec<[f32; 3]> = Vec::with_capacity(mesh.vertex_count());
let mut next_idx = 0u32;
for v_id in mesh.vertex_ids() {
let Some(v) = mesh.get_vertex(v_id) else {
continue;
};
let pos = v.position;
vertices.push([pos[0] as f32, pos[1] as f32, pos[2] as f32]);
v_index.insert(v_id, next_idx);
next_idx += 1;
}
let mut indices: Vec<u32> = Vec::with_capacity(mesh.face_count() * 3);
let mut skipped: 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();
if verts.len() != 3 {
skipped += 1;
continue; }
indices.extend_from_slice(&verts);
}
if skipped > 0 {
log::warn!("[halfedge::mesh_to_vertex_index_buffers] 警告:跳过 {skipped} 个非三角面");
}
(vertices, indices)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::io::build_mesh_from_vertices_and_faces;
use crate::validate::check_topology;
#[test]
fn export_basic_quad() {
let vertices = vec![
[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![[0, 1, 2], [0, 2, 3]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let (vb, ib) = mesh_to_vertex_index_buffers(&mesh);
assert_eq!(vb.len(), 4);
assert_eq!(ib.len(), 6);
assert_eq!(vb[0], [0.0, 0.0, 0.0]);
assert_eq!(vb[1], [1.0, 0.0, 0.0]);
assert_eq!(vb[2], [1.0, 1.0, 0.0]);
assert_eq!(vb[3], [0.0, 1.0, 0.0]);
for i in &ib {
assert!(*i < 4, "索引 {} 越界", i);
}
}
#[test]
fn export_tetrahedron() {
let vertices = vec![
[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![[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let (vb, ib) = mesh_to_vertex_index_buffers(&mesh);
assert_eq!(vb.len(), 4);
assert_eq!(ib.len(), 12); }
#[test]
fn export_preserves_ccw_winding() {
let vertices = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]];
let faces = vec![[0, 1, 2]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let (vb, ib) = mesh_to_vertex_index_buffers(&mesh);
let a = vb[ib[0] as usize];
let b = vb[ib[1] as usize];
let c = vb[ib[2] as usize];
let ab = [b[0] - a[0], b[1] - a[1], b[2] - a[2]];
let ac = [c[0] - a[0], c[1] - a[1], c[2] - a[2]];
let n = [
ab[1] * ac[2] - ab[2] * ac[1],
ab[2] * ac[0] - ab[0] * ac[2],
ab[0] * ac[1] - ab[1] * ac[0],
];
assert!(n[2] > 0.0, "CCW 朝向 +z 应有正法向 z 分量,实际: {:?}", n);
}
#[test]
fn export_empty_mesh() {
let mesh = MeshStorage::new();
let (vb, ib) = mesh_to_vertex_index_buffers(&mesh);
assert!(vb.is_empty());
assert!(ib.is_empty());
}
#[test]
fn export_roundtrip_validation() {
let vertices = vec![
[0.0, 0.0, 0.0],
[2.0, 0.0, 0.0],
[1.0, 2.0, 0.0],
[1.0, 1.0, 1.0],
];
let faces = vec![[0, 1, 2], [0, 2, 3], [0, 3, 1], [1, 3, 2]];
let mesh = build_mesh_from_vertices_and_faces(&vertices, &faces).unwrap();
let (vb, ib) = mesh_to_vertex_index_buffers(&mesh);
let verts_f64: Vec<[f64; 3]> = vb
.iter()
.map(|p| [p[0] as f64, p[1] as f64, p[2] as f64])
.collect();
let faces_u32: Vec<[u32; 3]> = ib.chunks(3).map(|c| [c[0], c[1], c[2]]).collect();
let mesh2 = build_mesh_from_vertices_and_faces(&verts_f64, &faces_u32).unwrap();
assert!(check_topology(&mesh2).is_ok(), "导出后重建的网格应通过校验");
assert_eq!(mesh2.vertex_count(), mesh.vertex_count());
assert_eq!(mesh2.face_count(), mesh.face_count());
}
}