use crate::ids::{EdgeId, FaceId, HalfEdgeId, VertexId};
use crate::storage::MeshStorage;
pub fn collect_outgoing_halfedges(mesh: &MeshStorage, v: VertexId) -> Vec<HalfEdgeId> {
let mut buf = Vec::new();
collect_outgoing_halfedges_into(mesh, v, &mut buf);
buf
}
pub fn collect_outgoing_halfedges_into(mesh: &MeshStorage, v: VertexId, buf: &mut Vec<HalfEdgeId>) {
buf.clear();
let start = match mesh.get_vertex(v).and_then(|vt| vt.halfedge) {
Some(s) => s,
None => return,
};
let max_iter = mesh.halfedge_count() + 1;
buf.push(start);
let mut he = start;
let mut closed = false;
for _ in 0..max_iter {
let next = mesh
.get_halfedge(he)
.and_then(|h| h.prev)
.and_then(|p| mesh.get_halfedge(p))
.and_then(|h| h.twin);
match next {
Some(n) if n != start => {
buf.push(n);
he = n;
}
Some(_) => {
closed = true; break;
}
None => break, }
}
if closed {
return;
}
let mut backward = Vec::new();
let mut he = start;
for _ in 0..max_iter {
let prev = mesh
.get_halfedge(he)
.and_then(|h| h.twin)
.and_then(|t| mesh.get_halfedge(t))
.and_then(|h| h.next);
match prev {
Some(p) => {
backward.push(p);
he = p;
}
None => break,
}
}
backward.reverse();
buf.splice(0..0, backward);
}
pub fn collect_face_halfedges(mesh: &MeshStorage, f: FaceId) -> Vec<HalfEdgeId> {
let mut buf = Vec::new();
collect_face_halfedges_into(mesh, f, &mut buf);
buf
}
pub fn collect_face_halfedges_into(mesh: &MeshStorage, f: FaceId, buf: &mut Vec<HalfEdgeId>) {
buf.clear();
let start = match mesh.get_face(f).and_then(|ft| ft.halfedge) {
Some(s) => s,
None => return,
};
let max_iter = mesh.halfedge_count() + 1;
buf.push(start);
let mut he = start;
for _ in 0..max_iter {
let next = mesh.get_halfedge(he).and_then(|h| h.next);
match next {
Some(n) if n != start => {
buf.push(n);
he = n;
}
Some(_) => break, None => break, }
}
}
pub struct VertexRing {
halfedges: Vec<HalfEdgeId>,
cursor: usize,
}
impl VertexRing {
pub fn new(mesh: &MeshStorage, v: VertexId) -> Self {
Self {
halfedges: collect_outgoing_halfedges(mesh, v),
cursor: 0,
}
}
}
impl Iterator for VertexRing {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<Self::Item> {
match self.halfedges.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.halfedges.len().saturating_sub(self.cursor);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for VertexRing {}
pub struct VertexAdjacentVerts {
verts: Vec<VertexId>,
cursor: usize,
}
impl VertexAdjacentVerts {
pub fn new(mesh: &MeshStorage, v: VertexId) -> Self {
let verts = collect_outgoing_halfedges(mesh, v)
.into_iter()
.filter_map(|he| mesh.get_halfedge(he).map(|h| h.vertex))
.collect();
Self { verts, cursor: 0 }
}
}
impl Iterator for VertexAdjacentVerts {
type Item = VertexId;
fn next(&mut self) -> Option<Self::Item> {
match self.verts.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.verts.len().saturating_sub(self.cursor);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for VertexAdjacentVerts {}
pub struct VertexAdjacentFaces {
faces: Vec<FaceId>,
cursor: usize,
}
impl VertexAdjacentFaces {
pub fn new(mesh: &MeshStorage, v: VertexId) -> Self {
let faces = collect_outgoing_halfedges(mesh, v)
.into_iter()
.filter_map(|he| mesh.get_halfedge(he).and_then(|h| h.face))
.collect();
Self { faces, cursor: 0 }
}
}
impl Iterator for VertexAdjacentFaces {
type Item = FaceId;
fn next(&mut self) -> Option<Self::Item> {
match self.faces.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.faces.len().saturating_sub(self.cursor);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for VertexAdjacentFaces {}
pub struct FaceHalfEdges {
halfedges: Vec<HalfEdgeId>,
cursor: usize,
}
impl FaceHalfEdges {
pub fn new(mesh: &MeshStorage, f: FaceId) -> Self {
Self {
halfedges: collect_face_halfedges(mesh, f),
cursor: 0,
}
}
}
impl Iterator for FaceHalfEdges {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<Self::Item> {
match self.halfedges.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.halfedges.len().saturating_sub(self.cursor);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for FaceHalfEdges {}
pub struct FaceVertices {
verts: Vec<VertexId>,
cursor: usize,
}
impl FaceVertices {
pub fn new(mesh: &MeshStorage, f: FaceId) -> Self {
let verts = collect_face_halfedges(mesh, f)
.into_iter()
.filter_map(|he| mesh.get_halfedge(he).map(|h| h.vertex))
.collect();
Self { verts, cursor: 0 }
}
}
impl Iterator for FaceVertices {
type Item = VertexId;
fn next(&mut self) -> Option<Self::Item> {
match self.verts.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.verts.len().saturating_sub(self.cursor);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for FaceVertices {}
fn probe_cw_end(mesh: &MeshStorage, start: HalfEdgeId) -> (HalfEdgeId, bool) {
let mut cw_end = start;
let max_iter = mesh.halfedge_count() + 1;
for _ in 0..max_iter {
let next_cw = mesh
.get_halfedge(cw_end)
.and_then(|h| h.twin)
.and_then(|t| mesh.get_halfedge(t))
.and_then(|h| h.next);
match next_cw {
Some(n) if n == start => return (cw_end, true), Some(n) => cw_end = n,
None => return (cw_end, false), }
}
(cw_end, true)
}
pub struct VertexRingLazy<'a> {
mesh: &'a MeshStorage,
start: HalfEdgeId,
current: Option<HalfEdgeId>,
closed: bool,
}
impl<'a> VertexRingLazy<'a> {
pub fn new(mesh: &'a MeshStorage, v: VertexId) -> Self {
let start = match mesh.get_vertex(v).and_then(|vt| vt.halfedge) {
Some(s) => s,
None => {
return Self {
mesh,
start: HalfEdgeId::default(),
current: None,
closed: false,
};
}
};
let (cw_end, closed) = probe_cw_end(mesh, start);
let begin = if closed { start } else { cw_end };
Self {
mesh,
start: begin,
current: Some(begin),
closed,
}
}
}
impl<'a> Iterator for VertexRingLazy<'a> {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<HalfEdgeId> {
let cur = self.current?;
let result = cur;
let next_ccw = self
.mesh
.get_halfedge(cur)
.and_then(|h| h.prev)
.and_then(|p| self.mesh.get_halfedge(p))
.and_then(|h| h.twin);
match next_ccw {
Some(n) if self.closed && n == self.start => self.current = None,
Some(n) => self.current = Some(n),
None => self.current = None, }
Some(result)
}
}
impl<'a> std::iter::FusedIterator for VertexRingLazy<'a> {}
pub struct VertexAdjacentVertsLazy<'a> {
mesh: &'a MeshStorage,
ring: VertexRingLazy<'a>,
}
impl<'a> VertexAdjacentVertsLazy<'a> {
pub fn new(mesh: &'a MeshStorage, v: VertexId) -> Self {
Self {
mesh,
ring: VertexRingLazy::new(mesh, v),
}
}
}
impl<'a> Iterator for VertexAdjacentVertsLazy<'a> {
type Item = VertexId;
fn next(&mut self) -> Option<VertexId> {
let mesh = self.mesh;
for he in &mut self.ring {
if let Some(v) = mesh.get_halfedge(he).map(|h| h.vertex) {
return Some(v);
}
}
None
}
}
impl<'a> std::iter::FusedIterator for VertexAdjacentVertsLazy<'a> {}
pub struct VertexAdjacentFacesLazy<'a> {
mesh: &'a MeshStorage,
ring: VertexRingLazy<'a>,
}
impl<'a> VertexAdjacentFacesLazy<'a> {
pub fn new(mesh: &'a MeshStorage, v: VertexId) -> Self {
Self {
mesh,
ring: VertexRingLazy::new(mesh, v),
}
}
}
impl<'a> Iterator for VertexAdjacentFacesLazy<'a> {
type Item = FaceId;
fn next(&mut self) -> Option<FaceId> {
let mesh = self.mesh;
for he in &mut self.ring {
if let Some(f) = mesh.get_halfedge(he).and_then(|h| h.face) {
return Some(f);
}
}
None
}
}
impl<'a> std::iter::FusedIterator for VertexAdjacentFacesLazy<'a> {}
pub struct FaceHalfEdgesLazy<'a> {
mesh: &'a MeshStorage,
start: HalfEdgeId,
current: Option<HalfEdgeId>,
}
impl<'a> FaceHalfEdgesLazy<'a> {
pub fn new(mesh: &'a MeshStorage, f: FaceId) -> Self {
let start = match mesh.get_face(f).and_then(|ft| ft.halfedge) {
Some(s) => s,
None => {
return Self {
mesh,
start: HalfEdgeId::default(),
current: None,
};
}
};
Self {
mesh,
start,
current: Some(start),
}
}
}
impl<'a> Iterator for FaceHalfEdgesLazy<'a> {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<HalfEdgeId> {
let cur = self.current?;
let result = cur;
let next = self.mesh.get_halfedge(cur).and_then(|h| h.next);
match next {
Some(n) if n == self.start => self.current = None, Some(n) => self.current = Some(n),
None => self.current = None, }
Some(result)
}
}
impl<'a> std::iter::FusedIterator for FaceHalfEdgesLazy<'a> {}
pub struct FaceVerticesLazy<'a> {
mesh: &'a MeshStorage,
ring: FaceHalfEdgesLazy<'a>,
}
impl<'a> FaceVerticesLazy<'a> {
pub fn new(mesh: &'a MeshStorage, f: FaceId) -> Self {
Self {
mesh,
ring: FaceHalfEdgesLazy::new(mesh, f),
}
}
}
impl<'a> Iterator for FaceVerticesLazy<'a> {
type Item = VertexId;
fn next(&mut self) -> Option<VertexId> {
let mesh = self.mesh;
for he in &mut self.ring {
if let Some(v) = mesh.get_halfedge(he).map(|h| h.vertex) {
return Some(v);
}
}
None
}
}
impl<'a> std::iter::FusedIterator for FaceVerticesLazy<'a> {}
impl VertexRing {
pub fn lazy(mesh: &MeshStorage, v: VertexId) -> VertexRingLazy<'_> {
VertexRingLazy::new(mesh, v)
}
}
impl VertexAdjacentVerts {
pub fn lazy(mesh: &MeshStorage, v: VertexId) -> VertexAdjacentVertsLazy<'_> {
VertexAdjacentVertsLazy::new(mesh, v)
}
}
impl VertexAdjacentFaces {
pub fn lazy(mesh: &MeshStorage, v: VertexId) -> VertexAdjacentFacesLazy<'_> {
VertexAdjacentFacesLazy::new(mesh, v)
}
}
impl FaceHalfEdges {
pub fn lazy(mesh: &MeshStorage, f: FaceId) -> FaceHalfEdgesLazy<'_> {
FaceHalfEdgesLazy::new(mesh, f)
}
}
impl FaceVertices {
pub fn lazy(mesh: &MeshStorage, f: FaceId) -> FaceVerticesLazy<'_> {
FaceVerticesLazy::new(mesh, f)
}
}
macro_rules! impl_eager_iterator_stats {
($iter:ty, $field:ident) => {
impl $iter {
pub fn count_hint(&self) -> Option<usize> {
let remaining = self.$field.len().saturating_sub(self.cursor);
Some(remaining)
}
pub fn is_empty(&self) -> bool {
self.cursor >= self.$field.len()
}
}
};
}
impl_eager_iterator_stats!(VertexRing, halfedges);
impl_eager_iterator_stats!(VertexAdjacentVerts, verts);
impl_eager_iterator_stats!(VertexAdjacentFaces, faces);
impl_eager_iterator_stats!(FaceHalfEdges, halfedges);
impl_eager_iterator_stats!(FaceVertices, verts);
macro_rules! impl_lazy_iterator_stats {
($iter:ty) => {
impl $iter {
pub fn count_hint(&self) -> Option<usize> {
None
}
pub fn is_empty(&self) -> bool {
self.current.is_none()
}
}
};
}
impl_lazy_iterator_stats!(VertexRingLazy<'_>);
impl_lazy_iterator_stats!(FaceHalfEdgesLazy<'_>);
macro_rules! impl_lazy_iterator_stats_delegated {
($iter:ty) => {
impl $iter {
pub fn count_hint(&self) -> Option<usize> {
None
}
pub fn is_empty(&self) -> bool {
self.ring.is_empty()
}
}
};
}
impl_lazy_iterator_stats_delegated!(VertexAdjacentVertsLazy<'_>);
impl_lazy_iterator_stats_delegated!(VertexAdjacentFacesLazy<'_>);
impl_lazy_iterator_stats_delegated!(FaceVerticesLazy<'_>);
impl MeshStorage {
pub fn max_vertex_valence(&self) -> usize {
self.vertex_ids()
.map(|v| VertexRing::new(self, v).count())
.max()
.unwrap_or(0)
}
pub fn edge_count(&self) -> usize {
self.halfedge_count() / 2
}
pub fn edge_ids(&self) -> EdgeIter<'_> {
EdgeIter::new(self)
}
}
pub struct EdgeIter<'a> {
edges: Vec<EdgeId>,
cursor: usize,
_phantom: std::marker::PhantomData<&'a MeshStorage>,
}
impl<'a> EdgeIter<'a> {
pub fn new(mesh: &'a MeshStorage) -> Self {
use std::collections::HashSet;
let mut seen = HashSet::with_capacity(mesh.edge_count());
let mut edges = Vec::with_capacity(mesh.edge_count());
for he in mesh.halfedge_ids() {
let canonical = match mesh.get_halfedge(he).and_then(|h| h.twin) {
Some(twin) => {
if he < twin {
he
} else {
twin
}
}
None => he,
};
if seen.insert(canonical) {
edges.push(EdgeId::from_halfedge(canonical));
}
}
Self {
edges,
cursor: 0,
_phantom: std::marker::PhantomData,
}
}
}
impl Iterator for EdgeIter<'_> {
type Item = EdgeId;
fn next(&mut self) -> Option<Self::Item> {
match self.edges.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.edges.len().saturating_sub(self.cursor);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for EdgeIter<'_> {}
impl EdgeId {
pub(crate) fn from_halfedge(he: HalfEdgeId) -> Self {
Self(he)
}
}
pub struct VertexAdjacentEdges {
edges: Vec<EdgeId>,
cursor: usize,
}
impl VertexAdjacentEdges {
pub fn new(mesh: &MeshStorage, v: VertexId) -> Self {
use std::collections::HashSet;
let mut seen = HashSet::new();
let mut edges = Vec::new();
for he in collect_outgoing_halfedges(mesh, v) {
let h = mesh.get_halfedge(he).expect("halfedge exists in mesh");
let canonical = match h.twin {
Some(twin) => {
if he < twin {
he
} else {
twin
}
}
None => he,
};
if seen.insert(canonical) {
edges.push(EdgeId::from_halfedge(canonical));
}
}
Self { edges, cursor: 0 }
}
}
impl Iterator for VertexAdjacentEdges {
type Item = EdgeId;
fn next(&mut self) -> Option<Self::Item> {
match self.edges.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let rem = self.edges.len().saturating_sub(self.cursor);
(rem, Some(rem))
}
}
impl ExactSizeIterator for VertexAdjacentEdges {}
pub fn vertex_two_ring(mesh: &MeshStorage, v: VertexId) -> Vec<VertexId> {
use std::collections::HashSet;
let neighbors: HashSet<VertexId> = VertexAdjacentVerts::new(mesh, v).collect();
let mut result = HashSet::new();
for n in &neighbors {
for n2 in VertexAdjacentVerts::new(mesh, *n) {
if n2 != v && !neighbors.contains(&n2) {
result.insert(n2);
}
}
}
let mut out: Vec<_> = result.into_iter().collect();
out.sort();
out
}
pub fn vertex_k_ring(mesh: &MeshStorage, v: VertexId, k: usize) -> Vec<VertexId> {
if k == 0 {
return vec![v];
}
use std::collections::{HashMap, VecDeque};
let mut dist: HashMap<VertexId, usize> = HashMap::new();
let mut queue = VecDeque::new();
dist.insert(v, 0);
queue.push_back(v);
while let Some(cur) = queue.pop_front() {
let d = dist[&cur];
if d >= k {
continue;
}
for n in VertexAdjacentVerts::new(mesh, cur) {
if let std::collections::hash_map::Entry::Vacant(e) = dist.entry(n) {
e.insert(d + 1);
queue.push_back(n);
}
}
}
let mut result: Vec<_> = dist
.into_iter()
.filter(|(_, d)| *d == k)
.map(|(id, _)| id)
.collect();
result.sort();
result
}
pub fn vertex_k_disk(mesh: &MeshStorage, v: VertexId, k: usize) -> Vec<VertexId> {
if k == 0 {
return vec![v];
}
use std::collections::{HashMap, VecDeque};
let mut dist: HashMap<VertexId, usize> = HashMap::new();
let mut queue = VecDeque::new();
dist.insert(v, 0);
queue.push_back(v);
while let Some(cur) = queue.pop_front() {
let d = dist[&cur];
if d >= k {
continue;
}
for n in VertexAdjacentVerts::new(mesh, cur) {
if let std::collections::hash_map::Entry::Vacant(e) = dist.entry(n) {
e.insert(d + 1);
queue.push_back(n);
}
}
}
let mut result: Vec<_> = dist.into_keys().collect();
result.sort();
result
}
pub fn is_closed(mesh: &MeshStorage) -> bool {
!mesh
.halfedge_ids()
.any(|he| mesh.get_halfedge(he).is_some_and(|h| h.face.is_none()))
}
pub fn next_boundary_halfedge(mesh: &MeshStorage, he: HalfEdgeId) -> Option<HalfEdgeId> {
let h = mesh.get_halfedge(he)?;
if h.face.is_some() {
return None;
}
let v = h.vertex;
let start = mesh.get_vertex(v)?.halfedge?;
let max_iter = mesh.halfedge_count() + 1;
let mut cur = start;
for _ in 0..max_iter {
if mesh.get_halfedge(cur).is_some_and(|ch| ch.face.is_none()) && cur != he {
return Some(cur);
}
let next = mesh
.get_halfedge(cur)
.and_then(|ch| ch.prev)
.and_then(|p| mesh.get_halfedge(p))
.and_then(|ph| ph.twin);
match next {
Some(n) if n == start => break, Some(n) => cur = n,
None => break,
}
}
None
}
pub struct BoundaryLoop {
halfedges: Vec<HalfEdgeId>,
cursor: usize,
}
impl BoundaryLoop {
pub fn new(mesh: &MeshStorage, start: HalfEdgeId) -> Self {
let mut halfedges = Vec::new();
if mesh.get_halfedge(start).is_none_or(|h| h.face.is_some()) {
return Self {
halfedges,
cursor: 0,
};
}
halfedges.push(start);
let max_iter = mesh.halfedge_count() + 1;
let mut cur = start;
for _ in 0..max_iter {
match next_boundary_halfedge(mesh, cur) {
Some(n) if n == start => break, Some(n) => {
halfedges.push(n);
cur = n;
}
None => break,
}
}
Self {
halfedges,
cursor: 0,
}
}
}
impl Iterator for BoundaryLoop {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<Self::Item> {
match self.halfedges.get(self.cursor) {
Some(&id) => {
self.cursor += 1;
Some(id)
}
None => None,
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
let remaining = self.halfedges.len().saturating_sub(self.cursor);
(remaining, Some(remaining))
}
}
impl ExactSizeIterator for BoundaryLoop {}
pub struct BoundaryLoopLazy<'a> {
mesh: &'a MeshStorage,
start: HalfEdgeId,
current: Option<HalfEdgeId>,
}
impl<'a> BoundaryLoopLazy<'a> {
pub fn new(mesh: &'a MeshStorage, start: HalfEdgeId) -> Self {
let current = mesh
.get_halfedge(start)
.filter(|h| h.face.is_none())
.map(|_| start);
Self {
mesh,
start,
current,
}
}
}
impl Iterator for BoundaryLoopLazy<'_> {
type Item = HalfEdgeId;
fn next(&mut self) -> Option<Self::Item> {
let cur = self.current?;
let next = next_boundary_halfedge(self.mesh, cur);
self.current = match next {
Some(n) if n == self.start => None, n => n,
};
Some(cur)
}
}
impl std::iter::FusedIterator for BoundaryLoopLazy<'_> {}
pub fn boundary_loops(mesh: &MeshStorage) -> Vec<Vec<HalfEdgeId>> {
use std::collections::HashSet;
let mut visited = HashSet::with_capacity(mesh.halfedge_count());
let mut loops = Vec::new();
for he in mesh.halfedge_ids() {
if visited.contains(&he) {
continue;
}
let is_boundary = mesh.get_halfedge(he).is_some_and(|h| h.face.is_none());
if !is_boundary {
continue;
}
let boundary_he: Vec<_> = BoundaryLoop::new(mesh, he).collect();
for &h in &boundary_he {
visited.insert(h);
}
if !boundary_he.is_empty() {
loops.push(boundary_he);
}
}
loops
}
#[inline]
pub fn is_boundary_edge(mesh: &MeshStorage, he: HalfEdgeId) -> bool {
let h = match mesh.get_halfedge(he) {
Some(h) => h,
None => return false,
};
if h.face.is_none() {
return true;
}
match h.twin {
None => true,
Some(t) => mesh
.get_halfedge(t)
.map(|th| th.face.is_none())
.unwrap_or(true),
}
}
pub fn is_boundary_vertex(mesh: &MeshStorage, v: VertexId) -> bool {
VertexRingLazy::new(mesh, v).any(|he| is_boundary_edge(mesh, he))
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::{Face, HalfEdge, MeshStorage, Vertex};
fn build_triangle() -> (MeshStorage, [VertexId; 3], [HalfEdgeId; 6], FaceId) {
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([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(v0)); let t0 = mesh.add_halfedge(HalfEdge::new(v0)); let t1 = mesh.add_halfedge(HalfEdge::new(v1)); let t2 = mesh.add_halfedge(HalfEdge::new(v2));
let f = mesh.add_face(Face::new());
for (he, twin, next, prev) in [(h0, t0, h1, h2), (h1, t1, h2, h0), (h2, t2, h0, h1)] {
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 [(t0, h0), (t1, h1), (t2, h2)] {
mesh.get_halfedge_mut(t).unwrap().twin = Some(he);
}
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_face_mut(f).unwrap().halfedge = Some(h0);
(mesh, [v0, v1, v2], [h0, h1, h2, t0, t1, t2], f)
}
#[test]
fn face_halfedges_in_ccw_order() {
let (mesh, _v, he, f) = build_triangle();
let [h0, h1, h2, _, _, _] = he;
let collected: Vec<_> = FaceHalfEdges::new(&mesh, f).collect();
assert_eq!(collected, vec![h0, h1, h2]);
}
#[test]
fn face_vertices_in_ccw_order() {
let (mesh, v, _he, f) = build_triangle();
let [v0, v1, v2] = v;
let collected: Vec<_> = FaceVertices::new(&mesh, f).collect();
assert_eq!(collected, vec![v1, v2, v0]);
}
#[test]
fn vertex_ring_around_v0() {
let (mesh, v, he, _f) = build_triangle();
let [v0, _v1, _v2] = v;
let [h0, _h1, _h2, _t0, _t1, t2] = he;
let collected: Vec<_> = VertexRing::new(&mesh, v0).collect();
assert_eq!(collected, vec![h0, t2]);
}
#[test]
fn vertex_ring_around_v1() {
let (mesh, v, he, _f) = build_triangle();
let [_v0, v1, _v2] = v;
let [_h0, h1, _h2, t0, _t1, _t2] = he;
let collected: Vec<_> = VertexRing::new(&mesh, v1).collect();
assert_eq!(collected, vec![h1, t0]);
}
#[test]
fn vertex_adjacent_verts_around_v0() {
let (mesh, v, _he, _f) = build_triangle();
let [v0, v1, v2] = v;
let collected: Vec<_> = VertexAdjacentVerts::new(&mesh, v0).collect();
assert_eq!(collected, vec![v1, v2]);
}
#[test]
fn vertex_adjacent_faces_around_v0() {
let (mesh, v, _he, f) = build_triangle();
let [v0, _v1, _v2] = v;
let collected: Vec<_> = VertexAdjacentFaces::new(&mesh, v0).collect();
assert_eq!(collected, vec![f]);
}
#[test]
fn boundary_detection_for_single_triangle() {
let (mesh, v, he, _f) = build_triangle();
let [v0, v1, v2] = v;
let [h0, h1, h2, t0, t1, t2] = he;
for he in [h0, h1, h2, t0, t1, t2] {
assert!(is_boundary_edge(&mesh, he), "半边 {:?} 应为边界边", he);
}
for v in [v0, v1, v2] {
assert!(is_boundary_vertex(&mesh, v), "顶点 {:?} 应为边界顶点", v);
}
}
#[test]
fn iterator_does_not_hold_borrow() {
let (mut mesh, v, _he, _f) = build_triangle();
let [v0, _v1, _v2] = v;
let iter = VertexRing::new(&mesh, v0);
{
let _mesh_mut = &mut mesh;
}
assert_eq!(iter.count(), 2);
}
#[test]
fn can_modify_mesh_during_iteration() {
let (mut mesh, v, _he, _f) = build_triangle();
let [v0, _v1, _v2] = v;
let ids: Vec<_> = VertexRing::new(&mesh, v0).collect();
for he in ids {
mesh.get_halfedge_mut(he).unwrap().face = None;
}
for he in VertexRing::new(&mesh, v0) {
assert!(mesh.get_halfedge(he).unwrap().face.is_none());
}
}
#[test]
fn invalid_or_isolated_ids_yield_empty() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3])); let f = mesh.add_face(Face::new());
assert_eq!(VertexRing::new(&mesh, v).count(), 0);
assert_eq!(VertexAdjacentVerts::new(&mesh, v).count(), 0);
assert_eq!(VertexAdjacentFaces::new(&mesh, v).count(), 0);
assert_eq!(FaceHalfEdges::new(&mesh, f).count(), 0);
assert_eq!(FaceVertices::new(&mesh, f).count(), 0);
let removed_v = mesh.add_vertex(Vertex::new([1.0; 3]));
mesh.remove_vertex(removed_v);
assert_eq!(VertexRing::new(&mesh, removed_v).count(), 0);
assert!(!is_boundary_vertex(&mesh, removed_v));
let removed_he = mesh.add_halfedge(HalfEdge::new(v));
mesh.remove_halfedge(removed_he);
assert!(!is_boundary_edge(&mesh, removed_he));
}
fn build_two_triangles() -> (MeshStorage, [VertexId; 4], FaceId, FaceId) {
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([0.0, 1.0, 0.0]));
let v3 = mesh.add_vertex(Vertex::new([1.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(v0)); let g0 = mesh.add_halfedge(HalfEdge::new(v0)); let g1 = mesh.add_halfedge(HalfEdge::new(v3)); let g2 = mesh.add_halfedge(HalfEdge::new(v1)); let t1 = mesh.add_halfedge(HalfEdge::new(v1)); let t2 = mesh.add_halfedge(HalfEdge::new(v2)); let t_g1 = mesh.add_halfedge(HalfEdge::new(v0)); let t_g2 = mesh.add_halfedge(HalfEdge::new(v3));
let f1 = mesh.add_face(Face::new());
let f2 = mesh.add_face(Face::new());
for (he, twin, next, prev) in [(h0, g0, h1, h2), (h1, t1, h2, h0), (h2, t2, h0, h1)] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(f1);
}
for (he, twin, next, prev) in [(g0, h0, g1, g2), (g1, t_g1, g2, g0), (g2, t_g2, g0, g1)] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(f2);
}
for (t, he) in [(t1, h1), (t2, h2), (t_g1, g1), (t_g2, g2)] {
mesh.get_halfedge_mut(t).unwrap().twin = Some(he);
}
mesh.get_vertex_mut(v0).unwrap().halfedge = Some(h0);
mesh.get_vertex_mut(v1).unwrap().halfedge = Some(g0);
mesh.get_vertex_mut(v2).unwrap().halfedge = Some(h1);
mesh.get_vertex_mut(v3).unwrap().halfedge = Some(g1);
mesh.get_face_mut(f1).unwrap().halfedge = Some(h0);
mesh.get_face_mut(f2).unwrap().halfedge = Some(g0);
(mesh, [v0, v1, v2, v3], f1, f2)
}
#[test]
fn two_triangle_corner_vertices_are_still_boundary() {
let (mesh, v, _f1, _f2) = build_two_triangles();
let [v0, v1, v2, v3] = v;
assert!(
is_boundary_vertex(&mesh, v0),
"v0 关联到外边界边 → 边界顶点"
);
assert!(
is_boundary_vertex(&mesh, v1),
"v1 关联到外边界边 → 边界顶点"
);
assert!(is_boundary_vertex(&mesh, v2), "v2 应为边界顶点");
assert!(is_boundary_vertex(&mesh, v3), "v3 应为边界顶点");
}
#[test]
fn two_triangle_shared_vertex_has_three_outgoing() {
let (mesh, v, _f1, _f2) = build_two_triangles();
let [v0, _v1, _v2, _v3] = v;
let ring: Vec<_> = VertexRing::new(&mesh, v0).collect();
assert_eq!(ring.len(), 3, "v0 周围应有 3 条 outgoing 半边");
let mut sorted = ring.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), 3, "三条 outgoing 半边应互不相同");
}
#[test]
fn two_triangle_v0_has_two_adjacent_faces() {
let (mesh, v, f1, f2) = build_two_triangles();
let [v0, _v1, _v2, _v3] = v;
let mut faces: Vec<_> = VertexAdjacentFaces::new(&mesh, v0).collect();
faces.sort();
let mut expected = vec![f1, f2];
expected.sort();
assert_eq!(faces, expected);
}
#[test]
fn shared_edge_is_not_boundary() {
let (mesh, v, _f1, _f2) = build_two_triangles();
let [v0, v1, _v2, _v3] = v;
let shared = VertexRing::new(&mesh, v0)
.find(|he| mesh.get_halfedge(*he).unwrap().vertex == v1)
.expect("v0→v1 的半边必须存在");
assert!(!is_boundary_edge(&mesh, shared), "共享边不应是边界边");
}
fn build_closed_fan() -> (MeshStorage, VertexId, [VertexId; 3], [FaceId; 3]) {
let mut mesh = MeshStorage::new();
let c = mesh.add_vertex(Vertex::new([0.5, 0.5, 0.0]));
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([0.5, 1.0, 0.0]));
let a1 = mesh.add_halfedge(HalfEdge::new(v0)); let b1 = mesh.add_halfedge(HalfEdge::new(v1)); let c1 = mesh.add_halfedge(HalfEdge::new(c)); let a2 = mesh.add_halfedge(HalfEdge::new(v1)); let b2 = mesh.add_halfedge(HalfEdge::new(v2)); let c2 = mesh.add_halfedge(HalfEdge::new(c)); let a3 = mesh.add_halfedge(HalfEdge::new(v2)); let b3 = mesh.add_halfedge(HalfEdge::new(v0)); let c3 = mesh.add_halfedge(HalfEdge::new(c)); let t1 = mesh.add_halfedge(HalfEdge::new(v0)); let t2 = mesh.add_halfedge(HalfEdge::new(v1)); let t3 = mesh.add_halfedge(HalfEdge::new(v2));
let f1 = mesh.add_face(Face::new());
let f2 = mesh.add_face(Face::new());
let f3 = mesh.add_face(Face::new());
for (he, twin, next, prev, face) in [
(a1, c3, b1, c1, f1),
(b1, t1, c1, a1, f1),
(c1, a2, a1, b1, f1),
] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(face);
}
for (he, twin, next, prev, face) in [
(a2, c1, b2, c2, f2),
(b2, t2, c2, a2, f2),
(c2, a3, a2, b2, f2),
] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(face);
}
for (he, twin, next, prev, face) in [
(a3, c2, b3, c3, f3),
(b3, t3, c3, a3, f3),
(c3, a1, a3, b3, f3),
] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(face);
}
for (t, he) in [(t1, b1), (t2, b2), (t3, b3)] {
mesh.get_halfedge_mut(t).unwrap().twin = Some(he);
}
mesh.get_vertex_mut(c).unwrap().halfedge = Some(a1);
mesh.get_vertex_mut(v0).unwrap().halfedge = Some(b1);
mesh.get_vertex_mut(v1).unwrap().halfedge = Some(b2);
mesh.get_vertex_mut(v2).unwrap().halfedge = Some(b3);
mesh.get_face_mut(f1).unwrap().halfedge = Some(a1);
mesh.get_face_mut(f2).unwrap().halfedge = Some(a2);
mesh.get_face_mut(f3).unwrap().halfedge = Some(a3);
(mesh, c, [v0, v1, v2], [f1, f2, f3])
}
#[test]
fn closed_fan_center_is_interior() {
let (mesh, c, _outer, _faces) = build_closed_fan();
assert!(!is_boundary_vertex(&mesh, c), "中心顶点 c 应为内部顶点");
}
#[test]
fn closed_fan_outer_vertices_are_boundary() {
let (mesh, _c, outer, _faces) = build_closed_fan();
for v in outer {
assert!(
is_boundary_vertex(&mesh, v),
"外圈顶点 {:?} 应为边界顶点",
v
);
}
}
#[test]
fn closed_fan_center_ring_closes_with_three_outgoing() {
let (mesh, c, _outer, _faces) = build_closed_fan();
let ring: Vec<_> = VertexRing::new(&mesh, c).collect();
assert_eq!(ring.len(), 3, "c 周围应有 3 条 outgoing 半边");
let mut sorted = ring.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), 3);
}
#[test]
fn closed_fan_center_adjacent_faces_yields_three() {
let (mesh, c, _outer, faces) = build_closed_fan();
let mut adj: Vec<_> = VertexAdjacentFaces::new(&mesh, c).collect();
adj.sort();
let mut expected = faces.to_vec();
expected.sort();
assert_eq!(adj, expected);
}
#[test]
fn lazy_vertex_ring_matches_eager_on_single_triangle() {
let (mesh, v, _he, _f) = build_triangle();
for vi in v {
let eager: Vec<_> = VertexRing::new(&mesh, vi).collect();
let lazy: Vec<_> = VertexRing::lazy(&mesh, vi).collect();
assert_eq!(eager, lazy, "顶点 {:?} 的 lazy 环应与预收集一致", vi);
}
}
#[test]
fn lazy_vertex_ring_matches_eager_on_closed_fan() {
let (mesh, c, outer, _faces) = build_closed_fan();
let eager: Vec<_> = VertexRing::new(&mesh, c).collect();
let lazy: Vec<_> = VertexRing::lazy(&mesh, c).collect();
assert_eq!(eager, lazy, "内部顶点 c 的 lazy 环应与预收集一致");
for vi in outer {
let eager: Vec<_> = VertexRing::new(&mesh, vi).collect();
let lazy: Vec<_> = VertexRing::lazy(&mesh, vi).collect();
assert_eq!(eager, lazy, "边界顶点 {:?} 的 lazy 环应与预收集一致", vi);
}
}
#[test]
fn lazy_vertex_ring_matches_eager_on_two_triangles() {
let (mesh, v, _f1, _f2) = build_two_triangles();
for vi in v {
let eager: Vec<_> = VertexRing::new(&mesh, vi).collect();
let lazy: Vec<_> = VertexRing::lazy(&mesh, vi).collect();
assert_eq!(eager, lazy, "顶点 {:?} 的 lazy 环应与预收集一致", vi);
}
}
#[test]
fn lazy_face_halfedges_matches_eager() {
let (mesh, _v, _he, f) = build_triangle();
let eager: Vec<_> = FaceHalfEdges::new(&mesh, f).collect();
let lazy: Vec<_> = FaceHalfEdges::lazy(&mesh, f).collect();
assert_eq!(eager, lazy);
}
#[test]
fn lazy_face_vertices_matches_eager() {
let (mesh, _v, _he, f) = build_triangle();
let eager: Vec<_> = FaceVertices::new(&mesh, f).collect();
let lazy: Vec<_> = FaceVertices::lazy(&mesh, f).collect();
assert_eq!(eager, lazy);
}
#[test]
fn lazy_adjacent_verts_matches_eager() {
let (mesh, c, outer, _faces) = build_closed_fan();
for vi in std::iter::once(c).chain(outer.iter().copied()) {
let eager: Vec<_> = VertexAdjacentVerts::new(&mesh, vi).collect();
let lazy: Vec<_> = VertexAdjacentVerts::lazy(&mesh, vi).collect();
assert_eq!(eager, lazy, "顶点 {:?} 的 lazy 邻接顶点应一致", vi);
}
}
#[test]
fn lazy_adjacent_faces_matches_eager() {
let (mesh, c, outer, _faces) = build_closed_fan();
for vi in std::iter::once(c).chain(outer.iter().copied()) {
let mut eager: Vec<_> = VertexAdjacentFaces::new(&mesh, vi).collect();
let mut lazy: Vec<_> = VertexAdjacentFaces::lazy(&mesh, vi).collect();
eager.sort();
lazy.sort();
assert_eq!(eager, lazy, "顶点 {:?} 的 lazy 邻接面应一致", vi);
}
}
#[test]
fn lazy_iterators_handle_invalid_ids() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3])); let f = mesh.add_face(Face::new());
assert_eq!(VertexRing::lazy(&mesh, v).count(), 0);
assert_eq!(VertexAdjacentVerts::lazy(&mesh, v).count(), 0);
assert_eq!(VertexAdjacentFaces::lazy(&mesh, v).count(), 0);
assert_eq!(FaceHalfEdges::lazy(&mesh, f).count(), 0);
assert_eq!(FaceVertices::lazy(&mesh, f).count(), 0);
}
#[test]
fn lazy_iterators_are_fused() {
let (mesh, v, _he, f) = build_triangle();
let [v0, _v1, _v2] = v;
let mut ring = VertexRing::lazy(&mesh, v0);
while ring.next().is_some() {}
for _ in 0..5 {
assert!(ring.next().is_none(), "FusedIterator 耗尽后应返回 None");
}
let mut face_he = FaceHalfEdgesLazy::new(&mesh, f);
while face_he.next().is_some() {}
for _ in 0..5 {
assert!(face_he.next().is_none());
}
}
#[test]
fn lazy_iterators_on_icosphere() {
let mesh = crate::test_util::build_icosphere(1); for v in mesh.vertex_ids() {
let eager: Vec<_> = VertexRing::new(&mesh, v).collect();
let lazy: Vec<_> = VertexRing::lazy(&mesh, v).collect();
assert_eq!(eager, lazy, "icosphere 顶点 {:?} 的 lazy 环应一致", v);
}
for f in mesh.face_ids() {
let eager: Vec<_> = FaceHalfEdges::new(&mesh, f).collect();
let lazy: Vec<_> = FaceHalfEdges::lazy(&mesh, f).collect();
assert_eq!(eager, lazy, "icosphere 面 {:?} 的 lazy 半边应一致", f);
}
}
#[test]
fn eager_count_hint_returns_some_remaining() {
let (mesh, v, _he, f) = build_triangle();
let [v0, _v1, _v2] = v;
let ring = VertexRing::new(&mesh, v0);
assert_eq!(ring.count_hint(), Some(2));
assert_eq!(ring.len(), 2);
assert!(!ring.is_empty());
let mut ring = VertexRing::new(&mesh, v0);
let _ = ring.next();
assert_eq!(ring.count_hint(), Some(1));
assert_eq!(ring.len(), 1);
let mut ring = VertexRing::new(&mesh, v0);
while ring.next().is_some() {}
assert_eq!(ring.count_hint(), Some(0));
assert_eq!(ring.len(), 0);
assert!(ring.is_empty());
let fh = FaceHalfEdges::new(&mesh, f);
assert_eq!(fh.count_hint(), Some(3));
assert_eq!(fh.len(), 3);
let fv = FaceVertices::new(&mesh, f);
assert_eq!(fv.count_hint(), Some(3));
assert_eq!(fv.len(), 3);
}
#[test]
fn eager_is_empty_on_invalid_ids() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3])); let f = mesh.add_face(Face::new());
assert!(VertexRing::new(&mesh, v).is_empty());
assert!(VertexAdjacentVerts::new(&mesh, v).is_empty());
assert!(VertexAdjacentFaces::new(&mesh, v).is_empty());
assert!(FaceHalfEdges::new(&mesh, f).is_empty());
assert!(FaceVertices::new(&mesh, f).is_empty());
assert_eq!(VertexRing::new(&mesh, v).count_hint(), Some(0));
assert_eq!(FaceHalfEdges::new(&mesh, f).count_hint(), Some(0));
}
#[test]
fn eager_exact_size_iterator_size_hint() {
let (mesh, v, _he, f) = build_triangle();
let [v0, _v1, _v2] = v;
let ring = VertexRing::new(&mesh, v0);
let (lower, upper) = ring.size_hint();
assert_eq!(lower, 2);
assert_eq!(upper, Some(2));
let mut ring = VertexRing::new(&mesh, v0);
ring.next();
let (lower, upper) = ring.size_hint();
assert_eq!(lower, 1);
assert_eq!(upper, Some(1));
let fv = FaceVertices::new(&mesh, f);
let (lower, upper) = fv.size_hint();
assert_eq!(lower, 3);
assert_eq!(upper, Some(3));
}
#[test]
fn lazy_count_hint_returns_none() {
let (mesh, v, _he, f) = build_triangle();
let [v0, _v1, _v2] = v;
assert_eq!(VertexRing::lazy(&mesh, v0).count_hint(), None);
assert_eq!(VertexAdjacentVerts::lazy(&mesh, v0).count_hint(), None);
assert_eq!(VertexAdjacentFaces::lazy(&mesh, v0).count_hint(), None);
assert_eq!(FaceHalfEdges::lazy(&mesh, f).count_hint(), None);
assert_eq!(FaceVertices::lazy(&mesh, f).count_hint(), None);
}
#[test]
fn lazy_is_empty_on_valid_and_invalid() {
let (mesh, v, _he, f) = build_triangle();
let [v0, _v1, _v2] = v;
assert!(!VertexRing::lazy(&mesh, v0).is_empty());
assert!(!VertexAdjacentVerts::lazy(&mesh, v0).is_empty());
assert!(!FaceHalfEdges::lazy(&mesh, f).is_empty());
assert!(!FaceVertices::lazy(&mesh, f).is_empty());
let mut mesh2 = MeshStorage::new();
let v = mesh2.add_vertex(Vertex::new([0.0; 3])); let f = mesh2.add_face(Face::new()); assert!(VertexRing::lazy(&mesh2, v).is_empty());
assert!(VertexAdjacentVerts::lazy(&mesh2, v).is_empty());
assert!(VertexAdjacentFaces::lazy(&mesh2, v).is_empty());
assert!(FaceHalfEdges::lazy(&mesh2, f).is_empty());
assert!(FaceVertices::lazy(&mesh2, f).is_empty());
}
#[test]
fn lazy_is_empty_after_exhaustion() {
let (mesh, v, _he, _f) = build_triangle();
let [v0, _v1, _v2] = v;
let mut ring = VertexRing::lazy(&mesh, v0);
assert!(!ring.is_empty()); while ring.next().is_some() {}
assert!(ring.is_empty()); }
#[test]
fn max_vertex_valence_single_triangle() {
let (mesh, _v, _he, _f) = build_triangle();
assert_eq!(mesh.max_vertex_valence(), 2);
}
#[test]
fn max_vertex_valence_closed_fan() {
let (mesh, _c, _outer, _faces) = build_closed_fan();
assert_eq!(mesh.max_vertex_valence(), 3);
}
#[test]
fn max_vertex_valence_empty_mesh() {
let mesh = MeshStorage::new();
assert_eq!(mesh.max_vertex_valence(), 0);
}
#[test]
fn max_vertex_valence_isolated_vertices() {
let mut mesh = MeshStorage::new();
mesh.add_vertex(Vertex::new([0.0; 3]));
mesh.add_vertex(Vertex::new([1.0; 3]));
assert_eq!(mesh.max_vertex_valence(), 0);
}
#[test]
fn max_vertex_valence_icosphere() {
let mesh = crate::test_util::build_icosphere(1);
let max_val = mesh.max_vertex_valence();
assert!(
(5..=6).contains(&max_val),
"icosphere(1) 最大度数应在 [5,6],实际 {}",
max_val
);
}
#[test]
fn edge_count_single_triangle() {
let (mesh, _v, _he, _f) = build_triangle();
assert_eq!(mesh.edge_count(), 3);
}
#[test]
fn edge_count_two_triangles() {
let (mesh, _v, _he, _f) = build_two_triangles();
assert_eq!(mesh.edge_count(), 5);
}
#[test]
fn edge_count_closed_fan() {
let (mesh, _c, _outer, _faces) = build_closed_fan();
assert_eq!(mesh.edge_count(), 6);
}
#[test]
fn edge_count_empty() {
let mesh = MeshStorage::new();
assert_eq!(mesh.edge_count(), 0);
}
#[test]
fn edge_ids_single_triangle_yields_three_unique_edges() {
let (mesh, _v, _he, _f) = build_triangle();
let edges: Vec<EdgeId> = mesh.edge_ids().collect();
assert_eq!(edges.len(), 3, "单三角形应有 3 条无向边");
let mut sorted = edges.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), 3);
}
#[test]
fn edge_ids_two_triangles_yields_five_unique_edges() {
let (mesh, _v, _he, _f) = build_two_triangles();
let edges: Vec<EdgeId> = mesh.edge_ids().collect();
assert_eq!(edges.len(), 5);
}
#[test]
fn edge_ids_on_icosphere() {
let mesh = crate::test_util::build_icosphere(1);
let edges: Vec<EdgeId> = mesh.edge_ids().collect();
assert_eq!(edges.len(), 120, "icosphere(1) 应有 120 条无向边");
assert_eq!(mesh.edge_count(), 120);
for e in &edges {
let he = e.halfedge();
assert!(mesh.contains_halfedge(he), "EdgeId 的半边应存在");
}
let mut sorted = edges.clone();
sorted.sort();
sorted.dedup();
assert_eq!(sorted.len(), 120, "所有 EdgeId 应互不相同");
}
#[test]
fn edge_id_vertices_on_icosphere() {
let mesh = crate::test_util::build_icosphere(1);
for edge in mesh.edge_ids() {
let (src, dst) = edge.vertices(&mesh).expect("闭合网格每条边应有 twin");
assert_ne!(src, dst, "边的两端点应不同");
assert!(mesh.contains_vertex(src) && mesh.contains_vertex(dst));
}
}
#[test]
fn edge_iter_is_exact_size() {
let mesh = crate::test_util::build_icosphere(1);
let mut iter = mesh.edge_ids();
let total = iter.len();
assert_eq!(total, 120);
let (lower, upper) = iter.size_hint();
assert_eq!(lower, 120);
assert_eq!(upper, Some(120));
for _ in 0..60 {
iter.next();
}
assert_eq!(iter.len(), 60);
}
#[test]
fn edge_count_matches_euler() {
let mesh = crate::test_util::build_icosphere(1);
let v = mesh.vertex_count();
let e = mesh.edge_count();
let f = mesh.face_count();
assert_eq!(v as i64 - e as i64 + f as i64, 2, "欧拉公式");
}
#[test]
fn is_closed_single_triangle_is_false() {
let (mesh, _v, _he, _f) = build_triangle();
assert!(!is_closed(&mesh), "单三角形有边界,非闭合");
}
#[test]
fn is_closed_icosphere_is_true() {
let mesh = crate::test_util::build_icosphere(1);
assert!(is_closed(&mesh), "icosphere 是闭合网格");
}
#[test]
fn boundary_loop_single_triangle() {
let (mesh, _v, he, _f) = build_triangle();
let [_h0, _h1, _h2, t0, t1, t2] = he;
let loops = boundary_loops(&mesh);
assert_eq!(loops.len(), 1, "单三角形应有 1 个边界环");
let boundary = &loops[0];
assert_eq!(boundary.len(), 3, "三角形边界环应有 3 条半边");
for &bhe in &[t0, t1, t2] {
assert!(boundary.contains(&bhe), "边界半边 {:?} 应在环中", bhe);
}
}
#[test]
fn boundary_loop_icosphere_is_empty() {
let mesh = crate::test_util::build_icosphere(1);
let loops = boundary_loops(&mesh);
assert!(loops.is_empty(), "闭合网格不应有边界环");
}
#[test]
fn boundary_loop_lazy_matches_eager() {
let (mesh, _v, he, _f) = build_triangle();
let [_h0, _h1, _h2, t0, t1, t2] = he;
for &start in &[t0, t1, t2] {
let eager: Vec<_> = BoundaryLoop::new(&mesh, start).collect();
let lazy: Vec<_> = BoundaryLoopLazy::new(&mesh, start).collect();
assert_eq!(eager.len(), lazy.len(), "eager 和 lazy 长度一致");
assert_eq!(eager.len(), 3);
}
}
#[test]
fn boundary_loop_detects_multiple_loops() {
let (mesh, _v, _he, _f) = build_two_triangles();
let loops = boundary_loops(&mesh);
assert!(!loops.is_empty(), "两三角形应有边界");
}
#[test]
fn is_closed_closed_fan_is_false() {
let (mesh, _c, _outer, _faces) = build_closed_fan();
assert!(!is_closed(&mesh), "扇形有外边界");
}
#[test]
fn euler_characteristic_single_triangle() {
let (mesh, _v, _he, _f) = build_triangle();
assert_eq!(mesh.euler_characteristic(), 1);
assert_eq!(mesh.genus(), 0);
}
#[test]
fn euler_characteristic_closed_fan() {
let (mesh, _c, _outer, _faces) = build_closed_fan();
assert_eq!(mesh.euler_characteristic(), 1);
}
#[test]
fn euler_characteristic_icosphere_is_two() {
let mesh = crate::test_util::build_icosphere(1);
assert_eq!(mesh.euler_characteristic(), 2, "闭合球面 χ 应为 2");
assert_eq!(mesh.genus(), 0, "球面亏格应为 0");
}
#[test]
fn euler_characteristic_empty_mesh() {
let mesh = MeshStorage::new();
assert_eq!(mesh.euler_characteristic(), 0);
assert_eq!(mesh.genus(), 1); }
#[test]
fn vertex_adjacent_edges_triangle() {
let (mesh, v, _he, _f) = build_triangle();
let [v0, _v1, _v2] = v;
let edges: Vec<_> = VertexAdjacentEdges::new(&mesh, v0).collect();
assert_eq!(edges.len(), 2);
for e in &edges {
assert!(mesh.contains_halfedge(e.halfedge()));
}
}
#[test]
fn vertex_adjacent_edges_icosphere() {
let mesh = crate::test_util::build_icosphere(1);
for v in mesh.vertex_ids() {
let edges: Vec<_> = VertexAdjacentEdges::new(&mesh, v).collect();
let valence = VertexRing::new(&mesh, v).count();
assert_eq!(edges.len(), valence, "顶点 {:?} 度数应等于关联边数", v);
}
}
#[test]
fn collect_outgoing_halfedges_into_matches_original() {
let mesh = crate::test_util::build_icosphere(1);
let mut buf = Vec::new();
for v in mesh.vertex_ids() {
let expected = collect_outgoing_halfedges(&mesh, v);
collect_outgoing_halfedges_into(&mesh, v, &mut buf);
assert_eq!(buf, expected, "顶点 {:?} 的 outgoing 半边不匹配", v);
}
}
#[test]
fn collect_face_halfedges_into_matches_original() {
let mesh = crate::test_util::build_icosphere(1);
let mut buf = Vec::new();
for f in mesh.face_ids() {
let expected = collect_face_halfedges(&mesh, f);
collect_face_halfedges_into(&mesh, f, &mut buf);
assert_eq!(buf, expected, "面 {:?} 的半边不匹配", f);
}
}
#[test]
fn collect_outgoing_halfedges_into_reuses_capacity() {
let mesh = crate::test_util::build_icosphere(1);
let mut buf = Vec::new();
collect_outgoing_halfedges_into(&mesh, mesh.vertex_ids().next().unwrap(), &mut buf);
let cap_after_first = buf.capacity();
assert!(cap_after_first > 0);
for v in mesh.vertex_ids() {
collect_outgoing_halfedges_into(&mesh, v, &mut buf);
assert!(buf.capacity() >= cap_after_first, "容量不应缩小");
}
}
#[test]
fn collect_face_halfedges_into_clears_buffer() {
let (mesh, _v, _he, f) = build_triangle();
let mut buf = vec![HalfEdgeId::default(); 100]; collect_face_halfedges_into(&mesh, f, &mut buf);
assert_eq!(buf.len(), 3);
}
#[test]
fn collect_outgoing_halfedges_into_isolated_vertex() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let mut buf = vec![HalfEdgeId::default(); 10];
collect_outgoing_halfedges_into(&mesh, v, &mut buf);
assert!(buf.is_empty(), "孤立顶点应返回空 buffer");
}
}