mesh_graph/elements/
face.rs

1use glam::Vec3;
2use itertools::Itertools;
3use parry3d::{bounding_volume::Aabb, na::Point3};
4use tracing::{error, instrument};
5
6use crate::{CircularHalfedgesIterator, MeshGraph, error_none};
7
8use super::{FaceId, HalfedgeId, VertexId};
9
10#[derive(Default, Debug, Clone, Copy)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct Face {
13    /// One of the halfedges of the face.
14    /// Serves as a starting point for traversing the face's edges and vertices
15    pub halfedge: HalfedgeId,
16
17    /// The index of the face in the BVH
18    pub index: u32,
19
20    /// The associated face id
21    pub id: FaceId,
22}
23
24impl Face {
25    /// Returns the three halfedges that form this face
26    #[instrument(skip(mesh_graph))]
27    pub fn halfedges<'a>(&self, mesh_graph: &'a MeshGraph) -> CircularHalfedgesIterator<'a> {
28        CircularHalfedgesIterator::new(Some(self.halfedge), mesh_graph, |he, mesh_graph| {
29            mesh_graph
30                .halfedges
31                .get(he)
32                .or_else(error_none!("Halfedge not found"))?
33                .next
34        })
35    }
36
37    /// Returns the three corner vertices of this face.
38    #[instrument(skip(mesh_graph))]
39    pub fn vertices(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = VertexId> {
40        self.halfedges(mesh_graph).filter_map(|he| {
41            mesh_graph
42                .halfedges
43                .get(he)
44                .or_else(error_none!("Halfedge not found"))
45                .map(|he| he.end_vertex)
46        })
47    }
48
49    /// Center positions of this face.
50    #[instrument(skip(mesh_graph))]
51    pub fn center(&self, mesh_graph: &MeshGraph) -> Vec3 {
52        self.vertex_positions(mesh_graph).sum::<Vec3>() / 3.0
53    }
54
55    /// Compute the parry Aabb of this triangle
56    #[instrument(skip(mesh_graph))]
57    pub fn aabb(&self, mesh_graph: &MeshGraph) -> Aabb {
58        Aabb::from_points(
59            self.vertex_positions(mesh_graph)
60                .map(|p| Point3::new(p.x, p.y, p.z)),
61        )
62    }
63
64    /// Returns an iterator over the vertex positions of this face.
65    #[instrument(skip(mesh_graph))]
66    pub fn vertex_positions(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = Vec3> {
67        self.vertices(mesh_graph).filter_map(|v| {
68            mesh_graph
69                .positions
70                .get(v)
71                .or_else(error_none!("Position not found"))
72                .copied()
73        })
74    }
75
76    /// Compute the normal of this triangle
77    #[instrument(skip(mesh_graph))]
78    pub fn normal(&self, mesh_graph: &MeshGraph) -> Option<Vec3> {
79        let positions = self.vertex_positions(mesh_graph).collect_vec();
80
81        if positions.len() < 3 {
82            error!("Face has less than 3 vertex positions");
83            return None;
84        }
85
86        let a = positions[1] - positions[0];
87        let b = positions[2] - positions[0];
88        Some(a.cross(b).normalize())
89    }
90
91    /// Wether this triangle is degenerate.
92    #[instrument(skip(mesh_graph))]
93    pub fn is_degenerate(&self, mesh_graph: &MeshGraph, epsilon_sqr: f32) -> bool {
94        let positions = self.vertex_positions(mesh_graph).collect_vec();
95
96        if positions.len() < 3 {
97            error!("Face has less than 3 vertex positions");
98            return true;
99        }
100
101        let p0 = positions[0];
102        let p1 = positions[1];
103        let p2 = positions[2];
104
105        // Check for coincident vertices
106        if p0.distance_squared(p1) < epsilon_sqr
107            || p0.distance_squared(p2) < epsilon_sqr
108            || p1.distance_squared(p2) < epsilon_sqr
109        {
110            return true;
111        }
112
113        // Check for collinear vertices using cross product
114        let a = p1 - p0;
115        let b = p2 - p0;
116        let cross = a.cross(b);
117
118        // Triangle is degenerate if cross product magnitude is very small
119        cross.length_squared() < epsilon_sqr
120    }
121}