use std::collections::{HashMap, HashSet};
use std::fmt;
use crate::ids::{FaceId, HalfEdgeId, VertexId};
use crate::storage::{Face, HalfEdge, MeshStorage, Vertex};
use crate::traversal::{FaceHalfEdges, VertexAdjacentFaces, VertexAdjacentVerts, VertexRing};
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TopologyError {
InvalidHalfEdge(HalfEdgeId),
FlipOnBoundaryEdge(HalfEdgeId),
CollapseOnBoundaryEdge(HalfEdgeId),
NoTwin(HalfEdgeId),
NoFace(HalfEdgeId),
DegenerateTriangle,
LinkConditionViolated { a: VertexId, b: VertexId },
Inconsistent(String),
}
impl fmt::Display for TopologyError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidHalfEdge(h) => write!(f, "无效半边句柄 {:?}", h),
Self::FlipOnBoundaryEdge(h) => write!(f, "禁止翻转边界边 {:?}", h),
Self::CollapseOnBoundaryEdge(h) => write!(f, "禁止折叠边界边 {:?}", h),
Self::NoTwin(h) => write!(f, "半边 {:?} 没有 twin", h),
Self::NoFace(h) => write!(f, "半边 {:?} 两侧均无面", h),
Self::DegenerateTriangle => write!(f, "操作会产生退化三角形"),
Self::LinkConditionViolated { a, b } => {
write!(f, "链接条件不满足:折叠 {:?}-{:?} 会产生非流形", a, b)
}
Self::Inconsistent(msg) => write!(f, "网格拓扑不一致:{}", msg),
}
}
}
impl std::error::Error for TopologyError {}
pub fn validate_mesh(mesh: &MeshStorage) -> Result<(), TopologyError> {
let all_he: Vec<HalfEdgeId> = mesh.halfedge_ids().collect();
for he_id in &all_he {
let he = match mesh.get_halfedge(*he_id) {
Some(h) => h,
None => continue,
};
if let Some(twin_id) = he.twin {
let twin = match mesh.get_halfedge(twin_id) {
Some(t) => t,
None => {
return Err(TopologyError::Inconsistent(format!(
"半边 {:?} 的 twin {:?} 不存在",
he_id, twin_id
)));
}
};
if twin.twin != Some(*he_id) {
return Err(TopologyError::Inconsistent(format!(
"twin 不互指:{:?}.twin={:?}, 但 {:?}.twin={:?}",
he_id, twin_id, twin_id, twin.twin
)));
}
if twin.vertex == he.vertex {
return Err(TopologyError::Inconsistent(format!(
"半边 {:?} 与其 twin 顶点相同(自环)",
he_id
)));
}
}
if let Some(next_id) = he.next {
match mesh.get_halfedge(next_id) {
Some(next) if next.prev == Some(*he_id) => {}
Some(next) => {
return Err(TopologyError::Inconsistent(format!(
"next/prev 不一致:{:?}.next={:?}, 但 {:?}.prev={:?}",
he_id, next_id, next_id, next.prev
)));
}
None => {
return Err(TopologyError::Inconsistent(format!(
"半边 {:?} 的 next {:?} 不存在",
he_id, next_id
)));
}
}
}
if let Some(prev_id) = he.prev {
match mesh.get_halfedge(prev_id) {
Some(prev) if prev.next == Some(*he_id) => {}
Some(prev) => {
return Err(TopologyError::Inconsistent(format!(
"prev/next 不一致:{:?}.prev={:?}, 但 {:?}.next={:?}",
he_id, prev_id, prev_id, prev.next
)));
}
None => {
return Err(TopologyError::Inconsistent(format!(
"半边 {:?} 的 prev {:?} 不存在",
he_id, prev_id
)));
}
}
}
}
for f_id in mesh.face_ids() {
let f = match mesh.get_face(f_id) {
Some(f) => f,
None => continue,
};
if let Some(start) = f.halfedge {
let mut count = 0usize;
let mut cur = start;
let max_iter = mesh.halfedge_count() + 1;
for _ in 0..max_iter {
count += 1;
match mesh.get_halfedge(cur).and_then(|h| h.next) {
Some(n) if n != start => cur = n,
_ => break,
}
}
if count != 3 {
return Err(TopologyError::Inconsistent(format!(
"面 {:?} 的边界环长度为 {},非三角面",
f_id, count
)));
}
}
}
Ok(())
}
pub fn split_edge(mesh: &mut MeshStorage, he: HalfEdgeId) -> Result<VertexId, TopologyError> {
let h_data = mesh
.get_halfedge(he)
.ok_or(TopologyError::InvalidHalfEdge(he))?
.clone();
let (h_id, h) = if h_data.face.is_none() {
let twin_id = h_data.twin.ok_or(TopologyError::NoTwin(he))?;
let twin_data = mesh
.get_halfedge(twin_id)
.ok_or(TopologyError::InvalidHalfEdge(twin_id))?
.clone();
if twin_data.face.is_none() {
return Err(TopologyError::NoFace(he));
}
(twin_id, twin_data)
} else {
(he, h_data)
};
let twin_id = h.twin.ok_or(TopologyError::NoTwin(h_id))?;
let twin = mesh
.get_halfedge(twin_id)
.ok_or(TopologyError::InvalidHalfEdge(twin_id))?
.clone();
let a = twin.vertex; let b = h.vertex; let f1 = h.face.ok_or(TopologyError::NoFace(h_id))?;
let n1 = h
.next
.ok_or_else(|| TopologyError::Inconsistent("h.next is None".into()))?;
let p1 = h
.prev
.ok_or_else(|| TopologyError::Inconsistent("h.prev is None".into()))?;
let c = mesh
.get_halfedge(n1)
.ok_or(TopologyError::InvalidHalfEdge(n1))?
.vertex;
let has_f2 = twin.face.is_some();
let (f2, n2, p2, d) = if has_f2 {
let f2 = twin.face.unwrap();
let n2 = twin
.next
.ok_or_else(|| TopologyError::Inconsistent("twin.next is None".into()))?;
let p2 = twin
.prev
.ok_or_else(|| TopologyError::Inconsistent("twin.prev is None".into()))?;
let d = mesh
.get_halfedge(n2)
.ok_or(TopologyError::InvalidHalfEdge(n2))?
.vertex;
(Some(f2), Some(n2), Some(p2), Some(d))
} else {
(None, None, None, None)
};
let pos_a = mesh
.get_vertex(a)
.ok_or_else(|| TopologyError::Inconsistent("顶点 A 不存在".into()))?
.position;
let pos_b = mesh
.get_vertex(b)
.ok_or_else(|| TopologyError::Inconsistent("顶点 B 不存在".into()))?
.position;
let mid = [
(pos_a[0] + pos_b[0]) * 0.5,
(pos_a[1] + pos_b[1]) * 0.5,
(pos_a[2] + pos_b[2]) * 0.5,
];
let m = mesh.add_vertex(Vertex::new(mid));
let a_to_m = mesh.add_halfedge(HalfEdge::new(m)); let m_to_a = mesh.add_halfedge(HalfEdge::new(a)); let m_to_b = mesh.add_halfedge(HalfEdge::new(b)); let b_to_m = mesh.add_halfedge(HalfEdge::new(m)); let m_to_c = mesh.add_halfedge(HalfEdge::new(c)); let c_to_m = mesh.add_halfedge(HalfEdge::new(m));
let (m_to_d, d_to_m) = if has_f2 {
let d = d.unwrap();
(
Some(mesh.add_halfedge(HalfEdge::new(d))), Some(mesh.add_halfedge(HalfEdge::new(m))), )
} else {
(None, None)
};
{
let am = mesh.get_halfedge_mut(a_to_m).unwrap();
am.twin = Some(m_to_a);
am.next = Some(m_to_c);
am.prev = Some(p1);
am.face = Some(f1);
let mc = mesh.get_halfedge_mut(m_to_c).unwrap();
mc.twin = Some(c_to_m);
mc.next = Some(p1);
mc.prev = Some(a_to_m);
mc.face = Some(f1);
let p1h = mesh.get_halfedge_mut(p1).unwrap();
p1h.next = Some(a_to_m);
p1h.prev = Some(m_to_c);
}
let f_new1 = mesh.add_face(Face::new());
{
let mb = mesh.get_halfedge_mut(m_to_b).unwrap();
mb.twin = Some(b_to_m);
mb.next = Some(n1);
mb.prev = Some(c_to_m);
mb.face = Some(f_new1);
let n1h = mesh.get_halfedge_mut(n1).unwrap();
n1h.next = Some(c_to_m);
n1h.prev = Some(m_to_b);
n1h.face = Some(f_new1);
let cm = mesh.get_halfedge_mut(c_to_m).unwrap();
cm.twin = Some(m_to_c);
cm.next = Some(m_to_b);
cm.prev = Some(n1);
cm.face = Some(f_new1);
}
mesh.get_face_mut(f_new1).unwrap().halfedge = Some(m_to_b);
if has_f2 {
let f2 = f2.unwrap();
let n2 = n2.unwrap();
let p2 = p2.unwrap();
let m_to_d = m_to_d.unwrap();
let d_to_m = d_to_m.unwrap();
{
let bm = mesh.get_halfedge_mut(b_to_m).unwrap();
bm.twin = Some(m_to_b);
bm.next = Some(m_to_d);
bm.prev = Some(p2);
bm.face = Some(f2);
let md = mesh.get_halfedge_mut(m_to_d).unwrap();
md.twin = Some(d_to_m);
md.next = Some(p2);
md.prev = Some(b_to_m);
md.face = Some(f2);
let p2h = mesh.get_halfedge_mut(p2).unwrap();
p2h.next = Some(b_to_m);
p2h.prev = Some(m_to_d);
}
let f_new2 = mesh.add_face(Face::new());
{
let ma = mesh.get_halfedge_mut(m_to_a).unwrap();
ma.twin = Some(a_to_m);
ma.next = Some(n2);
ma.prev = Some(d_to_m);
ma.face = Some(f_new2);
let n2h = mesh.get_halfedge_mut(n2).unwrap();
n2h.next = Some(d_to_m);
n2h.prev = Some(m_to_a);
n2h.face = Some(f_new2);
let dm = mesh.get_halfedge_mut(d_to_m).unwrap();
dm.twin = Some(m_to_d);
dm.next = Some(m_to_a);
dm.prev = Some(n2);
dm.face = Some(f_new2);
}
mesh.get_face_mut(f_new2).unwrap().halfedge = Some(m_to_a);
mesh.get_face_mut(f2).unwrap().halfedge = Some(b_to_m);
} else {
let bm = mesh.get_halfedge_mut(b_to_m).unwrap();
bm.twin = Some(m_to_b);
bm.face = None;
let ma = mesh.get_halfedge_mut(m_to_a).unwrap();
ma.twin = Some(a_to_m);
ma.face = None;
}
mesh.get_face_mut(f1).unwrap().halfedge = Some(a_to_m);
if mesh.get_vertex(a).unwrap().halfedge == Some(h_id) {
mesh.get_vertex_mut(a).unwrap().halfedge = Some(a_to_m);
}
if mesh.get_vertex(b).unwrap().halfedge == Some(twin_id) {
mesh.get_vertex_mut(b).unwrap().halfedge = Some(b_to_m);
}
mesh.get_vertex_mut(m).unwrap().halfedge = Some(m_to_a);
mesh.remove_halfedge(h_id);
mesh.remove_halfedge(twin_id);
Ok(m)
}
pub fn flip_edge(mesh: &mut MeshStorage, he: HalfEdgeId) -> Result<(), TopologyError> {
let h = mesh
.get_halfedge(he)
.ok_or(TopologyError::InvalidHalfEdge(he))?
.clone();
let twin_id = h.twin.ok_or(TopologyError::NoTwin(he))?;
let twin = mesh
.get_halfedge(twin_id)
.ok_or(TopologyError::InvalidHalfEdge(twin_id))?
.clone();
let f1 = h.face.ok_or(TopologyError::FlipOnBoundaryEdge(he))?;
let f2 = twin.face.ok_or(TopologyError::FlipOnBoundaryEdge(he))?;
let a = twin.vertex; let b = h.vertex;
let n1 = h
.next
.ok_or_else(|| TopologyError::Inconsistent("h.next is None".into()))?;
let p1 = h
.prev
.ok_or_else(|| TopologyError::Inconsistent("h.prev is None".into()))?;
let n2 = twin
.next
.ok_or_else(|| TopologyError::Inconsistent("twin.next is None".into()))?;
let p2 = twin
.prev
.ok_or_else(|| TopologyError::Inconsistent("twin.prev is None".into()))?;
let c = mesh
.get_halfedge(n1)
.ok_or(TopologyError::InvalidHalfEdge(n1))?
.vertex;
let d = mesh
.get_halfedge(n2)
.ok_or(TopologyError::InvalidHalfEdge(n2))?
.vertex;
if c == d {
return Err(TopologyError::DegenerateTriangle);
}
{
let h_mut = mesh.get_halfedge_mut(he).unwrap();
h_mut.vertex = c;
h_mut.next = Some(p1);
h_mut.prev = Some(n2);
h_mut.face = Some(f1);
let twin_mut = mesh.get_halfedge_mut(twin_id).unwrap();
twin_mut.vertex = d;
twin_mut.next = Some(p2);
twin_mut.prev = Some(n1);
twin_mut.face = Some(f2);
}
{
let p1_mut = mesh.get_halfedge_mut(p1).unwrap();
p1_mut.next = Some(n2);
p1_mut.prev = Some(he);
let n2_mut = mesh.get_halfedge_mut(n2).unwrap();
n2_mut.next = Some(he);
n2_mut.prev = Some(p1);
n2_mut.face = Some(f1);
}
{
let p2_mut = mesh.get_halfedge_mut(p2).unwrap();
p2_mut.next = Some(n1);
p2_mut.prev = Some(twin_id);
let n1_mut = mesh.get_halfedge_mut(n1).unwrap();
n1_mut.next = Some(twin_id);
n1_mut.prev = Some(p2);
n1_mut.face = Some(f2);
}
mesh.get_face_mut(f1).unwrap().halfedge = Some(he);
mesh.get_face_mut(f2).unwrap().halfedge = Some(twin_id);
if mesh.get_vertex(a).unwrap().halfedge == Some(he) {
mesh.get_vertex_mut(a).unwrap().halfedge = Some(n2);
}
if mesh.get_vertex(b).unwrap().halfedge == Some(twin_id) {
mesh.get_vertex_mut(b).unwrap().halfedge = Some(n1);
}
Ok(())
}
pub fn collapse_edge(mesh: &mut MeshStorage, he: HalfEdgeId) -> Result<VertexId, TopologyError> {
collapse_edge_impl(mesh, he, None)
}
pub fn collapse_edge_at(
mesh: &mut MeshStorage,
he: HalfEdgeId,
target_pos: [f64; 3],
) -> Result<VertexId, TopologyError> {
collapse_edge_impl(mesh, he, Some(target_pos))
}
fn collapse_edge_impl(
mesh: &mut MeshStorage,
he: HalfEdgeId,
target_pos: Option<[f64; 3]>,
) -> Result<VertexId, TopologyError> {
let h = mesh
.get_halfedge(he)
.ok_or(TopologyError::InvalidHalfEdge(he))?
.clone();
let twin_id = h.twin.ok_or(TopologyError::NoTwin(he))?;
let twin = mesh
.get_halfedge(twin_id)
.ok_or(TopologyError::InvalidHalfEdge(twin_id))?
.clone();
let f1 = h.face.ok_or(TopologyError::CollapseOnBoundaryEdge(he))?;
let f2 = twin.face.ok_or(TopologyError::CollapseOnBoundaryEdge(he))?;
let a = twin.vertex;
let b = h.vertex;
let n1 = h
.next
.ok_or_else(|| TopologyError::Inconsistent("h.next is None".into()))?;
let p1 = h
.prev
.ok_or_else(|| TopologyError::Inconsistent("h.prev is None".into()))?;
let n2 = twin
.next
.ok_or_else(|| TopologyError::Inconsistent("twin.next is None".into()))?;
let p2 = twin
.prev
.ok_or_else(|| TopologyError::Inconsistent("twin.prev is None".into()))?;
let c = mesh
.get_halfedge(n1)
.ok_or(TopologyError::InvalidHalfEdge(n1))?
.vertex;
let d = mesh
.get_halfedge(n2)
.ok_or(TopologyError::InvalidHalfEdge(n2))?
.vertex;
if c == d {
return Err(TopologyError::DegenerateTriangle);
}
if !check_link_condition(mesh, a, b, c, d) {
return Err(TopologyError::LinkConditionViolated { a, b });
}
let deleted = [he, twin_id, n1, p1, n2, p2];
let deleted_set: HashSet<HalfEdgeId> = deleted.iter().copied().collect();
let mut to_update: Vec<HalfEdgeId> = Vec::new();
for out_he in VertexRing::new(mesh, a).collect::<Vec<_>>() {
if let Some(t_id) = mesh.get_halfedge(out_he).and_then(|h| h.twin)
&& !deleted_set.contains(&t_id)
{
to_update.push(t_id);
}
}
for out_he in VertexRing::new(mesh, b).collect::<Vec<_>>() {
if let Some(t_id) = mesh.get_halfedge(out_he).and_then(|h| h.twin)
&& !deleted_set.contains(&t_id)
{
to_update.push(t_id);
}
}
let p1_twin = mesh
.get_halfedge(p1)
.ok_or(TopologyError::InvalidHalfEdge(p1))?
.twin
.ok_or_else(|| TopologyError::Inconsistent("p1.twin is None".into()))?;
let n1_twin = mesh
.get_halfedge(n1)
.ok_or(TopologyError::InvalidHalfEdge(n1))?
.twin
.ok_or_else(|| TopologyError::Inconsistent("n1.twin is None".into()))?;
let n2_twin = mesh
.get_halfedge(n2)
.ok_or(TopologyError::InvalidHalfEdge(n2))?
.twin
.ok_or_else(|| TopologyError::Inconsistent("n2.twin is None".into()))?;
let p2_twin = mesh
.get_halfedge(p2)
.ok_or(TopologyError::InvalidHalfEdge(p2))?
.twin
.ok_or_else(|| TopologyError::Inconsistent("p2.twin is None".into()))?;
let new_pos = match target_pos {
Some(p) => p,
None => {
let pos_a = mesh
.get_vertex(a)
.ok_or_else(|| TopologyError::Inconsistent("顶点 A 不存在".into()))?
.position;
let pos_b = mesh
.get_vertex(b)
.ok_or_else(|| TopologyError::Inconsistent("顶点 B 不存在".into()))?
.position;
[
(pos_a[0] + pos_b[0]) * 0.5,
(pos_a[1] + pos_b[1]) * 0.5,
(pos_a[2] + pos_b[2]) * 0.5,
]
}
};
let k = mesh.add_vertex(Vertex::new(new_pos));
{
let p1t = mesh.get_halfedge_mut(p1_twin).unwrap();
p1t.twin = Some(n1_twin);
let n1t = mesh.get_halfedge_mut(n1_twin).unwrap();
n1t.twin = Some(p1_twin);
}
{
let n2t = mesh.get_halfedge_mut(n2_twin).unwrap();
n2t.twin = Some(p2_twin);
let p2t = mesh.get_halfedge_mut(p2_twin).unwrap();
p2t.twin = Some(n2_twin);
}
for he_id in &to_update {
let h_mut = mesh.get_halfedge_mut(*he_id).unwrap();
if h_mut.vertex == a || h_mut.vertex == b {
h_mut.vertex = k;
}
}
mesh.get_vertex_mut(k).unwrap().halfedge = Some(p1_twin);
let c_out = mesh.get_vertex(c).unwrap().halfedge;
if c_out == Some(n1) || c_out == Some(p1) || c_out.is_none() {
mesh.get_vertex_mut(c).unwrap().halfedge = Some(n1_twin);
}
let d_out = mesh.get_vertex(d).unwrap().halfedge;
if d_out == Some(n2) || d_out == Some(p2) || d_out.is_none() {
mesh.get_vertex_mut(d).unwrap().halfedge = Some(n2_twin);
}
mesh.remove_halfedge(he);
mesh.remove_halfedge(twin_id);
mesh.remove_halfedge(n1);
mesh.remove_halfedge(p1);
mesh.remove_halfedge(n2);
mesh.remove_halfedge(p2);
mesh.remove_face(f1);
mesh.remove_face(f2);
mesh.remove_vertex(a);
mesh.remove_vertex(b);
Ok(k)
}
fn check_link_condition(
mesh: &MeshStorage,
a: VertexId,
b: VertexId,
c: VertexId,
d: VertexId,
) -> bool {
let neighbors_a: HashSet<VertexId> = VertexAdjacentVerts::new(mesh, a).collect();
let neighbors_b: HashSet<VertexId> = VertexAdjacentVerts::new(mesh, b).collect();
let common: HashSet<&VertexId> = neighbors_a.intersection(&neighbors_b).collect();
common.len() == 2 && common.contains(&c) && common.contains(&d)
}
pub fn extrude_face(
mesh: &mut MeshStorage,
face: FaceId,
offset: [f64; 3],
) -> Result<Vec<FaceId>, TopologyError> {
let offset_len = (offset[0].powi(2) + offset[1].powi(2) + offset[2].powi(2)).sqrt();
if offset_len < 1e-12 {
return Err(TopologyError::DegenerateTriangle);
}
if !mesh.contains_face(face) {
return Err(TopologyError::Inconsistent(format!("面 {:?} 不存在", face)));
}
let hs: Vec<HalfEdgeId> = FaceHalfEdges::new(mesh, face).collect();
if hs.len() != 3 {
return Err(TopologyError::Inconsistent(format!(
"面 {:?} 边界环长度={}, 非三角面",
face,
hs.len()
)));
}
let h_arr: [HalfEdgeId; 3] = [hs[0], hs[1], hs[2]];
let h0d = mesh
.get_halfedge(h_arr[0])
.ok_or(TopologyError::InvalidHalfEdge(h_arr[0]))?
.clone();
let h1d = mesh
.get_halfedge(h_arr[1])
.ok_or(TopologyError::InvalidHalfEdge(h_arr[1]))?
.clone();
let h2d = mesh
.get_halfedge(h_arr[2])
.ok_or(TopologyError::InvalidHalfEdge(h_arr[2]))?
.clone();
let v: [VertexId; 3] = [h2d.vertex, h0d.vertex, h1d.vertex];
let pos: [[f64; 3]; 3] = {
let mut arr = [[0.0f64; 3]; 3];
for i in 0..3 {
arr[i] = mesh
.get_vertex(v[i])
.ok_or_else(|| TopologyError::Inconsistent(format!("顶点 {:?} 不存在", v[i])))?
.position;
}
arr
};
let t: [HalfEdgeId; 3] = [
h0d.twin.ok_or(TopologyError::NoTwin(h_arr[0]))?,
h1d.twin.ok_or(TopologyError::NoTwin(h_arr[1]))?,
h2d.twin.ok_or(TopologyError::NoTwin(h_arr[2]))?,
];
for &item in &t {
let td = mesh
.get_halfedge(item)
.ok_or(TopologyError::InvalidHalfEdge(item))?;
if td.face.is_some() {
return Err(TopologyError::Inconsistent(
"extrude_face 仅支持所有边为边界边的面".into(),
));
}
}
for i in 0..3 {
let edge = [
pos[(i + 1) % 3][0] - pos[i][0],
pos[(i + 1) % 3][1] - pos[i][1],
pos[(i + 1) % 3][2] - pos[i][2],
];
let cross = [
edge[1] * offset[2] - edge[2] * offset[1],
edge[2] * offset[0] - edge[0] * offset[2],
edge[0] * offset[1] - edge[1] * offset[0],
];
let area = 0.5 * (cross[0].powi(2) + cross[1].powi(2) + cross[2].powi(2)).sqrt();
if area < 1e-12 {
return Err(TopologyError::DegenerateTriangle);
}
}
let vp: [VertexId; 3] = [
mesh.add_vertex(Vertex::new([
pos[0][0] + offset[0],
pos[0][1] + offset[1],
pos[0][2] + offset[2],
])),
mesh.add_vertex(Vertex::new([
pos[1][0] + offset[0],
pos[1][1] + offset[1],
pos[1][2] + offset[2],
])),
mesh.add_vertex(Vertex::new([
pos[2][0] + offset[0],
pos[2][1] + offset[1],
pos[2][2] + offset[2],
])),
];
let tp: [HalfEdgeId; 3] = [
mesh.add_halfedge(HalfEdge::new(vp[1])), mesh.add_halfedge(HalfEdge::new(vp[0])), mesh.add_halfedge(HalfEdge::new(vp[2])), ];
let a: [HalfEdgeId; 3] = [
mesh.add_halfedge(HalfEdge::new(vp[0])), mesh.add_halfedge(HalfEdge::new(vp[1])), mesh.add_halfedge(HalfEdge::new(vp[2])), ];
let b: [HalfEdgeId; 3] = [
mesh.add_halfedge(HalfEdge::new(v[1])), mesh.add_halfedge(HalfEdge::new(v[2])), mesh.add_halfedge(HalfEdge::new(v[0])), ];
let c: [HalfEdgeId; 3] = [
mesh.add_halfedge(HalfEdge::new(vp[0])), mesh.add_halfedge(HalfEdge::new(vp[1])), mesh.add_halfedge(HalfEdge::new(vp[2])), ];
let d: [HalfEdgeId; 3] = [
mesh.add_halfedge(HalfEdge::new(vp[1])), mesh.add_halfedge(HalfEdge::new(vp[2])), mesh.add_halfedge(HalfEdge::new(vp[0])), ];
let e: [HalfEdgeId; 3] = [
mesh.add_halfedge(HalfEdge::new(v[1])), mesh.add_halfedge(HalfEdge::new(v[2])), mesh.add_halfedge(HalfEdge::new(v[0])), ];
let f_top = mesh.add_face(Face::new());
let f_t1: [FaceId; 3] = [
mesh.add_face(Face::new()),
mesh.add_face(Face::new()),
mesh.add_face(Face::new()),
];
let f_t2: [FaceId; 3] = [
mesh.add_face(Face::new()),
mesh.add_face(Face::new()),
mesh.add_face(Face::new()),
];
let tp_twin: [HalfEdgeId; 3] = [d[1], d[0], d[2]];
let d_twin: [HalfEdgeId; 3] = [tp[1], tp[0], tp[2]];
for i in 0..3 {
let he = mesh.get_halfedge_mut(tp[i]).unwrap();
he.twin = Some(tp_twin[i]);
he.next = Some(tp[(i + 1) % 3]);
he.prev = Some(tp[(i + 2) % 3]);
he.face = Some(f_top);
}
mesh.get_face_mut(f_top).unwrap().halfedge = Some(tp[0]);
for i in 0..3 {
{
let he = mesh.get_halfedge_mut(t[i]).unwrap();
he.twin = Some(h_arr[i]); he.next = Some(a[i]);
he.prev = Some(b[i]);
he.face = Some(f_t1[i]);
let he = mesh.get_halfedge_mut(a[i]).unwrap();
he.twin = Some(e[(i + 2) % 3]); he.next = Some(b[i]);
he.prev = Some(t[i]);
he.face = Some(f_t1[i]);
let he = mesh.get_halfedge_mut(b[i]).unwrap();
he.twin = Some(c[i]); he.next = Some(t[i]);
he.prev = Some(a[i]);
he.face = Some(f_t1[i]);
}
mesh.get_face_mut(f_t1[i]).unwrap().halfedge = Some(t[i]);
{
let he = mesh.get_halfedge_mut(c[i]).unwrap();
he.twin = Some(b[i]); he.next = Some(d[i]);
he.prev = Some(e[i]);
he.face = Some(f_t2[i]);
let he = mesh.get_halfedge_mut(d[i]).unwrap();
he.twin = Some(d_twin[i]); he.next = Some(e[i]);
he.prev = Some(c[i]);
he.face = Some(f_t2[i]);
let he = mesh.get_halfedge_mut(e[i]).unwrap();
he.twin = Some(a[(i + 1) % 3]); he.next = Some(c[i]);
he.prev = Some(d[i]);
he.face = Some(f_t2[i]);
}
mesh.get_face_mut(f_t2[i]).unwrap().halfedge = Some(c[i]);
}
for i in 0..3 {
mesh.get_vertex_mut(vp[i]).unwrap().halfedge = Some(d[i]);
}
let errors = crate::validate::validate_topology(mesh);
if !errors.is_empty() {
return Err(TopologyError::Inconsistent(format!(
"extrude_face 后校验失败:{:?}",
errors
)));
}
Ok(vec![
f_top, f_t1[0], f_t2[0], f_t1[1], f_t2[1], f_t1[2], f_t2[2],
])
}
pub fn extrude_faces(
mesh: &mut MeshStorage,
faces: &[FaceId],
offset: [f64; 3],
) -> Result<Vec<FaceId>, TopologyError> {
let mut all_new = Vec::new();
for &f in faces {
let new = extrude_face(mesh, f, offset)?;
all_new.extend(new);
}
Ok(all_new)
}
pub fn extrude_region(
mesh: &mut MeshStorage,
faces: &[FaceId],
offset: [f64; 3],
) -> Result<Vec<FaceId>, TopologyError> {
let offset_len = (offset[0].powi(2) + offset[1].powi(2) + offset[2].powi(2)).sqrt();
if offset_len < 1e-12 {
return Err(TopologyError::DegenerateTriangle);
}
if faces.is_empty() {
return Err(TopologyError::Inconsistent("挤出区域为空".into()));
}
let region_set: HashSet<FaceId> = faces.iter().copied().collect();
if region_set.len() != faces.len() {
return Err(TopologyError::Inconsistent("挤出区域包含重复面".into()));
}
for &f in faces {
if !mesh.contains_face(f) {
return Err(TopologyError::Inconsistent(format!("面 {:?} 不存在", f)));
}
}
let mut region_face_hes: Vec<(FaceId, [HalfEdgeId; 3])> = Vec::new();
let mut region_verts_set: HashSet<VertexId> = HashSet::new();
for &f in faces {
let hes: Vec<HalfEdgeId> = FaceHalfEdges::new(mesh, f).collect();
if hes.len() != 3 {
return Err(TopologyError::Inconsistent(format!(
"面 {:?} 边界环长度={}, 非三角面",
f,
hes.len()
)));
}
for &h in &hes {
let v_t = mesh
.get_halfedge(h)
.ok_or(TopologyError::InvalidHalfEdge(h))?
.vertex;
region_verts_set.insert(v_t);
}
region_face_hes.push((f, [hes[0], hes[1], hes[2]]));
}
let mut vert_map: HashMap<VertexId, VertexId> = HashMap::new();
for &v in ®ion_verts_set {
let adj_faces: Vec<FaceId> = VertexAdjacentFaces::new(mesh, v).collect();
let all_in_region =
!adj_faces.is_empty() && adj_faces.iter().all(|f| region_set.contains(f));
if all_in_region {
let pos = mesh
.get_vertex(v)
.ok_or_else(|| TopologyError::Inconsistent(format!("顶点 {:?} 不存在", v)))?
.position;
mesh.get_vertex_mut(v).unwrap().position =
[pos[0] + offset[0], pos[1] + offset[1], pos[2] + offset[2]];
vert_map.insert(v, v);
} else {
let pos = mesh
.get_vertex(v)
.ok_or_else(|| TopologyError::Inconsistent(format!("顶点 {:?} 不存在", v)))?
.position;
let v_new = mesh.add_vertex(Vertex::new([
pos[0] + offset[0],
pos[1] + offset[1],
pos[2] + offset[2],
]));
vert_map.insert(v, v_new);
}
}
let mut boundary_hes: Vec<(HalfEdgeId, VertexId, VertexId, FaceId)> = Vec::new();
let mut interior_hes: HashSet<HalfEdgeId> = HashSet::new();
for &(_f, hes) in ®ion_face_hes {
for &h in &hes {
let h_data = mesh
.get_halfedge(h)
.ok_or(TopologyError::InvalidHalfEdge(h))?;
let twin_id = h_data
.twin
.ok_or_else(|| TopologyError::Inconsistent(format!("半边 {:?} 无 twin", h)))?;
let twin_data = mesh
.get_halfedge(twin_id)
.ok_or(TopologyError::InvalidHalfEdge(twin_id))?;
let v_t = h_data.vertex;
let v_o = twin_data.vertex;
match twin_data.face {
Some(twin_face) if region_set.contains(&twin_face) => {
interior_hes.insert(h);
}
_ => {
boundary_hes.push((h, v_o, v_t, _f));
}
}
}
}
for &(_h, v_o, v_t, _f) in &boundary_hes {
let pos_o = mesh
.get_vertex(v_o)
.ok_or_else(|| TopologyError::Inconsistent(format!("顶点 {:?} 不存在", v_o)))?
.position;
let pos_t = mesh
.get_vertex(v_t)
.ok_or_else(|| TopologyError::Inconsistent(format!("顶点 {:?} 不存在", v_t)))?
.position;
let edge = [
pos_t[0] - pos_o[0],
pos_t[1] - pos_o[1],
pos_t[2] - pos_o[2],
];
let cross = [
edge[1] * offset[2] - edge[2] * offset[1],
edge[2] * offset[0] - edge[0] * offset[2],
edge[0] * offset[1] - edge[1] * offset[0],
];
let area = 0.5 * (cross[0].powi(2) + cross[1].powi(2) + cross[2].powi(2)).sqrt();
if area < 1e-12 {
return Err(TopologyError::DegenerateTriangle);
}
}
let mut vert_he: HashMap<VertexId, (HalfEdgeId, HalfEdgeId)> = HashMap::new();
for &(_h, v_o, v_t, _f) in &boundary_hes {
for &v in &[v_o, v_t] {
vert_he.entry(v).or_insert_with(|| {
let v_new = vert_map[&v];
let up = mesh.add_halfedge(HalfEdge::new(v_new)); let down = mesh.add_halfedge(HalfEdge::new(v)); mesh.get_halfedge_mut(up).unwrap().twin = Some(down);
mesh.get_halfedge_mut(down).unwrap().twin = Some(up);
(up, down)
});
}
}
for &h in &interior_hes {
let v_t = mesh.get_halfedge(h).unwrap().vertex;
let new_v = vert_map[&v_t];
mesh.get_halfedge_mut(h).unwrap().vertex = new_v;
}
let mut top_he_map: HashMap<HalfEdgeId, HalfEdgeId> = HashMap::new();
let mut s_top_map: HashMap<HalfEdgeId, HalfEdgeId> = HashMap::new(); for &(h, v_o, v_t, _f) in &boundary_hes {
let v_t_new = vert_map[&v_t];
let v_o_new = vert_map[&v_o];
let h_top = mesh.add_halfedge(HalfEdge::new(v_t_new)); let s_top = mesh.add_halfedge(HalfEdge::new(v_o_new)); mesh.get_halfedge_mut(h_top).unwrap().twin = Some(s_top);
mesh.get_halfedge_mut(s_top).unwrap().twin = Some(h_top);
top_he_map.insert(h, h_top);
s_top_map.insert(h, s_top);
}
for &(f, hes) in ®ion_face_hes {
let [h0, h1, h2] = hes;
let r0 = *top_he_map.get(&h0).unwrap_or(&h0);
let r1 = *top_he_map.get(&h1).unwrap_or(&h1);
let r2 = *top_he_map.get(&h2).unwrap_or(&h2);
let r0_he = mesh.get_halfedge_mut(r0).unwrap();
r0_he.next = Some(r1);
r0_he.prev = Some(r2);
r0_he.face = Some(f);
let r1_he = mesh.get_halfedge_mut(r1).unwrap();
r1_he.next = Some(r2);
r1_he.prev = Some(r0);
r1_he.face = Some(f);
let r2_he = mesh.get_halfedge_mut(r2).unwrap();
r2_he.next = Some(r0);
r2_he.prev = Some(r1);
r2_he.face = Some(f);
mesh.get_face_mut(f).unwrap().halfedge = Some(r0);
for &h in &hes {
if top_he_map.contains_key(&h) {
let h_he = mesh.get_halfedge_mut(h).unwrap();
h_he.next = None;
h_he.prev = None;
h_he.face = None;
}
}
}
let mut new_faces = Vec::new();
for &(h, v_o, v_t, _f) in &boundary_hes {
let v_t_new = vert_map[&v_t];
let s_top = s_top_map[&h];
let (up_t, _down_t) = vert_he[&v_t]; let (_up_o, down_o) = vert_he[&v_o];
let diag = mesh.add_halfedge(HalfEdge::new(v_t_new)); let diag_rev = mesh.add_halfedge(HalfEdge::new(v_o)); mesh.get_halfedge_mut(diag).unwrap().twin = Some(diag_rev);
mesh.get_halfedge_mut(diag_rev).unwrap().twin = Some(diag);
let f_t1 = mesh.add_face(Face::new());
{
let he = mesh.get_halfedge_mut(h).unwrap();
he.next = Some(up_t);
he.prev = Some(diag_rev);
he.face = Some(f_t1);
let he = mesh.get_halfedge_mut(up_t).unwrap();
he.next = Some(diag_rev);
he.prev = Some(h);
he.face = Some(f_t1);
let he = mesh.get_halfedge_mut(diag_rev).unwrap();
he.next = Some(h);
he.prev = Some(up_t);
he.face = Some(f_t1);
}
mesh.get_face_mut(f_t1).unwrap().halfedge = Some(h);
let f_t2 = mesh.add_face(Face::new());
{
let he = mesh.get_halfedge_mut(diag).unwrap();
he.next = Some(s_top);
he.prev = Some(down_o);
he.face = Some(f_t2);
let he = mesh.get_halfedge_mut(s_top).unwrap();
he.next = Some(down_o);
he.prev = Some(diag);
he.face = Some(f_t2);
let he = mesh.get_halfedge_mut(down_o).unwrap();
he.next = Some(diag);
he.prev = Some(s_top);
he.face = Some(f_t2);
}
mesh.get_face_mut(f_t2).unwrap().halfedge = Some(diag);
new_faces.push(f_t1);
new_faces.push(f_t2);
}
for &(_h, v_o, v_t, _f) in &boundary_hes {
for &v in &[v_o, v_t] {
let (up, down) = vert_he[&v];
mesh.get_vertex_mut(v).unwrap().halfedge = Some(up);
let v_new = vert_map[&v];
mesh.get_vertex_mut(v_new).unwrap().halfedge = Some(down);
}
}
let errors = crate::validate::validate_topology(mesh);
if !errors.is_empty() {
return Err(TopologyError::Inconsistent(format!(
"extrude_region 后校验失败:{:?}",
errors
)));
}
Ok(new_faces)
}
pub fn add_triangle(
mesh: &mut MeshStorage,
v0: VertexId,
v1: VertexId,
v2: VertexId,
) -> Result<FaceId, TopologyError> {
if v0 == v1 || v1 == v2 || v0 == v2 {
return Err(TopologyError::DegenerateTriangle);
}
if !mesh.contains_vertex(v0) || !mesh.contains_vertex(v1) || !mesh.contains_vertex(v2) {
return Err(TopologyError::Inconsistent("顶点不存在".into()));
}
let h0 = mesh.add_halfedge(HalfEdge::new(v1)); let h1 = mesh.add_halfedge(HalfEdge::new(v2)); let h2 = mesh.add_halfedge(HalfEdge::new(v0));
for (he, next, prev) in [(h0, h1, h2), (h1, h2, h0), (h2, h0, h1)] {
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] {
mesh.get_halfedge_mut(he).unwrap().face = Some(face);
}
let edges = [(h0, v0, v1), (h1, v1, v2), (h2, v2, v0)];
for (he, src, dst) in edges {
let existing: Option<HalfEdgeId> = {
let mut found = None;
for ehe in mesh.halfedge_ids() {
if ehe == h0 || ehe == h1 || ehe == h2 {
continue;
}
let h = mesh.get_halfedge(ehe).unwrap();
if h.vertex != src {
continue;
}
if let Some(twin_id) = h.twin
&& let Some(twin_data) = mesh.get_halfedge(twin_id)
&& twin_data.vertex == dst
&& twin_data.face.is_none()
{
found = Some(ehe);
break;
}
}
found
};
match existing {
Some(ex) => {
let old_twin = mesh.get_halfedge(ex).unwrap().twin;
if let Some(old) = old_twin {
mesh.remove_halfedge(old);
}
mesh.get_halfedge_mut(he).unwrap().twin = Some(ex);
mesh.get_halfedge_mut(ex).unwrap().twin = Some(he);
}
None => {
let twin = mesh.add_halfedge(HalfEdge::new(src));
mesh.get_halfedge_mut(he).unwrap().twin = Some(twin);
mesh.get_halfedge_mut(twin).unwrap().twin = Some(he);
}
}
}
for (v, he) in [(v0, h0), (v1, h1), (v2, h2)] {
if mesh.get_vertex(v).unwrap().halfedge.is_none() {
mesh.get_vertex_mut(v).unwrap().halfedge = Some(he);
}
}
validate_mesh(mesh)?;
Ok(face)
}
pub fn split_face(mesh: &mut MeshStorage, face: FaceId) -> Result<VertexId, TopologyError> {
let face_data = mesh
.get_face(face)
.ok_or_else(|| TopologyError::Inconsistent("面不存在".into()))?;
let _start_he = face_data
.halfedge
.ok_or_else(|| TopologyError::Inconsistent("面无半边".into()))?;
let he_ids: Vec<HalfEdgeId> = FaceHalfEdges::new(mesh, face).collect();
if he_ids.len() != 3 {
return Err(TopologyError::Inconsistent("仅支持三角面".into()));
}
let he_data: Vec<_> = he_ids
.iter()
.map(|&h| mesh.get_halfedge(h).unwrap().clone())
.collect();
let verts: Vec<VertexId> = he_data.iter().map(|h| h.vertex).collect();
let [v0, v1, v2] = [verts[0], verts[1], verts[2]];
let [_he0, _he1, _he2] = [he_ids[0], he_ids[1], he_ids[2]];
let old_twins = [he_data[0].twin, he_data[1].twin, he_data[2].twin];
let pos: Vec<[f64; 3]> = verts
.iter()
.map(|&v| mesh.get_vertex(v).unwrap().position)
.collect();
let center = [
(pos[0][0] + pos[1][0] + pos[2][0]) / 3.0,
(pos[0][1] + pos[1][1] + pos[2][1]) / 3.0,
(pos[0][2] + pos[1][2] + pos[2][2]) / 3.0,
];
let m = mesh.add_vertex(Vertex::new(center));
mesh.remove_face(face);
for &he in &he_ids {
mesh.remove_halfedge(he);
}
let mut new_faces = Vec::new();
for (i, &(_, src, dst)) in [(0, v0, v1), (1, v1, v2), (2, v2, v0)].iter().enumerate() {
let spoke_out = mesh.add_halfedge(HalfEdge::new(src)); let spoke_in = mesh.add_halfedge(HalfEdge::new(m));
mesh.get_halfedge_mut(spoke_out).unwrap().twin = Some(spoke_in);
mesh.get_halfedge_mut(spoke_in).unwrap().twin = Some(spoke_out);
let outer = mesh.add_halfedge(HalfEdge::new(dst));
if let Some(old_twin) = old_twins[(i + 1) % 3] {
mesh.get_halfedge_mut(outer).unwrap().twin = Some(old_twin);
if let Some(t) = mesh.get_halfedge_mut(old_twin) {
t.twin = Some(outer);
}
}
mesh.get_halfedge_mut(spoke_out).unwrap().next = Some(outer);
mesh.get_halfedge_mut(spoke_out).unwrap().prev = Some(spoke_in);
mesh.get_halfedge_mut(outer).unwrap().next = Some(spoke_in);
mesh.get_halfedge_mut(outer).unwrap().prev = Some(spoke_out);
mesh.get_halfedge_mut(spoke_in).unwrap().next = Some(spoke_out);
mesh.get_halfedge_mut(spoke_in).unwrap().prev = Some(outer);
let new_f = mesh.add_face(Face::new());
mesh.get_face_mut(new_f).unwrap().halfedge = Some(spoke_out);
for &he in &[spoke_out, outer, spoke_in] {
mesh.get_halfedge_mut(he).unwrap().face = Some(new_f);
}
new_faces.push(new_f);
}
mesh.get_vertex_mut(m).unwrap().halfedge = Some(
mesh.get_face(new_faces[0])
.and_then(|f| f.halfedge)
.unwrap(),
);
validate_mesh(mesh)?;
Ok(m)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::ids::FaceId;
use crate::storage::{Face, HalfEdge, MeshStorage, Vertex};
use crate::traversal::{FaceHalfEdges, VertexRing, is_boundary_edge, is_boundary_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)
}
fn build_two_triangles() -> (MeshStorage, [VertexId; 4], [HalfEdgeId; 10], 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],
[h0, h1, h2, g0, g1, g2, t1, t2, t_g1, t_g2],
f1,
f2,
)
}
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),
(a2, c1, b2, c2, f2),
(b2, t2, c2, a2, f2),
(c2, a3, a2, b2, f2),
(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 validate_clean_meshes() {
let (m1, _, _, _) = build_triangle();
assert!(validate_mesh(&m1).is_ok());
let (m2, _, _, _, _) = build_two_triangles();
assert!(validate_mesh(&m2).is_ok());
let (m3, _, _, _) = build_closed_fan();
assert!(validate_mesh(&m3).is_ok());
}
#[test]
fn split_boundary_edge_of_single_triangle() {
let (mut mesh, v, he, _f) = build_triangle();
let [v0, v1, _v2] = v;
let [h0, _h1, _h2, _t0, _t1, _t2] = he;
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let m = split_edge(&mut mesh, h0).expect("分裂边界边应成功");
assert_eq!(mesh.vertex_count(), v_before + 1);
assert_eq!(mesh.face_count(), f_before + 1);
assert_eq!(mesh.halfedge_count(), he_before + 4);
assert!(mesh.contains_vertex(m));
assert!(!mesh.contains_halfedge(h0));
assert!(validate_mesh(&mesh).is_ok());
let pos_m = mesh.get_vertex(m).unwrap().position;
let pos_v0 = mesh.get_vertex(v0).unwrap().position;
let pos_v1 = mesh.get_vertex(v1).unwrap().position;
assert!((pos_m[0] - (pos_v0[0] + pos_v1[0]) / 2.0).abs() < 1e-12);
assert_eq!(mesh.face_count(), 2);
for f in mesh.face_ids().collect::<Vec<_>>() {
let count = FaceHalfEdges::new(&mesh, f).count();
assert_eq!(count, 3, "折叠后面应为三角面");
}
}
#[test]
fn split_interior_edge_of_two_triangles() {
let (mut mesh, _v, he, _f1, _f2) = build_two_triangles();
let [h0, _h1, _h2, _g0, _g1, _g2, _t1, _t2, _t_g1, _t_g2] = he;
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let m = split_edge(&mut mesh, h0).expect("分裂内部边应成功");
assert_eq!(mesh.vertex_count(), v_before + 1);
assert_eq!(mesh.face_count(), f_before + 2);
assert_eq!(mesh.halfedge_count(), he_before + 6);
assert!(mesh.contains_vertex(m));
assert!(!mesh.contains_halfedge(h0));
assert!(validate_mesh(&mesh).is_ok());
for f in mesh.face_ids().collect::<Vec<_>>() {
let count = FaceHalfEdges::new(&mesh, f).count();
assert_eq!(count, 3, "分裂后面应为三角面");
}
}
#[test]
fn split_boundary_edge_passed_as_boundary_halfedge() {
let (mut mesh, _v, he, _f) = build_triangle();
let [h0, _h1, _h2, t0, _t1, _t2] = he;
let m = split_edge(&mut mesh, t0).expect("传入边界半边应自动转为操作 twin");
assert!(mesh.contains_vertex(m));
assert!(!mesh.contains_halfedge(h0));
assert!(!mesh.contains_halfedge(t0));
assert!(validate_mesh(&mesh).is_ok());
}
#[test]
fn flip_interior_edge_of_two_triangles() {
let (mut mesh, v, he, _f1, _f2) = build_two_triangles();
let [_v0, _v1, v2, _v3] = v;
let [h0, _h1, _h2, _g0, _g1, _g2, _t1, _t2, _t_g1, _t_g2] = he;
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
flip_edge(&mut mesh, h0).expect("翻转内部边应成功");
assert_eq!(mesh.vertex_count(), v_before);
assert_eq!(mesh.face_count(), f_before);
assert_eq!(mesh.halfedge_count(), he_before);
assert!(validate_mesh(&mesh).is_ok());
let h = mesh.get_halfedge(h0).unwrap();
assert_eq!(h.vertex, v2, "翻转后 h.vertex 应为 C=v2");
for f in mesh.face_ids().collect::<Vec<_>>() {
let count = FaceHalfEdges::new(&mesh, f).count();
assert_eq!(count, 3, "翻转后面应为三角面");
}
}
#[test]
fn flip_boundary_edge_fails() {
let (mut mesh, _v, he, _f) = build_triangle();
let [h0, _h1, _h2, _t0, _t1, _t2] = he;
let err = flip_edge(&mut mesh, h0).unwrap_err();
assert_eq!(err, TopologyError::FlipOnBoundaryEdge(h0));
}
#[test]
fn flip_then_flip_restores_topology() {
let (mut mesh, _v, he, _f1, _f2) = build_two_triangles();
let [h0, _, _, _, _, _, _, _, _, _] = he;
flip_edge(&mut mesh, h0).expect("第一次翻转");
assert!(validate_mesh(&mesh).is_ok());
flip_edge(&mut mesh, h0).expect("第二次翻转恢复");
assert!(validate_mesh(&mesh).is_ok());
assert_eq!(mesh.face_count(), 2);
}
#[test]
fn collapse_interior_edge_of_closed_fan() {
let (mut mesh, c, outer, _faces) = build_closed_fan();
let [v0, _v1, _v2] = outer;
let a1 = VertexRing::new(&mesh, c)
.find(|he| mesh.get_halfedge(*he).unwrap().vertex == v0)
.expect("c→v0 半边必须存在");
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let k = collapse_edge(&mut mesh, a1).expect("折叠内部边应成功");
assert_eq!(mesh.vertex_count(), v_before - 1);
assert_eq!(mesh.face_count(), f_before - 2);
assert_eq!(mesh.halfedge_count(), he_before - 6);
assert!(mesh.contains_vertex(k));
assert!(!mesh.contains_vertex(c));
assert!(!mesh.contains_vertex(v0));
assert!(validate_mesh(&mesh).is_ok());
assert_eq!(mesh.face_count(), 1);
for f in mesh.face_ids().collect::<Vec<_>>() {
let count = FaceHalfEdges::new(&mesh, f).count();
assert_eq!(count, 3, "折叠后面应为三角面");
}
}
#[test]
fn collapse_boundary_edge_fails() {
let (mut mesh, _v, he, _f) = build_triangle();
let [h0, _h1, _h2, _t0, _t1, _t2] = he;
let err = collapse_edge(&mut mesh, h0).unwrap_err();
assert_eq!(err, TopologyError::CollapseOnBoundaryEdge(h0));
}
fn build_link_violation_mesh() -> (MeshStorage, VertexId, VertexId) {
let mut mesh = MeshStorage::new();
let a = mesh.add_vertex(Vertex::new([0.0, 0.0, 0.0]));
let b = mesh.add_vertex(Vertex::new([1.0, 0.0, 0.0]));
let c = mesh.add_vertex(Vertex::new([0.5, -1.0, 0.0]));
let d = mesh.add_vertex(Vertex::new([0.5, 0.0, 1.0]));
let e = mesh.add_vertex(Vertex::new([0.5, 1.0, 0.0]));
let h_ab = mesh.add_halfedge(HalfEdge::new(b)); let h_bc = mesh.add_halfedge(HalfEdge::new(c)); let h_ca = mesh.add_halfedge(HalfEdge::new(a)); let h_ba = mesh.add_halfedge(HalfEdge::new(a)); let h_ad = mesh.add_halfedge(HalfEdge::new(d)); let h_db = mesh.add_halfedge(HalfEdge::new(b)); let h_ac = mesh.add_halfedge(HalfEdge::new(c)); let h_ce = mesh.add_halfedge(HalfEdge::new(e)); let h_ea = mesh.add_halfedge(HalfEdge::new(a)); let h_bd = mesh.add_halfedge(HalfEdge::new(d)); let h_de = mesh.add_halfedge(HalfEdge::new(e)); let h_eb = mesh.add_halfedge(HalfEdge::new(b)); let t_bc = mesh.add_halfedge(HalfEdge::new(b)); let t_ad = mesh.add_halfedge(HalfEdge::new(a)); let t_ce = mesh.add_halfedge(HalfEdge::new(c)); let t_ea = mesh.add_halfedge(HalfEdge::new(e)); let t_de = mesh.add_halfedge(HalfEdge::new(d)); let t_eb = mesh.add_halfedge(HalfEdge::new(e));
let f1 = mesh.add_face(Face::new());
let f2 = mesh.add_face(Face::new());
let f3 = mesh.add_face(Face::new());
let f4 = mesh.add_face(Face::new());
for (he, twin, next, prev) in [
(h_ab, h_ba, h_bc, h_ca),
(h_bc, t_bc, h_ca, h_ab),
(h_ca, h_ac, h_ab, h_bc),
] {
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 [
(h_ba, h_ab, h_ad, h_db),
(h_ad, t_ad, h_db, h_ba),
(h_db, h_bd, h_ba, h_ad),
] {
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 (he, twin, next, prev) in [
(h_ac, h_ca, h_ce, h_ea),
(h_ce, t_ce, h_ea, h_ac),
(h_ea, t_ea, h_ac, h_ce),
] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(f3);
}
for (he, twin, next, prev) in [
(h_bd, h_db, h_de, h_eb),
(h_de, t_de, h_eb, h_bd),
(h_eb, t_eb, h_bd, h_de),
] {
let h = mesh.get_halfedge_mut(he).unwrap();
h.twin = Some(twin);
h.next = Some(next);
h.prev = Some(prev);
h.face = Some(f4);
}
for (t, he) in [
(t_bc, h_bc),
(t_ad, h_ad),
(t_ce, h_ce),
(t_ea, h_ea),
(t_de, h_de),
(t_eb, h_eb),
] {
mesh.get_halfedge_mut(t).unwrap().twin = Some(he);
}
mesh.get_vertex_mut(a).unwrap().halfedge = Some(h_ab);
mesh.get_vertex_mut(b).unwrap().halfedge = Some(h_ba);
mesh.get_vertex_mut(c).unwrap().halfedge = Some(h_ca);
mesh.get_vertex_mut(d).unwrap().halfedge = Some(h_db);
mesh.get_vertex_mut(e).unwrap().halfedge = Some(h_ea);
mesh.get_face_mut(f1).unwrap().halfedge = Some(h_ab);
mesh.get_face_mut(f2).unwrap().halfedge = Some(h_ba);
mesh.get_face_mut(f3).unwrap().halfedge = Some(h_ac);
mesh.get_face_mut(f4).unwrap().halfedge = Some(h_bd);
(mesh, a, b)
}
#[test]
fn collapse_with_violated_link_condition_fails() {
let (mut mesh, a, b) = build_link_violation_mesh();
assert!(validate_mesh(&mesh).is_ok());
let h_ab = VertexRing::new(&mesh, a)
.find(|he| mesh.get_halfedge(*he).unwrap().vertex == b)
.expect("A→B 半边必须存在");
let err = collapse_edge(&mut mesh, h_ab).unwrap_err();
assert_eq!(
err,
TopologyError::LinkConditionViolated { a, b },
"公共邻居 {{C,D,E}} != {{C,D}} → 应返回链接条件违反"
);
assert!(validate_mesh(&mesh).is_ok());
assert!(mesh.contains_vertex(a));
assert!(mesh.contains_vertex(b));
}
#[test]
fn collapse_link_condition_check_function() {
let (mesh, c, outer, _faces) = build_closed_fan();
let [v0, v1, v2] = outer;
assert!(check_link_condition(&mesh, c, v0, v1, v2));
assert!(!check_link_condition(&mesh, c, v0, v1, v0));
}
#[test]
fn split_then_validate_all_faces_triangular() {
let (mut mesh, _v, he, _f1, _f2) = build_two_triangles();
let [h0, _h1, _, _, _, _, _, _, _, _] = he;
split_edge(&mut mesh, h0).unwrap();
assert!(validate_mesh(&mesh).is_ok());
let boundary_he = mesh
.halfedge_ids()
.find(|he| is_boundary_edge(&mesh, *he))
.expect("应存在边界半边");
split_edge(&mut mesh, boundary_he).unwrap();
assert!(validate_mesh(&mesh).is_ok());
for f in mesh.face_ids().collect::<Vec<_>>() {
assert_eq!(FaceHalfEdges::new(&mesh, f).count(), 3);
}
}
#[test]
fn flip_preserves_boundary_vertices() {
let (mut mesh, v, he, _f1, _f2) = build_two_triangles();
let [v0, v1, v2, v3] = v;
let [h0, _, _, _, _, _, _, _, _, _] = he;
flip_edge(&mut mesh, h0).unwrap();
for vi in [v0, v1, v2, v3] {
assert!(
is_boundary_vertex(&mesh, vi),
"翻转后顶点 {:?} 应仍为边界顶点",
vi
);
}
}
#[test]
fn collapse_then_remaining_mesh_valid() {
let (mut mesh, c, outer, _faces) = build_closed_fan();
let [v0, v1, v2] = outer;
let a1 = VertexRing::new(&mesh, c)
.find(|he| mesh.get_halfedge(*he).unwrap().vertex == v0)
.unwrap();
let k = collapse_edge(&mut mesh, a1).expect("折叠应成功");
assert!(is_boundary_vertex(&mesh, k), "折叠后新顶点 K 应为边界顶点");
assert!(mesh.contains_vertex(v1));
assert!(mesh.contains_vertex(v2));
assert!(is_boundary_vertex(&mesh, v1));
assert!(is_boundary_vertex(&mesh, v2));
assert!(validate_mesh(&mesh).is_ok());
}
#[test]
fn collapse_edge_uses_midpoint_position() {
let (mut mesh, c, outer, _faces) = build_closed_fan();
let [v0, _v1, _v2] = outer;
let a1 = VertexRing::new(&mesh, c)
.find(|he| mesh.get_halfedge(*he).unwrap().vertex == v0)
.expect("c→v0 半边必须存在");
let k = collapse_edge(&mut mesh, a1).expect("折叠应成功");
let pos = mesh.get_vertex(k).unwrap().position;
assert_eq!(pos, [0.25, 0.25, 0.0]);
assert!(validate_mesh(&mesh).is_ok());
}
#[test]
fn collapse_edge_at_uses_custom_position() {
let (mut mesh, c, outer, _faces) = build_closed_fan();
let [v0, _v1, _v2] = outer;
let a1 = VertexRing::new(&mesh, c)
.find(|he| mesh.get_halfedge(*he).unwrap().vertex == v0)
.expect("c→v0 半边必须存在");
let target = [1.0, 2.0, 3.0];
let k = collapse_edge_at(&mut mesh, a1, target).expect("折叠应成功");
let pos = mesh.get_vertex(k).unwrap().position;
assert_eq!(pos, target);
assert!(validate_mesh(&mesh).is_ok());
}
#[test]
fn collapse_edge_at_preserves_topology_counts() {
let (mut mesh1, c, outer, _faces) = build_closed_fan();
let [v0, _v1, _v2] = outer;
let a1 = VertexRing::new(&mesh1, c)
.find(|he| mesh1.get_halfedge(*he).unwrap().vertex == v0)
.unwrap();
let (mut mesh2, c2, outer2, _faces2) = build_closed_fan();
let [v0_2, _v1_2, _v2_2] = outer2;
let a1_2 = VertexRing::new(&mesh2, c2)
.find(|he| mesh2.get_halfedge(*he).unwrap().vertex == v0_2)
.unwrap();
let k1 = collapse_edge(&mut mesh1, a1).unwrap();
let k2 = collapse_edge_at(&mut mesh2, a1_2, [10.0, 20.0, 30.0]).unwrap();
assert_eq!(mesh1.vertex_count(), mesh2.vertex_count());
assert_eq!(mesh1.face_count(), mesh2.face_count());
assert_eq!(mesh1.halfedge_count(), mesh2.halfedge_count());
let p1 = mesh1.get_vertex(k1).unwrap().position;
let p2 = mesh2.get_vertex(k2).unwrap().position;
assert_eq!(p1, [0.25, 0.25, 0.0]);
assert_eq!(p2, [10.0, 20.0, 30.0]);
assert!(validate_mesh(&mesh1).is_ok());
assert!(validate_mesh(&mesh2).is_ok());
}
#[test]
fn collapse_edge_at_on_boundary_edge_fails() {
let (mut mesh, _v, he, _f) = build_triangle();
let [h0, _h1, _h2, _t0, _t1, _t2] = he;
let err = collapse_edge_at(&mut mesh, h0, [1.0, 1.0, 1.0]).unwrap_err();
assert_eq!(err, TopologyError::CollapseOnBoundaryEdge(h0));
}
#[test]
fn invalid_halfedge_returns_error() {
let mut mesh = MeshStorage::new();
let fake = HalfEdgeId::default();
assert_eq!(
split_edge(&mut mesh, fake).unwrap_err(),
TopologyError::InvalidHalfEdge(fake)
);
assert_eq!(
flip_edge(&mut mesh, fake).unwrap_err(),
TopologyError::InvalidHalfEdge(fake)
);
assert_eq!(
collapse_edge(&mut mesh, fake).unwrap_err(),
TopologyError::InvalidHalfEdge(fake)
);
}
fn build_two_disjoint_triangles() -> (MeshStorage, [FaceId; 2]) {
use crate::io::build_mesh_from_vertices_and_faces;
let verts = vec![
[0.0, 0.0, 0.0],
[1.0, 0.0, 0.0],
[0.0, 1.0, 0.0],
[5.0, 0.0, 0.0],
[6.0, 0.0, 0.0],
[5.0, 1.0, 0.0],
];
let faces = vec![[0, 1, 2], [3, 4, 5]];
let mesh = build_mesh_from_vertices_and_faces(&verts, &faces);
let fids: Vec<FaceId> = mesh.face_ids().collect();
assert_eq!(fids.len(), 2);
(mesh, [fids[0], fids[1]])
}
#[test]
fn extrude_single_triangle() {
let (mut mesh, v, _he, f) = build_triangle();
let [v0, v1, v2] = v;
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let offset = [0.0, 0.0, 1.0];
let new_faces = extrude_face(&mut mesh, f, offset).expect("挤出应成功");
assert_eq!(mesh.vertex_count(), v_before + 3);
assert_eq!(mesh.face_count(), f_before + 7);
assert_eq!(mesh.halfedge_count(), he_before + 18);
assert_eq!(new_faces.len(), 7);
let errors = crate::validate::validate_topology(&mesh);
assert!(errors.is_empty(), "挤出后拓扑应一致:{:?}", errors);
let mut new_verts: Vec<VertexId> = mesh
.vertex_ids()
.filter(|v| ![v0, v1, v2].contains(v))
.collect();
new_verts.sort_by_key(|x| {
let p = mesh.get_vertex(*x).unwrap().position;
(p[0] * 1000.0) as i64 + (p[1] * 1000.0) as i64 * 1000
});
for v in &new_verts {
let p = mesh.get_vertex(*v).unwrap().position;
assert!((p[2] - 1.0).abs() < 1e-12, "新顶点 z 坐标应为 1.0");
}
assert_eq!(new_verts.len(), 3);
for v in [v0, v1, v2] {
let p = mesh.get_vertex(v).unwrap().position;
assert_eq!(p[2], 0.0);
}
assert!(mesh.contains_face(f));
let chi = mesh.vertex_count() as i64 - (mesh.halfedge_count() / 2) as i64
+ mesh.face_count() as i64;
assert_eq!(chi, 2, "挤出后 Euler 示性数应为 2(闭合三棱柱)");
}
#[test]
fn extrude_zero_offset_returns_error() {
let (mut mesh, _v, _he, f) = build_triangle();
let err = extrude_face(&mut mesh, f, [0.0, 0.0, 0.0]).unwrap_err();
assert_eq!(err, TopologyError::DegenerateTriangle);
}
#[test]
fn extrude_degenerate_side_face_returns_error() {
let (mut mesh, _v, _he, f) = build_triangle();
let err = extrude_face(&mut mesh, f, [1.0, 0.0, 0.0]).unwrap_err();
assert_eq!(err, TopologyError::DegenerateTriangle);
}
#[test]
fn extrude_face_with_internal_edge_returns_error() {
let (mut mesh, _v, _he, f1, _f2) = build_two_triangles();
let err = extrude_face(&mut mesh, f1, [0.0, 0.0, 1.0]).unwrap_err();
match err {
TopologyError::Inconsistent(_) => {}
other => panic!("期望 Inconsistent 错误,得到 {:?}", other),
}
}
#[test]
fn extrude_faces_batch_disjoint() {
let (mut mesh, fs) = build_two_disjoint_triangles();
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let offset = [0.0, 0.0, 2.0];
let new_faces = extrude_faces(&mut mesh, &fs, offset).expect("批量挤出应成功");
assert_eq!(mesh.vertex_count(), v_before + 6);
assert_eq!(mesh.face_count(), f_before + 14);
assert_eq!(mesh.halfedge_count(), he_before + 36);
assert_eq!(new_faces.len(), 14);
let errors = crate::validate::validate_topology(&mesh);
assert!(errors.is_empty(), "批量挤出后拓扑应一致:{:?}", errors);
}
#[test]
fn extrude_face_from_built_mesh() {
use crate::io::build_mesh_from_vertices_and_faces;
let verts = vec![[0.0, 0.0, 0.0], [2.0, 0.0, 0.0], [1.0, 2.0, 0.0]];
let faces = vec![[0, 1, 2]];
let mut mesh = build_mesh_from_vertices_and_faces(&verts, &faces);
let f = mesh.face_ids().next().expect("应有一个面");
let offset = [0.0, 0.0, 3.0];
let new_faces = extrude_face(&mut mesh, f, offset).expect("挤出应成功");
assert_eq!(new_faces.len(), 7);
let errors = crate::validate::validate_topology(&mesh);
assert!(errors.is_empty(), "挤出后拓扑应一致:{:?}", errors);
let mut z_top = 0;
let mut z_bot = 0;
for v in mesh.vertex_ids() {
let p = mesh.get_vertex(v).unwrap().position;
if (p[2] - 3.0).abs() < 1e-12 {
z_top += 1;
} else if p[2].abs() < 1e-12 {
z_bot += 1;
} else {
panic!("意外顶点 z 坐标 {}", p[2]);
}
}
assert_eq!(z_top, 3);
assert_eq!(z_bot, 3);
}
#[test]
fn extrude_face_invalid_face_id() {
let (mut mesh, _v, _he, _f) = build_triangle();
let fake = FaceId::default();
let err = extrude_face(&mut mesh, fake, [0.0, 0.0, 1.0]).unwrap_err();
match err {
TopologyError::Inconsistent(_) => {}
other => panic!("期望 Inconsistent 错误,得到 {:?}", other),
}
}
#[test]
fn extrude_face_preserves_boundary_after_extrude() {
let (mut mesh, _v, _he, f) = build_triangle();
let _ = extrude_face(&mut mesh, f, [0.0, 0.0, 1.0]).expect("挤出应成功");
let boundary_count = mesh
.halfedge_ids()
.filter(|h| {
mesh.get_halfedge(*h)
.map(|he| he.face.is_none())
.unwrap_or(false)
})
.count();
assert_eq!(boundary_count, 0, "挤出形成闭合体后不应有边界半边");
}
fn build_tetrahedron() -> (MeshStorage, [VertexId; 4], [FaceId; 4]) {
use crate::io::build_mesh_from_vertices_and_faces;
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, 2, 1], [0, 1, 3], [0, 3, 2], [1, 2, 3], ];
let mesh = build_mesh_from_vertices_and_faces(&verts, &faces);
let v_ids: Vec<VertexId> = mesh.vertex_ids().collect();
let f_ids: Vec<FaceId> = mesh.face_ids().collect();
assert_eq!(v_ids.len(), 4);
assert_eq!(f_ids.len(), 4);
assert_eq!(mesh.halfedge_count(), 12);
(
mesh,
[v_ids[0], v_ids[1], v_ids[2], v_ids[3]],
[f_ids[0], f_ids[1], f_ids[2], f_ids[3]],
)
}
#[test]
fn extrude_region_single_face_of_tetrahedron() {
let (mut mesh, _v, faces) = build_tetrahedron();
assert!(validate_mesh(&mesh).is_ok());
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let offset = [0.0, 0.0, 0.5];
let new_faces = extrude_region(&mut mesh, &[faces[0]], offset).expect("单面区域挤出应成功");
assert_eq!(new_faces.len(), 6);
assert_eq!(mesh.vertex_count(), v_before + 3);
assert_eq!(mesh.face_count(), f_before + 6);
assert_eq!(mesh.halfedge_count(), he_before + 18);
let errors = crate::validate::validate_topology(&mesh);
assert!(errors.is_empty(), "挤出后拓扑应一致:{:?}", errors);
let chi = mesh.vertex_count() as i64 - (mesh.halfedge_count() / 2) as i64
+ mesh.face_count() as i64;
assert_eq!(chi, 2, "挤出后 Euler 示性数应为 2");
let boundary_count = mesh
.halfedge_ids()
.filter(|h| {
mesh.get_halfedge(*h)
.map(|he| he.face.is_none())
.unwrap_or(false)
})
.count();
assert_eq!(boundary_count, 0, "挤出后应为闭合体");
}
#[test]
fn extrude_region_two_adjacent_faces() {
let (mut mesh, _v, faces) = build_tetrahedron();
assert!(validate_mesh(&mesh).is_ok());
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let offset = [0.5, 0.5, 0.5];
let new_faces = extrude_region(&mut mesh, &[faces[0], faces[1]], offset)
.expect("双相邻面区域挤出应成功");
assert_eq!(new_faces.len(), 8);
assert_eq!(mesh.vertex_count(), v_before + 4);
assert_eq!(mesh.face_count(), f_before + 8);
assert_eq!(mesh.halfedge_count(), he_before + 24);
let errors = crate::validate::validate_topology(&mesh);
assert!(errors.is_empty(), "挤出后拓扑应一致:{:?}", errors);
let chi = mesh.vertex_count() as i64 - (mesh.halfedge_count() / 2) as i64
+ mesh.face_count() as i64;
assert_eq!(chi, 2, "挤出后 Euler 示性数应为 2");
}
#[test]
fn extrude_region_three_faces_leaving_cap() {
let (mut mesh, v, faces) = build_tetrahedron();
assert!(validate_mesh(&mesh).is_ok());
let [a, _b, _c, _d] = v;
let v_before = mesh.vertex_count();
let f_before = mesh.face_count();
let he_before = mesh.halfedge_count();
let offset = [0.0, 0.0, 0.5];
let new_faces = extrude_region(&mut mesh, &[faces[0], faces[1], faces[2]], offset)
.expect("三面区域挤出应成功");
assert_eq!(new_faces.len(), 6);
assert_eq!(mesh.vertex_count(), v_before + 3);
assert_eq!(mesh.face_count(), f_before + 6);
assert_eq!(mesh.halfedge_count(), he_before + 18);
let errors = crate::validate::validate_topology(&mesh);
assert!(errors.is_empty(), "挤出后拓扑应一致:{:?}", errors);
let chi = mesh.vertex_count() as i64 - (mesh.halfedge_count() / 2) as i64
+ mesh.face_count() as i64;
assert_eq!(chi, 2, "挤出后 Euler 示性数应为 2");
let pos_a = mesh.get_vertex(a).unwrap().position;
assert!(
(pos_a[2] - 0.5).abs() < 1e-12,
"内部顶点 A 应被平移到 z=0.5"
);
assert!(mesh.contains_face(faces[3]));
}
#[test]
fn extrude_region_zero_offset_returns_error() {
let (mut mesh, _v, faces) = build_tetrahedron();
let err = extrude_region(&mut mesh, &[faces[0]], [0.0, 0.0, 0.0]).unwrap_err();
assert_eq!(err, TopologyError::DegenerateTriangle);
}
#[test]
fn extrude_region_empty_faces_returns_error() {
let (mut mesh, _v, _faces) = build_tetrahedron();
let err = extrude_region(&mut mesh, &[], [0.0, 0.0, 1.0]).unwrap_err();
match err {
TopologyError::Inconsistent(_) => {}
other => panic!("期望 Inconsistent 错误,得到 {:?}", other),
}
}
#[test]
fn extrude_region_degenerate_side_returns_error() {
let (mut mesh, _v, faces) = build_tetrahedron();
let err = extrude_region(&mut mesh, &[faces[0]], [0.0, 1.0, 0.0]).unwrap_err();
assert_eq!(err, TopologyError::DegenerateTriangle);
}
#[test]
fn add_triangle_to_empty_mesh() {
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 f = add_triangle(&mut mesh, v0, v1, v2).expect("add_triangle 应成功");
assert!(mesh.contains_face(f));
assert_eq!(mesh.face_count(), 1);
assert_eq!(mesh.vertex_count(), 3);
assert_eq!(mesh.halfedge_count(), 6); assert!(validate_mesh(&mesh).is_ok());
}
#[test]
fn add_two_adjacent_triangles() {
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 _f1 = add_triangle(&mut mesh, v0, v1, v2).expect("F1");
let _f2 = add_triangle(&mut mesh, v1, v0, v3).expect("F2");
assert_eq!(mesh.face_count(), 2);
assert_eq!(mesh.vertex_count(), 4);
assert!(validate_mesh(&mesh).is_ok());
assert_eq!(mesh.halfedge_count(), 10);
}
#[test]
fn add_degenerate_triangle_fails() {
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 err = add_triangle(&mut mesh, v0, v1, v0).unwrap_err();
assert_eq!(err, TopologyError::DegenerateTriangle);
}
#[test]
fn add_triangle_with_invalid_vertex_fails() {
let mut mesh = MeshStorage::new();
let v = mesh.add_vertex(Vertex::new([0.0; 3]));
let bad = VertexId::default();
let err = add_triangle(&mut mesh, v, bad, v).unwrap_err();
assert!(matches!(
err,
TopologyError::DegenerateTriangle | TopologyError::Inconsistent(_)
));
}
#[test]
fn add_triangle_preserves_existing_edge() {
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 _f1 = add_triangle(&mut mesh, v0, v1, v2).expect("F1");
assert_eq!(mesh.face_count(), 1);
let v3 = mesh.add_vertex(Vertex::new([1.0, -1.0, 0.0]));
let _f2 = add_triangle(&mut mesh, v1, v0, v3).expect("F2");
assert_eq!(mesh.face_count(), 2);
assert!(validate_mesh(&mesh).is_ok());
assert_eq!(mesh.halfedge_count(), 10);
}
}