Skip to main content

mesh_graph/elements/
face.rs

1use glam::Vec3;
2use itertools::Itertools;
3use parry3d::bounding_volume::Aabb;
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(
29            Some(self.halfedge),
30            mesh_graph,
31            |he, mesh_graph| {
32                mesh_graph
33                    .halfedges
34                    .get(he)
35                    .or_else(error_none!("Halfedge not found"))?
36                    .next
37            },
38            3,
39        )
40    }
41
42    /// Returns the three corner vertices of this face.
43    #[instrument(skip(mesh_graph))]
44    pub fn vertices(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = VertexId> {
45        self.halfedges(mesh_graph).filter_map(|he| {
46            mesh_graph
47                .halfedges
48                .get(he)
49                .or_else(error_none!("Halfedge not found"))
50                .map(|he| he.end_vertex)
51        })
52    }
53
54    /// Center positions of this face.
55    #[instrument(skip(mesh_graph))]
56    pub fn center(&self, mesh_graph: &MeshGraph) -> Vec3 {
57        self.vertex_positions(mesh_graph).sum::<Vec3>() / 3.0
58    }
59
60    /// Compute the parry Aabb of this triangle
61    #[instrument(skip(mesh_graph))]
62    pub fn aabb(&self, mesh_graph: &MeshGraph) -> Aabb {
63        Aabb::from_points(
64            self.vertex_positions(mesh_graph)
65                .map(|p| Vec3::new(p.x, p.y, p.z)),
66        )
67    }
68
69    /// Returns an iterator over the vertex positions of this face.
70    #[instrument(skip(mesh_graph))]
71    pub fn vertex_positions(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = Vec3> {
72        self.vertices(mesh_graph).filter_map(|v| {
73            mesh_graph
74                .positions
75                .get(v)
76                .or_else(error_none!("Position not found"))
77                .copied()
78        })
79    }
80
81    /// Compute the normal of this triangle
82    #[instrument(skip(mesh_graph))]
83    pub fn normal(&self, mesh_graph: &MeshGraph) -> Option<Vec3> {
84        let positions = self.vertex_positions(mesh_graph).collect_vec();
85
86        if positions.len() < 3 {
87            error!("Face has less than 3 vertex positions");
88            return None;
89        }
90
91        Some(Self::normal_from_positions(&positions))
92    }
93
94    #[inline]
95    pub fn normal_from_positions(positions: &[Vec3]) -> Vec3 {
96        let a = positions[1] - positions[0];
97        let b = positions[2] - positions[0];
98
99        a.cross(b).normalize()
100    }
101
102    /// Wether this triangle is degenerate.
103    #[instrument(skip(mesh_graph))]
104    pub fn is_degenerate(&self, mesh_graph: &MeshGraph, epsilon_sqr: f32) -> bool {
105        let positions = self.vertex_positions(mesh_graph).collect_vec();
106
107        if positions.len() < 3 {
108            error!("Face has less than 3 vertex positions");
109            return true;
110        }
111
112        let p0 = positions[0];
113        let p1 = positions[1];
114        let p2 = positions[2];
115
116        // Check for coincident vertices
117        if p0.distance_squared(p1) < epsilon_sqr
118            || p0.distance_squared(p2) < epsilon_sqr
119            || p1.distance_squared(p2) < epsilon_sqr
120        {
121            return true;
122        }
123
124        // Check for collinear vertices using cross product
125        let a = p1 - p0;
126        let b = p2 - p0;
127        let cross = a.cross(b);
128
129        // Triangle is degenerate if cross product magnitude is very small
130        cross.length_squared() < epsilon_sqr
131    }
132
133    #[instrument(skip(mesh_graph))]
134    pub fn halfedge_between(
135        &self,
136        vertex_id1: VertexId,
137        vertex_id2: VertexId,
138        mesh_graph: &MeshGraph,
139    ) -> Option<HalfedgeId> {
140        for he_id in self.halfedges(mesh_graph) {
141            let he = mesh_graph
142                .halfedges
143                .get(he_id)
144                .or_else(error_none!("Halfedge not found"))?;
145
146            if he.end_vertex == vertex_id1 || he.end_vertex == vertex_id2 {
147                let prev_he_id = he
148                    .prev(mesh_graph)
149                    .or_else(error_none!("Prev halfedge not found"))?;
150                let prev_he = mesh_graph
151                    .halfedges
152                    .get(prev_he_id)
153                    .or_else(error_none!("Halfedge not found"))?;
154
155                if prev_he.end_vertex == vertex_id1 || prev_he.end_vertex == vertex_id2 {
156                    return Some(he_id);
157                }
158            }
159        }
160
161        None
162    }
163}