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}