use slotmap::{SecondaryMap, SlotMap};
use crate::Scalar;
use crate::ids::{FaceId, HalfEdgeId, VertexId};
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Vertex {
pub position: [Scalar; 3],
pub halfedge: Option<HalfEdgeId>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct HalfEdge {
pub vertex: VertexId,
pub twin: Option<HalfEdgeId>,
pub next: Option<HalfEdgeId>,
pub prev: Option<HalfEdgeId>,
pub face: Option<FaceId>,
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Face {
pub halfedge: Option<HalfEdgeId>,
}
impl Vertex {
pub fn new(position: [Scalar; 3]) -> Self {
Self {
position,
halfedge: None,
}
}
}
impl Default for Vertex {
fn default() -> Self {
Self::new([0.0; 3])
}
}
impl HalfEdge {
pub fn new(vertex: VertexId) -> Self {
Self {
vertex,
twin: None,
next: None,
prev: None,
face: None,
}
}
}
impl Face {
pub fn new() -> Self {
Self { halfedge: None }
}
}
impl Default for Face {
fn default() -> Self {
Self::new()
}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct MeshStorage {
vertices: SlotMap<VertexId, Vertex>,
halfedges: SlotMap<HalfEdgeId, HalfEdge>,
faces: SlotMap<FaceId, Face>,
#[cfg_attr(feature = "serde", serde(skip))]
positions: Vec<[Scalar; 3]>,
#[cfg_attr(feature = "serde", serde(skip))]
pos_index: SecondaryMap<VertexId, u32>,
}
impl Default for MeshStorage {
fn default() -> Self {
Self::new()
}
}
impl MeshStorage {
pub fn new() -> Self {
Self {
vertices: SlotMap::with_key(),
halfedges: SlotMap::with_key(),
faces: SlotMap::with_key(),
positions: Vec::new(),
pos_index: SecondaryMap::new(),
}
}
pub fn add_vertex(&mut self, vertex: Vertex) -> VertexId {
let pos = vertex.position;
let id = self.vertices.insert(vertex);
let idx = self.positions.len() as u32;
self.positions.push(pos);
self.pos_index.insert(id, idx);
id
}
pub fn add_halfedge(&mut self, halfedge: HalfEdge) -> HalfEdgeId {
self.halfedges.insert(halfedge)
}
pub fn add_face(&mut self, face: Face) -> FaceId {
self.faces.insert(face)
}
pub fn remove_vertex(&mut self, id: VertexId) -> Option<Vertex> {
let removed = self.vertices.remove(id);
if removed.is_some()
&& let Some(idx) = self.pos_index.remove(id)
{
let idx = idx as usize;
self.positions.swap_remove(idx);
if idx < self.positions.len() {
let old_last_idx = self.positions.len() as u32;
for (_k, v) in self.pos_index.iter_mut() {
if *v == old_last_idx {
*v = idx as u32;
break;
}
}
}
}
removed
}
pub fn remove_halfedge(&mut self, id: HalfEdgeId) -> Option<HalfEdge> {
self.halfedges.remove(id)
}
pub fn remove_face(&mut self, id: FaceId) -> Option<Face> {
self.faces.remove(id)
}
pub fn get_vertex(&self, id: VertexId) -> Option<&Vertex> {
self.vertices.get(id)
}
pub fn get_halfedge(&self, id: HalfEdgeId) -> Option<&HalfEdge> {
self.halfedges.get(id)
}
pub fn get_face(&self, id: FaceId) -> Option<&Face> {
self.faces.get(id)
}
pub fn get_vertex_mut(&mut self, id: VertexId) -> Option<&mut Vertex> {
self.vertices.get_mut(id)
}
pub fn get_halfedge_mut(&mut self, id: HalfEdgeId) -> Option<&mut HalfEdge> {
self.halfedges.get_mut(id)
}
pub fn get_face_mut(&mut self, id: FaceId) -> Option<&mut Face> {
self.faces.get_mut(id)
}
pub fn contains_vertex(&self, id: VertexId) -> bool {
self.vertices.contains_key(id)
}
pub fn contains_halfedge(&self, id: HalfEdgeId) -> bool {
self.halfedges.contains_key(id)
}
pub fn contains_face(&self, id: FaceId) -> bool {
self.faces.contains_key(id)
}
pub fn vertex_count(&self) -> usize {
self.vertices.len()
}
pub fn halfedge_count(&self) -> usize {
self.halfedges.len()
}
pub fn face_count(&self) -> usize {
self.faces.len()
}
pub fn vertex_ids(&self) -> impl Iterator<Item = VertexId> + '_ {
self.vertices.keys()
}
pub fn halfedge_ids(&self) -> impl Iterator<Item = HalfEdgeId> + '_ {
self.halfedges.keys()
}
pub fn face_ids(&self) -> impl Iterator<Item = FaceId> + '_ {
self.faces.keys()
}
pub fn vertices(&self) -> impl Iterator<Item = &Vertex> + '_ {
self.vertices.values()
}
pub fn vertices_mut(&mut self) -> impl Iterator<Item = &mut Vertex> + '_ {
self.vertices.values_mut()
}
pub fn halfedges(&self) -> impl Iterator<Item = &HalfEdge> + '_ {
self.halfedges.values()
}
pub fn halfedges_mut(&mut self) -> impl Iterator<Item = &mut HalfEdge> + '_ {
self.halfedges.values_mut()
}
pub fn faces(&self) -> impl Iterator<Item = &Face> + '_ {
self.faces.values()
}
pub fn faces_mut(&mut self) -> impl Iterator<Item = &mut Face> + '_ {
self.faces.values_mut()
}
pub fn is_empty(&self) -> bool {
self.vertex_count() == 0 && self.face_count() == 0
}
#[inline]
pub fn get_position(&self, id: VertexId) -> Option<[Scalar; 3]> {
let idx = self.pos_index.get(id)?;
self.positions.get(*idx as usize).copied()
}
#[inline]
pub fn positions_dense(&self) -> &[[Scalar; 3]] {
&self.positions
}
#[inline]
pub fn position_index(&self, id: VertexId) -> Option<u32> {
self.pos_index.get(id).copied()
}
pub fn set_position(&mut self, id: VertexId, pos: [Scalar; 3]) -> Option<()> {
let vertex = self.vertices.get_mut(id)?;
vertex.position = pos;
if let Some(idx) = self.pos_index.get(id) {
self.positions[*idx as usize] = pos;
}
Some(())
}
pub fn sync_position(&mut self, id: VertexId) {
if let (Some(v), Some(idx)) = (self.vertices.get(id), self.pos_index.get(id)) {
self.positions[*idx as usize] = v.position;
}
}
pub fn rebuild_position_cache(&mut self) {
self.positions.clear();
self.pos_index.clear();
for (id, v) in self.vertices.iter() {
let idx = self.positions.len() as u32;
self.positions.push(v.position);
self.pos_index.insert(id, idx);
}
}
pub fn euler_characteristic(&self) -> i64 {
let v = self.vertex_count() as i64;
let e = (self.halfedge_count() / 2) as i64;
let f = self.face_count() as i64;
v - e + f
}
pub fn genus(&self) -> i64 {
(2 - self.euler_characteristic()) / 2
}
pub fn clear(&mut self) {
self.vertices.clear();
self.halfedges.clear();
self.faces.clear();
self.positions.clear();
self.pos_index.clear();
}
pub fn reserve(&mut self, vertex_cap: usize, halfedge_cap: usize, face_cap: usize) {
self.vertices.reserve(vertex_cap);
self.halfedges.reserve(halfedge_cap);
self.faces.reserve(face_cap);
self.positions.reserve(vertex_cap);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn add_and_get_vertex() {
let mut mesh = MeshStorage::new();
let id = mesh.add_vertex(Vertex::new([1.0, 2.0, 3.0]));
assert!(mesh.contains_vertex(id));
let v = mesh.get_vertex(id).expect("刚插入的顶点必须可查");
assert_eq!(v.position, [1.0, 2.0, 3.0]);
assert!(v.halfedge.is_none());
assert_eq!(mesh.vertex_count(), 1);
}
#[test]
fn add_and_get_halfedge() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0; 3]));
let v1 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let he = mesh.add_halfedge(HalfEdge::new(v1));
mesh.get_vertex_mut(v0).unwrap().halfedge = Some(he);
assert!(mesh.contains_halfedge(he));
let h = mesh.get_halfedge(he).unwrap();
assert_eq!(h.vertex, v1);
assert!(h.twin.is_none());
assert_eq!(mesh.get_vertex(v0).unwrap().halfedge, Some(he));
assert_eq!(mesh.halfedge_count(), 1);
}
#[test]
fn add_and_get_face() {
let mut mesh = MeshStorage::new();
let f = mesh.add_face(Face::new());
assert!(mesh.contains_face(f));
assert!(mesh.get_face(f).unwrap().halfedge.is_none());
assert_eq!(mesh.face_count(), 1);
}
#[test]
fn remove_vertex_invalidates_id() {
let mut mesh = MeshStorage::new();
let id = mesh.add_vertex(Vertex::new([0.0; 3]));
assert!(mesh.contains_vertex(id));
let removed = mesh.remove_vertex(id);
assert!(removed.is_some(), "删除已存在的顶点应返回 Some");
assert_eq!(removed.unwrap().position, [0.0; 3]);
assert!(!mesh.contains_vertex(id));
assert!(mesh.get_vertex(id).is_none());
assert_eq!(mesh.vertex_count(), 0);
assert!(mesh.remove_vertex(id).is_none());
}
#[test]
fn remove_halfedge_invalidates_id() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3]));
let id = mesh.add_halfedge(HalfEdge::new(v));
assert!(mesh.remove_halfedge(id).is_some());
assert!(!mesh.contains_halfedge(id));
assert!(mesh.get_halfedge(id).is_none());
assert!(mesh.remove_halfedge(id).is_none());
}
#[test]
fn remove_face_invalidates_id() {
let mut mesh = MeshStorage::new();
let id = mesh.add_face(Face::new());
assert!(mesh.remove_face(id).is_some());
assert!(!mesh.contains_face(id));
assert!(mesh.get_face(id).is_none());
assert!(mesh.remove_face(id).is_none());
}
#[test]
fn slot_reuse_does_not_resurrect_old_id() {
let mut mesh = MeshStorage::new();
let old_id = mesh.add_vertex(Vertex::new([1.0, 1.0, 1.0]));
mesh.remove_vertex(old_id);
let new_id = mesh.add_vertex(Vertex::new([2.0, 2.0, 2.0]));
assert!(mesh.contains_vertex(new_id));
assert!(!mesh.contains_vertex(old_id));
assert!(mesh.get_vertex(old_id).is_none());
assert_ne!(old_id, new_id);
}
#[test]
fn get_mut_allows_in_place_update() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0; 3]));
let v1 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let he = mesh.add_halfedge(HalfEdge::new(v1));
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(he); h.next = Some(he);
h.prev = Some(he);
let v = mesh.get_vertex_mut(v0).unwrap();
v.halfedge = Some(he);
assert_eq!(mesh.get_vertex(v0).unwrap().halfedge, Some(he));
let h2 = mesh.get_halfedge(he).unwrap();
assert_eq!(h2.twin, Some(he));
assert_eq!(h2.next, Some(he));
assert_eq!(h2.prev, Some(he));
}
#[test]
fn accessing_invalid_id_never_panics() {
let mut mesh = MeshStorage::new();
let id = mesh.add_face(Face::new());
mesh.remove_face(id);
assert!(mesh.get_face(id).is_none());
assert!(mesh.get_face_mut(id).is_none());
assert!(!mesh.contains_face(id));
}
#[test]
fn clear_empties_all_three_slotmaps() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([1.0, 2.0, 3.0]));
mesh.add_vertex(Vertex::new([4.0, 5.0, 6.0]));
let v = mesh.add_vertex(Vertex::new([0.0; 3]));
mesh.add_halfedge(HalfEdge::new(v));
mesh.add_halfedge(HalfEdge::new(v));
mesh.add_face(Face::new());
assert_eq!(mesh.vertex_count(), 3);
assert_eq!(mesh.halfedge_count(), 2);
assert_eq!(mesh.face_count(), 1);
mesh.clear();
assert_eq!(mesh.vertex_count(), 0);
assert_eq!(mesh.halfedge_count(), 0);
assert_eq!(mesh.face_count(), 0);
assert_eq!(mesh.vertex_ids().count(), 0);
assert_eq!(mesh.halfedge_ids().count(), 0);
assert_eq!(mesh.face_ids().count(), 0);
assert_eq!(mesh.euler_characteristic(), 0);
}
#[test]
fn clear_equivalent_to_new() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([1.0; 3]));
mesh.add_face(Face::new());
mesh.clear();
let fresh = MeshStorage::new();
assert_eq!(mesh.vertex_count(), fresh.vertex_count());
assert_eq!(mesh.halfedge_count(), fresh.halfedge_count());
assert_eq!(mesh.face_count(), fresh.face_count());
}
#[test]
fn clear_allows_reuse_after() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([1.0; 3]));
mesh.clear();
let v = mesh.add_vertex(Vertex::new([2.0; 3]));
assert!(mesh.contains_vertex(v));
assert_eq!(mesh.vertex_count(), 1);
}
#[test]
fn reserve_does_not_change_counts() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([0.0; 3]));
mesh.reserve(100, 200, 50);
assert_eq!(mesh.vertex_count(), 1);
assert_eq!(mesh.halfedge_count(), 0);
assert_eq!(mesh.face_count(), 0);
}
#[test]
fn reserve_zero_is_noop() {
let mut mesh = MeshStorage::new();
mesh.reserve(0, 0, 0);
assert_eq!(mesh.vertex_count(), 0);
}
#[test]
fn is_empty_on_new_mesh() {
let mesh = MeshStorage::new();
assert!(mesh.is_empty());
}
#[test]
fn is_empty_after_clear() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([0.0; 3]));
mesh.add_face(Face::new());
assert!(!mesh.is_empty());
mesh.clear();
assert!(mesh.is_empty());
}
#[test]
fn vertices_iter_yields_all_data() {
let mut mesh = MeshStorage::new();
let _v0 = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let _v1 = mesh.add_vertex(Vertex::new([0.0, 1.0, 0.0]));
let _v2 = mesh.add_vertex(Vertex::new([0.0, 0.0, 1.0]));
let verts: Vec<&Vertex> = mesh.vertices().collect();
assert_eq!(verts.len(), 3);
let positions: Vec<[f64; 3]> = verts.iter().map(|v| v.position).collect();
assert!(positions.contains(&[1.0, 0.0, 0.0]));
assert!(positions.contains(&[0.0, 1.0, 0.0]));
assert!(positions.contains(&[0.0, 0.0, 1.0]));
assert_eq!(mesh.vertices().count(), mesh.vertex_ids().count());
}
#[test]
fn vertices_mut_allows_modification() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([1.0; 3]));
mesh.add_vertex(Vertex::new([2.0; 3]));
for v in mesh.vertices_mut() {
v.position = [0.0; 3];
}
for v in mesh.vertices() {
assert_eq!(v.position, [0.0; 3]);
}
}
#[test]
fn halfedges_faces_iters_match_id_counts() {
use crate::test_util::build_icosphere;
let mesh = build_icosphere(1);
assert_eq!(mesh.halfedges().count(), mesh.halfedge_ids().count());
assert_eq!(mesh.faces().count(), mesh.face_ids().count());
assert!(!mesh.is_empty());
}
#[test]
fn empty_iterators_yield_nothing() {
let mesh = MeshStorage::new();
assert_eq!(mesh.vertices().count(), 0);
assert_eq!(mesh.halfedges().count(), 0);
assert_eq!(mesh.faces().count(), 0);
}
#[test]
fn slot_reuse_multiple_cycles() {
let mut mesh = MeshStorage::new();
let mut old_ids = Vec::new();
for _ in 0..5 {
let id = mesh.add_vertex(Vertex::new([1.0; 3]));
old_ids.push(id);
mesh.remove_vertex(id);
}
for id in &old_ids {
assert!(!mesh.contains_vertex(*id));
assert!(mesh.get_vertex(*id).is_none());
}
assert_eq!(mesh.vertex_count(), 0);
let new_id = mesh.add_vertex(Vertex::new([2.0; 3]));
assert!(mesh.contains_vertex(new_id));
for id in &old_ids {
assert_ne!(*id, new_id);
}
}
#[test]
fn slot_reuse_halfedge_and_face() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3]));
let old_he = mesh.add_halfedge(HalfEdge::new(v));
let old_face = mesh.add_face(Face::new());
mesh.remove_halfedge(old_he);
mesh.remove_face(old_face);
let new_he = mesh.add_halfedge(HalfEdge::new(v));
let new_face = mesh.add_face(Face::new());
assert!(!mesh.contains_halfedge(old_he));
assert!(!mesh.contains_face(old_face));
assert!(mesh.contains_halfedge(new_he));
assert!(mesh.contains_face(new_face));
assert_ne!(old_he, new_he);
assert_ne!(old_face, new_face);
}
#[test]
fn vertex_default_is_origin() {
let v = Vertex::default();
assert_eq!(v.position, [0.0; 3]);
assert!(v.halfedge.is_none());
}
#[test]
fn face_default_has_no_halfedge() {
let f = Face::default();
assert!(f.halfedge.is_none());
}
#[test]
fn halfedge_new_has_no_links() {
let v = VertexId::default();
let he = HalfEdge::new(v);
assert_eq!(he.vertex, v);
assert!(he.twin.is_none());
assert!(he.next.is_none());
assert!(he.prev.is_none());
assert!(he.face.is_none());
}
#[test]
fn iteration_count_consistency_after_remove() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([0.0; 3]));
let _v1 = mesh.add_vertex(Vertex::new([1.0; 3]));
let _v2 = mesh.add_vertex(Vertex::new([2.0; 3]));
mesh.remove_vertex(v0);
assert_eq!(mesh.vertex_count(), 2);
assert_eq!(mesh.vertex_ids().count(), 2);
assert_eq!(mesh.vertices().count(), 2);
assert!(!mesh.vertex_ids().any(|id| id == v0));
}
#[test]
fn halfedges_mut_allows_modification() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3]));
mesh.add_halfedge(HalfEdge::new(v));
mesh.add_halfedge(HalfEdge::new(v));
for he in mesh.halfedges_mut() {
he.face = Some(FaceId::default());
}
for he in mesh.halfedges() {
assert_eq!(he.face, Some(FaceId::default()));
}
}
#[test]
fn faces_mut_allows_modification() {
let mut mesh = MeshStorage::new();
let he = mesh.add_halfedge(HalfEdge::new(VertexId::default()));
mesh.add_face(Face::new());
mesh.add_face(Face::new());
for f in mesh.faces_mut() {
f.halfedge = Some(he);
}
for f in mesh.faces() {
assert_eq!(f.halfedge, Some(he));
}
}
#[test]
fn euler_characteristic_of_icosphere_is_two() {
let mesh = crate::test_util::build_icosphere(0);
assert_eq!(mesh.euler_characteristic(), 2);
assert_eq!(mesh.genus(), 0);
}
#[test]
fn euler_characteristic_of_tetrahedron_is_two() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
assert_eq!(mesh.vertex_count(), 4);
assert_eq!(mesh.face_count(), 4);
assert_eq!(mesh.halfedge_count(), 12);
assert_eq!(mesh.euler_characteristic(), 2);
assert_eq!(mesh.genus(), 0);
}
#[test]
fn euler_characteristic_empty_mesh() {
let mesh = MeshStorage::new();
assert_eq!(mesh.euler_characteristic(), 0);
}
#[test]
fn closed_mesh_has_no_boundary() {
let mesh = crate::test_util::build_icosphere(0);
assert!(crate::traversal::is_closed(&mesh));
}
#[test]
fn single_triangle_has_boundary() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
assert!(!crate::traversal::is_closed(&mesh));
}
#[test]
fn boundary_halfedges_have_no_face() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
let boundary_count = mesh
.halfedge_ids()
.filter(|he| mesh.get_halfedge(*he).unwrap().face.is_none())
.count();
assert_eq!(boundary_count, 3, "单三角形应有 3 条边界半边");
}
#[test]
fn closed_mesh_has_no_boundary_halfedges() {
let mesh = crate::test_util::build_icosphere(0);
let boundary_count = mesh
.halfedge_ids()
.filter(|he| mesh.get_halfedge(*he).unwrap().face.is_none())
.count();
assert_eq!(boundary_count, 0);
}
#[test]
fn icosphere_passes_full_validation() {
let mesh = crate::test_util::build_icosphere(1);
assert!(crate::validate::check_topology(&mesh).is_ok());
}
#[test]
fn single_triangle_passes_validation() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
assert!(crate::validate::check_topology(&mesh).is_ok());
}
#[test]
fn two_disconnected_triangles_pass_validation() {
let verts = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[2.0, 0.0, 0.0],
[3.0, 0.0, 0.0],
[2.0, 1.0, 0.0],
];
let faces = vec![[0, 1, 2], [3, 4, 5]];
let mesh = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
assert_eq!(mesh.vertex_count(), 6);
assert_eq!(mesh.face_count(), 2);
assert_eq!(mesh.halfedge_count(), 12);
assert!(crate::validate::check_topology(&mesh).is_ok());
}
#[test]
fn twin_relationship_is_symmetric() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
for he_id in mesh.halfedge_ids() {
let he = mesh.get_halfedge(he_id).unwrap();
if let Some(twin_id) = he.twin {
let twin = mesh.get_halfedge(twin_id).unwrap();
assert_eq!(twin.twin, Some(he_id), "twin 应双向互指");
}
}
}
#[test]
fn next_prev_chain_is_consistent() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
for he_id in mesh.halfedge_ids() {
let he = mesh.get_halfedge(he_id).unwrap();
if let Some(next_id) = he.next {
let next = mesh.get_halfedge(next_id).unwrap();
assert_eq!(next.prev, Some(he_id), "next.prev 应互指");
}
if let Some(prev_id) = he.prev {
let prev = mesh.get_halfedge(prev_id).unwrap();
assert_eq!(prev.next, Some(he_id), "prev.next 应互指");
}
}
}
#[test]
fn face_boundary_next_chain_closes() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
for f_id in mesh.face_ids() {
let face = mesh.get_face(f_id).unwrap();
let start = face.halfedge.unwrap();
let he1 = start;
let he2 = mesh.get_halfedge(he1).unwrap().next.unwrap();
let he3 = mesh.get_halfedge(he2).unwrap().next.unwrap();
let back = mesh.get_halfedge(he3).unwrap().next.unwrap();
assert_eq!(back, he1, "next 链应回到起点");
assert_ne!(he1, he2);
assert_ne!(he2, he3);
assert_ne!(he1, he3);
}
}
#[test]
fn large_icosphere_counts_correct() {
let mesh = crate::test_util::build_icosphere(2);
assert_eq!(mesh.vertex_count(), 162);
assert_eq!(mesh.face_count(), 320);
assert_eq!(mesh.halfedge_count(), 960);
assert_eq!(mesh.euler_characteristic(), 2);
assert!(crate::validate::check_topology(&mesh).is_ok());
}
#[test]
fn mesh_storage_clone_is_equal() {
let verts = 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 = crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).unwrap();
let cloned = mesh.clone();
assert_eq!(cloned.vertex_count(), mesh.vertex_count());
assert_eq!(cloned.halfedge_count(), mesh.halfedge_count());
assert_eq!(cloned.face_count(), mesh.face_count());
assert_eq!(cloned.euler_characteristic(), mesh.euler_characteristic());
}
#[test]
fn mesh_storage_debug_formats() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([1.0; 3]));
let debug = format!("{:?}", mesh);
assert!(debug.contains("MeshStorage"));
}
#[cfg(feature = "serde")]
#[test]
fn serde_roundtrip_preserves_topology() {
let verts = 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 =
crate::io::build_mesh_from_vertices_and_faces(&verts, &faces).expect("valid mesh");
let json = serde_json::to_string(&mesh).expect("serialize");
let deserialized: MeshStorage = serde_json::from_str(&json).expect("deserialize");
assert_eq!(deserialized.vertex_count(), mesh.vertex_count());
assert_eq!(deserialized.halfedge_count(), mesh.halfedge_count());
assert_eq!(deserialized.face_count(), mesh.face_count());
assert_eq!(
deserialized.euler_characteristic(),
mesh.euler_characteristic()
);
let orig_pos: Vec<[f64; 3]> = mesh.vertices().map(|v| v.position).collect();
let new_pos: Vec<[f64; 3]> = deserialized.vertices().map(|v| v.position).collect();
assert_eq!(orig_pos.len(), new_pos.len());
for (a, b) in orig_pos.iter().zip(new_pos.iter()) {
assert_eq!(a, b);
}
}
#[test]
fn soa_cache_get_position_matches_vertex() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([1.0, 2.0, 3.0]));
let v1 = mesh.add_vertex(Vertex::new([4.0, 5.0, 6.0]));
assert_eq!(mesh.get_position(v0), Some([1.0, 2.0, 3.0]));
assert_eq!(mesh.get_position(v1), Some([4.0, 5.0, 6.0]));
let fake = VertexId::default();
assert_eq!(mesh.get_position(fake), None);
}
#[test]
fn soa_cache_positions_dense_length_matches() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([1.0; 3]));
mesh.add_vertex(Vertex::new([2.0; 3]));
mesh.add_vertex(Vertex::new([3.0; 3]));
let dense = mesh.positions_dense();
assert_eq!(dense.len(), 3);
assert_eq!(dense[0], [1.0; 3]);
assert_eq!(dense[1], [2.0; 3]);
assert_eq!(dense[2], [3.0; 3]);
}
#[test]
fn soa_cache_position_index_round_trip() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([10.0, 0.0, 0.0]));
let v1 = mesh.add_vertex(Vertex::new([0.0, 20.0, 0.0]));
let dense = mesh.positions_dense();
let idx0 = mesh.position_index(v0).expect("v0 应有索引");
let idx1 = mesh.position_index(v1).expect("v1 应有索引");
assert_eq!(dense[idx0 as usize], [10.0, 0.0, 0.0]);
assert_eq!(dense[idx1 as usize], [0.0, 20.0, 0.0]);
}
#[test]
fn soa_cache_set_position_syncs_both() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3]));
mesh.set_position(v, [7.0, 8.0, 9.0]);
assert_eq!(mesh.get_vertex(v).unwrap().position, [7.0, 8.0, 9.0]);
assert_eq!(mesh.get_position(v), Some([7.0, 8.0, 9.0]));
let idx = mesh.position_index(v).unwrap();
assert_eq!(mesh.positions_dense()[idx as usize], [7.0, 8.0, 9.0]);
}
#[test]
fn soa_cache_remove_preserves_dense_layout() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([1.0; 3]));
let _v1 = mesh.add_vertex(Vertex::new([2.0; 3]));
let v2 = mesh.add_vertex(Vertex::new([3.0; 3]));
mesh.remove_vertex(_v1);
assert_eq!(mesh.get_position(v0), Some([1.0; 3]));
assert_eq!(mesh.get_position(v2), Some([3.0; 3]));
assert_eq!(mesh.positions_dense().len(), 2);
}
#[test]
fn soa_cache_sync_position_after_get_vertex_mut() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3]));
mesh.get_vertex_mut(v).unwrap().position = [5.0, 5.0, 5.0];
assert_eq!(mesh.get_position(v), Some([0.0; 3]));
mesh.sync_position(v);
assert_eq!(mesh.get_position(v), Some([5.0, 5.0, 5.0]));
}
#[test]
fn soa_cache_rebuild_from_master() {
let mut mesh = MeshStorage::new();
let v0 = mesh.add_vertex(Vertex::new([1.0; 3]));
let v1 = mesh.add_vertex(Vertex::new([2.0; 3]));
mesh.get_vertex_mut(v0).unwrap().position = [9.0; 3];
mesh.get_vertex_mut(v1).unwrap().position = [8.0; 3];
mesh.rebuild_position_cache();
assert_eq!(mesh.get_position(v0), Some([9.0; 3]));
assert_eq!(mesh.get_position(v1), Some([8.0; 3]));
assert_eq!(mesh.positions_dense().len(), 2);
}
#[test]
fn soa_cache_clear_empties_cache() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([1.0; 3]));
mesh.add_vertex(Vertex::new([2.0; 3]));
assert_eq!(mesh.positions_dense().len(), 2);
mesh.clear();
assert_eq!(mesh.positions_dense().len(), 0);
}
#[test]
fn soa_cache_empty_mesh_no_panic() {
let mesh = MeshStorage::new();
assert_eq!(mesh.positions_dense().len(), 0);
assert_eq!(mesh.positions_dense(), &[] as &[[f64; 3]]);
}
#[test]
fn soa_cache_icosphere_consistency() {
use crate::test_util::build_icosphere;
let mesh = build_icosphere(1);
for v_id in mesh.vertex_ids() {
let from_vertex = mesh.get_vertex(v_id).unwrap().position;
let from_cache = mesh.get_position(v_id).unwrap();
assert_eq!(from_vertex, from_cache, "顶点 {:?} 缓存不一致", v_id);
}
}
}