use crate::ids::{FaceId, HalfEdgeId, VertexId};
use crate::linalg::vec3::{Vec3, add, angle_between, cross, dot, length, normalize, scale, sub};
use crate::storage::MeshStorage;
use crate::traversal::{FaceHalfEdges, VertexAdjacentFaces, VertexRing};
pub fn edge_length(mesh: &MeshStorage, he: HalfEdgeId) -> Option<f64> {
let h = mesh.get_halfedge(he)?;
let tip = h.vertex;
let twin_id = h.twin?;
let origin = mesh.get_halfedge(twin_id)?.vertex;
let p_tip = mesh.get_vertex(tip)?.position;
let p_origin = mesh.get_vertex(origin)?.position;
Some(length(sub(p_tip, p_origin)))
}
pub fn face_area(mesh: &MeshStorage, f: FaceId) -> Option<f64> {
let (a, b, c) = face_triangle_positions(mesh, f)?;
Some(0.5 * length(cross(sub(b, a), sub(c, a))))
}
pub fn face_normal(mesh: &MeshStorage, f: FaceId) -> Option<Vec3> {
let (a, b, c) = face_triangle_positions(mesh, f)?;
let n = cross(sub(b, a), sub(c, a));
let l = length(n);
if l < 1e-12 {
return None;
}
Some(scale(n, 1.0 / l))
}
pub fn face_min_angle(mesh: &MeshStorage, f: FaceId) -> Option<f64> {
let (a, b, c) = face_triangle_positions(mesh, f)?;
let angle_a = angle_between(sub(b, a), sub(c, a));
let angle_b = angle_between(sub(a, b), sub(c, b));
let angle_c = angle_between(sub(a, c), sub(b, c));
Some(angle_a.min(angle_b).min(angle_c))
}
pub fn vertex_normal(mesh: &MeshStorage, v: VertexId) -> Option<Vec3> {
let mut accum = [0.0f64; 3];
let mut has_any = false;
for f in VertexAdjacentFaces::new(mesh, v) {
if let (Some(n), Some(area)) = (face_normal(mesh, f), face_area(mesh, f)) {
accum = add(accum, scale(n, area));
has_any = true;
}
}
if !has_any {
return None;
}
let result = normalize(accum);
if length(result) < 1e-12 {
None
} else {
Some(result)
}
}
fn face_polygon_positions(mesh: &MeshStorage, f: FaceId) -> Option<Vec<Vec3>> {
let mut verts = Vec::new();
for he in FaceHalfEdges::new(mesh, f) {
let v = mesh.get_halfedge(he)?.vertex;
verts.push(mesh.get_vertex(v)?.position);
}
if verts.is_empty() { None } else { Some(verts) }
}
fn face_triangle_positions(mesh: &MeshStorage, f: FaceId) -> Option<(Vec3, Vec3, Vec3)> {
let verts = face_polygon_positions(mesh, f)?;
if verts.len() != 3 {
return None;
}
Some((verts[0], verts[1], verts[2]))
}
pub fn polygon_area(mesh: &MeshStorage, f: FaceId) -> Option<f64> {
let verts = face_polygon_positions(mesh, f)?;
let n = verts.len();
if n < 3 {
return None;
}
let mut sum = [0.0; 3];
for i in 0..n {
let j = (i + 1) % n;
sum = add(sum, cross(verts[i], verts[j]));
}
Some(0.5 * length(sum))
}
pub fn polygon_normal(mesh: &MeshStorage, f: FaceId) -> Option<Vec3> {
let verts = face_polygon_positions(mesh, f)?;
let n_verts = verts.len();
if n_verts < 3 {
return None;
}
let center = {
let mut c = [0.0; 3];
for v in &verts {
c = add(c, *v);
}
[
c[0] / n_verts as f64,
c[1] / n_verts as f64,
c[2] / n_verts as f64,
]
};
let mut normal = [0.0; 3];
for i in 0..n_verts {
let j = (i + 1) % n_verts;
normal = add(normal, cross(sub(verts[i], center), sub(verts[j], center)));
}
let l = length(normal);
if l < 1e-12 {
None
} else {
Some(scale(normal, 1.0 / l))
}
}
pub fn surface_area(mesh: &MeshStorage) -> f64 {
mesh.face_ids().filter_map(|f| face_area(mesh, f)).sum()
}
pub fn mesh_volume(mesh: &MeshStorage) -> f64 {
use crate::predicates::tet_signed_volume;
let origin = [0.0, 0.0, 0.0];
let positions = mesh.positions_dense();
let mut volume = 0.0;
for f in mesh.face_ids() {
let verts: Vec<VertexId> = crate::traversal::FaceVertices::new(mesh, f).collect();
if verts.len() != 3 {
continue;
}
let (v0, v1, v2) = match (
mesh.position_index(verts[0]),
mesh.position_index(verts[1]),
mesh.position_index(verts[2]),
) {
(Some(i0), Some(i1), Some(i2)) => (
positions[i0 as usize],
positions[i1 as usize],
positions[i2 as usize],
),
_ => continue,
};
volume += tet_signed_volume(origin, v0, v1, v2);
}
volume
}
pub fn cotan_laplacian(mesh: &MeshStorage, v: VertexId) -> Option<[f64; 3]> {
let pos_v = mesh.get_vertex(v)?.position;
let outgoing: Vec<HalfEdgeId> = VertexRing::new(mesh, v).collect();
let mut sum = [0.0; 3];
for &he in &outgoing {
let neighbor = mesh.get_halfedge(he)?.vertex;
let pos_u = mesh.get_vertex(neighbor)?.position;
let weight = cotan_edge_weight(mesh, he)?;
let diff = sub(pos_u, pos_v);
sum = add(sum, scale(diff, weight));
}
Some(sum)
}
pub fn cotan_edge_weight(mesh: &MeshStorage, he: HalfEdgeId) -> Option<f64> {
let h = mesh.get_halfedge(he)?;
let a_pos = mesh.get_vertex(h.vertex)?.position;
let mut weight = 0.0;
if let Some(face) = h.face {
let fhe_ids: Vec<_> = FaceHalfEdges::new(mesh, face).collect();
if fhe_ids.len() == 3 {
let v0 = mesh.get_halfedge(fhe_ids[0])?.vertex;
let v1 = mesh.get_halfedge(fhe_ids[1])?.vertex;
let v2 = mesh.get_halfedge(fhe_ids[2])?.vertex;
let pos = [
mesh.get_vertex(v0)?.position,
mesh.get_vertex(v1)?.position,
mesh.get_vertex(v2)?.position,
];
let src = h.twin.and_then(|t| mesh.get_halfedge(t)).map(|t| t.vertex);
let dst = h.vertex;
if let Some(src_v) = src {
let pos_src_v = mesh.get_vertex(src_v)?.position;
let pos_dst_v = mesh.get_vertex(dst)?.position;
let opp = pos.iter().position(|&p| p != pos_src_v && p != pos_dst_v);
if let Some(idx) = opp {
let opp_pos = pos[idx];
let pos_src = mesh.get_vertex(src_v)?.position;
let pos_dst = a_pos;
let cot = cotan(opp_pos, pos_src, pos_dst);
weight += cot;
}
}
}
}
if let Some(twin) = h.twin
&& let Some(tface) = mesh.get_halfedge(twin)?.face
{
let fhe_ids: Vec<_> = FaceHalfEdges::new(mesh, tface).collect();
if fhe_ids.len() == 3 {
let v0 = mesh.get_halfedge(fhe_ids[0])?.vertex;
let v1 = mesh.get_halfedge(fhe_ids[1])?.vertex;
let v2 = mesh.get_halfedge(fhe_ids[2])?.vertex;
let pos = [
mesh.get_vertex(v0)?.position,
mesh.get_vertex(v1)?.position,
mesh.get_vertex(v2)?.position,
];
let src = h.vertex;
let dst = mesh.get_halfedge(twin)?.vertex;
let pos_src_val = mesh.get_vertex(src)?.position;
let pos_dst_val = mesh.get_vertex(dst)?.position;
let opp = pos
.iter()
.position(|&p| p != pos_src_val && p != pos_dst_val);
if let Some(idx) = opp {
let opp_pos = pos[idx];
let pos_src = mesh.get_vertex(src)?.position;
let pos_dst = mesh.get_vertex(dst)?.position;
let cot = cotan(opp_pos, pos_src, pos_dst);
weight += cot;
}
}
}
Some(weight)
}
fn cotan(opposite: Vec3, a: Vec3, b: Vec3) -> f64 {
let oa = sub(a, opposite);
let ob = sub(b, opposite);
let cross_len = length(cross(oa, ob));
let dot_val = dot(oa, ob);
if cross_len < 1e-14 {
0.0
} else {
dot_val / cross_len
}
}
pub fn dihedral_angle(mesh: &MeshStorage, he: HalfEdgeId) -> Option<f64> {
let h = mesh.get_halfedge(he)?;
let f1 = h.face?;
let twin = h.twin?;
let f2 = mesh.get_halfedge(twin)?.face?;
let n1 = face_normal(mesh, f1)?;
let n2 = face_normal(mesh, f2)?;
let d = dot(n1, n2).clamp(-1.0, 1.0);
Some(d.acos())
}
pub fn is_feature_edge(
mesh: &MeshStorage,
he: HalfEdgeId,
angle_threshold_rad: f64,
) -> Option<bool> {
Some(dihedral_angle(mesh, he)? > angle_threshold_rad)
}
pub fn feature_edges(mesh: &MeshStorage, angle_threshold_rad: f64) -> Vec<HalfEdgeId> {
mesh.halfedge_ids()
.filter(|&he| is_feature_edge(mesh, he, angle_threshold_rad).unwrap_or(false))
.collect()
}
pub fn surface_area_par(mesh: &MeshStorage) -> f64 {
use rayon::prelude::*;
let face_ids: Vec<_> = mesh.face_ids().collect();
face_ids
.par_iter()
.filter_map(|&f| face_area(mesh, f))
.sum()
}
pub fn mesh_volume_par(mesh: &MeshStorage) -> f64 {
use crate::predicates::tet_signed_volume;
use rayon::prelude::*;
let origin = [0.0, 0.0, 0.0];
let positions = mesh.positions_dense();
let face_ids: Vec<_> = mesh.face_ids().collect();
face_ids
.par_iter()
.map(|&f| {
let verts: Vec<VertexId> = crate::traversal::FaceVertices::new(mesh, f).collect();
if verts.len() != 3 {
return 0.0;
}
let (v0, v1, v2) = match (
mesh.position_index(verts[0]),
mesh.position_index(verts[1]),
mesh.position_index(verts[2]),
) {
(Some(i0), Some(i1), Some(i2)) => (
positions[i0 as usize],
positions[i1 as usize],
positions[i2 as usize],
),
_ => return 0.0,
};
tet_signed_volume(origin, v0, v1, v2)
})
.sum()
}
pub fn vertex_normals_par(mesh: &MeshStorage) -> Vec<[f64; 3]> {
use rayon::prelude::*;
let verts: Vec<VertexId> = mesh.vertex_ids().collect();
verts
.par_iter()
.map(|&v| vertex_normal(mesh, v).unwrap_or([0.0, 0.0, 0.0]))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
use crate::storage::{Face, HalfEdge, MeshStorage, Vertex};
fn build_unit_triangle() -> (MeshStorage, [VertexId; 3], FaceId) {
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.0, 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 t_ab = mesh.add_halfedge(HalfEdge::new(a)); let t_bc = mesh.add_halfedge(HalfEdge::new(b)); let t_ca = mesh.add_halfedge(HalfEdge::new(c));
let f = mesh.add_face(Face::new());
for (he, twin, next, prev) in [
(h_ab, t_ab, h_bc, h_ca),
(h_bc, t_bc, h_ca, h_ab),
(h_ca, t_ca, 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(f);
}
for (t, he) in [(t_ab, h_ab), (t_bc, h_bc), (t_ca, h_ca)] {
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_bc);
mesh.get_vertex_mut(c).unwrap().halfedge = Some(h_ca);
mesh.get_face_mut(f).unwrap().halfedge = Some(h_ab);
(mesh, [a, b, c], f)
}
#[test]
fn edge_length_basic() {
let (mesh, _v, _f) = build_unit_triangle();
let he_ab = mesh
.halfedge_ids()
.find(|h| {
let h = mesh.get_halfedge(*h).unwrap();
h.vertex == _v[1] && mesh.get_halfedge(h.twin.unwrap()).unwrap().vertex == _v[0]
})
.unwrap();
assert!((edge_length(&mesh, he_ab).unwrap() - 1.0).abs() < 1e-9);
let he_bc = mesh
.halfedge_ids()
.find(|h| {
let h = mesh.get_halfedge(*h).unwrap();
h.vertex == _v[2] && mesh.get_halfedge(h.twin.unwrap()).unwrap().vertex == _v[1]
})
.unwrap();
assert!((edge_length(&mesh, he_bc).unwrap() - 2.0_f64.sqrt()).abs() < 1e-9);
}
#[test]
fn edge_length_invalid_returns_none() {
let (mesh, _v, _f) = build_unit_triangle();
let bad = HalfEdgeId::default();
assert!(edge_length(&mesh, bad).is_none());
}
#[test]
fn face_area_unit_triangle() {
let (mesh, _v, f) = build_unit_triangle();
assert!((face_area(&mesh, f).unwrap() - 0.5).abs() < 1e-9);
}
#[test]
fn face_normal_ccw_points_up() {
let (mesh, _v, f) = build_unit_triangle();
let n = face_normal(&mesh, f).unwrap();
assert!((n[0] - 0.0).abs() < 1e-9);
assert!((n[1] - 0.0).abs() < 1e-9);
assert!((n[2] - 1.0).abs() < 1e-9);
}
#[test]
fn vertex_normal_corner_of_triangle() {
let (mesh, v, _f) = build_unit_triangle();
let n = vertex_normal(&mesh, v[0]).unwrap();
assert!((n[2] - 1.0).abs() < 1e-9);
}
#[test]
fn face_min_angle_unit_triangle() {
let (mesh, _v, f) = build_unit_triangle();
let min_ang = face_min_angle(&mesh, f).unwrap();
assert!((min_ang - std::f64::consts::FRAC_PI_4).abs() < 1e-9);
}
#[test]
fn face_min_angle_equilateral() {
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, 3.0_f64.sqrt() / 2.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 t_ab = mesh.add_halfedge(HalfEdge::new(a));
let t_bc = mesh.add_halfedge(HalfEdge::new(b));
let t_ca = mesh.add_halfedge(HalfEdge::new(c));
let f = mesh.add_face(Face::new());
for (he, twin, next, prev) in [
(h_ab, t_ab, h_bc, h_ca),
(h_bc, t_bc, h_ca, h_ab),
(h_ca, t_ca, 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(f);
}
for (t, he) in [(t_ab, h_ab), (t_bc, h_bc), (t_ca, h_ca)] {
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_bc);
mesh.get_vertex_mut(c).unwrap().halfedge = Some(h_ca);
mesh.get_face_mut(f).unwrap().halfedge = Some(h_ab);
let min_ang = face_min_angle(&mesh, f).unwrap();
assert!((min_ang - std::f64::consts::FRAC_PI_3).abs() < 1e-9);
}
#[test]
fn degenerate_face_returns_none_for_normal() {
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([2.0, 0.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 t_ab = mesh.add_halfedge(HalfEdge::new(a));
let t_bc = mesh.add_halfedge(HalfEdge::new(b));
let t_ca = mesh.add_halfedge(HalfEdge::new(c));
let f = mesh.add_face(Face::new());
for (he, twin, next, prev) in [
(h_ab, t_ab, h_bc, h_ca),
(h_bc, t_bc, h_ca, h_ab),
(h_ca, t_ca, 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(f);
}
for (t, he) in [(t_ab, h_ab), (t_bc, h_bc), (t_ca, h_ca)] {
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_bc);
mesh.get_vertex_mut(c).unwrap().halfedge = Some(h_ca);
mesh.get_face_mut(f).unwrap().halfedge = Some(h_ab);
assert!(face_normal(&mesh, f).is_none());
assert!((face_area(&mesh, f).unwrap() - 0.0).abs() < 1e-12);
assert!(face_min_angle(&mesh, f).unwrap().abs() < 1e-9);
}
#[test]
fn surface_area_icosphere() {
let mesh = crate::test_util::build_icosphere(2);
let area = surface_area(&mesh);
assert!(area > 10.0 && area < 15.0, "表面积应在 4π 附近: {}", area);
}
#[test]
fn volume_icosphere() {
let mesh = crate::test_util::build_icosphere(2);
let vol = mesh_volume(&mesh);
assert!(vol > 3.0 && vol < 5.5, "体积应在 4π/3 附近: {}", vol);
}
#[test]
fn mesh_volume_empty_mesh_returns_zero() {
let mesh = MeshStorage::new();
assert_eq!(mesh_volume(&mesh), 0.0);
}
#[test]
fn mesh_volume_par_empty_mesh_returns_zero() {
let mesh = MeshStorage::new();
assert_eq!(mesh_volume_par(&mesh), 0.0);
}
#[test]
fn surface_area_empty_mesh_returns_zero() {
let mesh = MeshStorage::new();
assert_eq!(surface_area(&mesh), 0.0);
}
#[test]
fn surface_area_par_empty_mesh_returns_zero() {
let mesh = MeshStorage::new();
assert_eq!(surface_area_par(&mesh), 0.0);
}
#[test]
fn surface_area_par_matches_serial() {
let mesh = crate::test_util::build_icosphere(1);
let s = surface_area(&mesh);
let p = surface_area_par(&mesh);
assert!((s - p).abs() < 1e-10, "serial={} par={}", s, p);
}
#[test]
fn mesh_volume_par_matches_serial() {
let mesh = crate::test_util::build_icosphere(1);
let s = mesh_volume(&mesh);
let p = mesh_volume_par(&mesh);
assert!((s - p).abs() < 1e-10, "serial={} par={}", s, p);
}
#[test]
fn vertex_normals_par_matches_serial() {
let mesh = crate::test_util::build_icosphere(1);
let par = vertex_normals_par(&mesh);
let serial: Vec<[f64; 3]> = mesh
.vertex_ids()
.map(|v| vertex_normal(&mesh, v).unwrap_or([0.0, 0.0, 0.0]))
.collect();
assert_eq!(par.len(), serial.len());
for (i, (a, b)) in par.iter().zip(serial.iter()).enumerate() {
for c in 0..3 {
assert!(
(a[c] - b[c]).abs() < 1e-10,
"顶点 {} 法向分量 {} 不一致: {} vs {}",
i,
c,
a[c],
b[c]
);
}
}
}
}