mesh_graph/elements/
vertex.rs

1use tracing::{error, instrument};
2
3use crate::{CircularHalfedgesIterator, MeshGraph, error_none};
4
5use super::{FaceId, HalfedgeId, VertexId};
6
7/// A vertex is an corner point of a face.
8///
9/// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/all.svg" alt="Connectivity" style="max-width: 50em" />
10#[derive(Debug, Default, Clone, Copy)]
11#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
12pub struct Vertex {
13    /// One of the halfedges with this vertex as start point.
14    /// If possible this is a boundary halfedge, i.e. it has no associated face.
15    ///
16    /// After the mesh graph is constructed correctly, this is always `Some`.
17    ///
18    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/outgoing_halfedge.svg" alt="Connectivity" style="max-width: 50em" />
19    pub outgoing_halfedge: Option<HalfedgeId>,
20}
21
22impl Vertex {
23    /// One of the incoming halfedges of this vertex.
24    ///
25    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/incoming_halfedge.svg" alt="Connectivity" style="max-width: 50em" />
26    #[instrument(skip(mesh_graph))]
27    pub fn incoming_halfedge(&self, mesh_graph: &MeshGraph) -> Option<HalfedgeId> {
28        mesh_graph
29            .halfedges
30            .get(
31                self.outgoing_halfedge
32                    .or_else(error_none!("Vertex has no outgoing halfedge"))?,
33            )
34            .or_else(error_none!("Outgoing halfedge not found"))
35            .and_then(|halfedge| halfedge.twin.or_else(error_none!("Twin is None")))
36    }
37
38    /// Returns all halfedges that point away from this vertex.
39    ///
40    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/outgoing_halfedges.svg" alt="Connectivity" style="max-width: 50em" />
41    #[instrument(skip(mesh_graph))]
42    pub fn outgoing_halfedges<'a>(
43        &self,
44        mesh_graph: &'a MeshGraph,
45    ) -> CircularHalfedgesIterator<'a> {
46        CircularHalfedgesIterator::new(self.outgoing_halfedge, mesh_graph, |he, mesh_graph| {
47            mesh_graph
48                .halfedges
49                .get(he)
50                .or_else(error_none!("Halfedge is None"))?
51                .cw_rotated_neighbour(mesh_graph)
52        })
53    }
54
55    /// Returns all halfedges that point towards this vertex
56    ///
57    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/incoming_halfedges.svg" alt="Connectivity" style="max-width: 50em" />
58    #[instrument(skip(mesh_graph))]
59    pub fn incoming_halfedges(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = HalfedgeId> {
60        self.outgoing_halfedges(mesh_graph).filter_map(|he_id| {
61            mesh_graph
62                .halfedges
63                .get(he_id)
64                .or_else(error_none!("Halfedge is None"))?
65                .twin
66                .or_else(error_none!("Twin is None"))
67        })
68    }
69
70    /// Returns all faces incident to this vertex.
71    ///
72    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/faces.svg" alt="Connectivity" style="max-width: 50em" />
73    #[instrument(skip(mesh_graph))]
74    pub fn faces(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = FaceId> {
75        self.outgoing_halfedges(mesh_graph).filter_map(|he| {
76            mesh_graph
77                .halfedges
78                .get(he)
79                .or_else(error_none!("Halfedge is None"))?
80                .face
81        })
82    }
83
84    /// Returns all neighbouring (connected through an edge) vertices of this vertex.
85    ///
86    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/neighbours.svg" alt="Connectivity" style="max-width: 50em" />
87    #[instrument(skip(mesh_graph))]
88    pub fn neighbours(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = VertexId> {
89        self.outgoing_halfedges(mesh_graph).filter_map(|he| {
90            mesh_graph
91                .halfedges
92                .get(he)
93                .or_else(error_none!("Halfedge is None"))
94                .map(|he| he.end_vertex)
95        })
96    }
97
98    /// The degree of this vertex, i.e., the number of edges incident to it. Sometimes called the valence.
99    #[inline]
100    #[instrument(skip(mesh_graph))]
101    pub fn degree(&self, mesh_graph: &MeshGraph) -> usize {
102        self.neighbours(mesh_graph).count()
103    }
104
105    /// Returns true if this vertex is a boundary vertex, i.e., if it is incident to a boundary edge.
106    #[instrument(skip(mesh_graph))]
107    pub fn is_boundary(&self, mesh_graph: &MeshGraph) -> bool {
108        if let Some(outgoing_he) = self.outgoing_halfedge {
109            mesh_graph
110                .halfedges
111                .get(outgoing_he)
112                .or_else(error_none!("Outgoing halfedge not found"))
113                .map(|he| he.is_boundary())
114                .unwrap_or(false)
115        } else {
116            error!("Outgoing halfedge is None");
117            false
118        }
119    }
120
121    /// Returns the halfedges that are opposite to this vertex for every incident face to this vertex.
122    /// They are ordered counterclockwise.
123    ///
124    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/one_ring.svg" alt="Connectivity" style="max-width: 50em" />
125    #[instrument(skip(mesh_graph))]
126    pub fn one_ring(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = HalfedgeId> {
127        self.incoming_halfedges(mesh_graph)
128            .collect::<Vec<_>>()
129            .into_iter()
130            .rev()
131            .filter_map(|he| {
132                mesh_graph
133                    .halfedges
134                    .get(he)
135                    .or_else(error_none!("Outgoing halfedge not found"))?
136                    .cw_rotated_neighbour(mesh_graph)
137            })
138    }
139}