Skip to main content

mesh_graph/elements/
vertex.rs

1use tracing::{error, instrument};
2
3use crate::{CircularHalfedgesIterator, MeshGraph, error_none, utils::unwrap_or_return};
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(
47            self.outgoing_halfedge,
48            mesh_graph,
49            |he, mesh_graph| {
50                mesh_graph
51                    .halfedges
52                    .get(he)
53                    .or_else(error_none!("Halfedge is None"))?
54                    .cw_rotated_neighbour(mesh_graph)
55            },
56            100_000,
57        )
58    }
59
60    /// Returns all halfedges that point towards this vertex
61    ///
62    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/incoming_halfedges.svg" alt="Connectivity" style="max-width: 50em" />
63    #[instrument(skip(mesh_graph))]
64    pub fn incoming_halfedges(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = HalfedgeId> {
65        self.outgoing_halfedges(mesh_graph).filter_map(|he_id| {
66            mesh_graph
67                .halfedges
68                .get(he_id)
69                .or_else(error_none!("Halfedge is None"))?
70                .twin
71                .or_else(error_none!("Twin is None"))
72        })
73    }
74
75    /// Returns all faces incident to this vertex.
76    ///
77    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/faces.svg" alt="Connectivity" style="max-width: 50em" />
78    #[instrument(skip(mesh_graph))]
79    pub fn faces(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = FaceId> {
80        self.outgoing_halfedges(mesh_graph).filter_map(|he| {
81            mesh_graph
82                .halfedges
83                .get(he)
84                .or_else(error_none!("Halfedge is None"))?
85                .face
86        })
87    }
88
89    /// Returns all neighbouring (connected through an edge) vertices of this vertex.
90    ///
91    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/neighbours.svg" alt="Connectivity" style="max-width: 50em" />
92    #[instrument(skip(mesh_graph))]
93    pub fn neighbours(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = VertexId> {
94        self.outgoing_halfedges(mesh_graph).filter_map(|he| {
95            mesh_graph
96                .halfedges
97                .get(he)
98                .or_else(error_none!("Halfedge is None"))
99                .map(|he| he.end_vertex)
100        })
101    }
102
103    /// The degree of this vertex, i.e., the number of edges incident to it. Sometimes called the valence.
104    #[inline]
105    #[instrument(skip(mesh_graph))]
106    pub fn degree(&self, mesh_graph: &MeshGraph) -> usize {
107        self.neighbours(mesh_graph).count()
108    }
109
110    /// Returns true if this vertex is a boundary vertex, i.e., if it is incident to a boundary edge.
111    #[instrument(skip(mesh_graph))]
112    pub fn is_boundary(&self, mesh_graph: &MeshGraph) -> bool {
113        if let Some(outgoing_he) = self.outgoing_halfedge {
114            mesh_graph
115                .halfedges
116                .get(outgoing_he)
117                .or_else(error_none!("Outgoing halfedge not found"))
118                .map(|he| he.is_boundary())
119                .unwrap_or(false)
120        } else {
121            error!("Outgoing halfedge is None");
122            false
123        }
124    }
125
126    /// Returns the halfedges that are opposite to this vertex for every incident face to this vertex.
127    /// They are ordered counterclockwise.
128    ///
129    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/one_ring.svg" alt="Connectivity" style="max-width: 50em" />
130    #[instrument(skip(mesh_graph))]
131    pub fn one_ring(&self, mesh_graph: &MeshGraph) -> impl Iterator<Item = HalfedgeId> {
132        self.incoming_halfedges(mesh_graph)
133            .collect::<Vec<_>>()
134            .into_iter()
135            .rev()
136            .filter_map(|he| {
137                mesh_graph
138                    .halfedges
139                    .get(he)
140                    .or_else(error_none!("Outgoing halfedge not found"))?
141                    .cw_rotated_neighbour(mesh_graph)
142            })
143    }
144
145    /// Returns an iterator over the series of boundary halfedges starting from this vertex.
146    ///
147    /// If this vertex has no outgoing halfedge or no outgoing boundary halfedges, the iterator is empty.
148    pub fn boundary_halfedgdes<'a>(
149        &self,
150        mesh_graph: &'a MeshGraph,
151    ) -> CircularHalfedgesIterator<'a> {
152        let Some(out_he_id) = self.outgoing_halfedge else {
153            return CircularHalfedgesIterator::empty(mesh_graph);
154        };
155
156        let he = unwrap_or_return!(
157            mesh_graph.halfedges.get(out_he_id),
158            "Halfedge not found",
159            CircularHalfedgesIterator::empty(mesh_graph)
160        );
161
162        if !he.is_boundary() {
163            return CircularHalfedgesIterator::empty(mesh_graph);
164        }
165
166        CircularHalfedgesIterator::new(
167            self.outgoing_halfedge,
168            mesh_graph,
169            |he_id, mesh_graph| {
170                let end_v_id = mesh_graph
171                    .halfedges
172                    .get(he_id)
173                    .or_else(error_none!("Halfedge not found"))?
174                    .end_vertex;
175                let next_he_id = mesh_graph
176                    .vertices
177                    .get(end_v_id)
178                    .or_else(error_none!("Vertex not found"))?
179                    .outgoing_halfedge?;
180
181                let next_he = mesh_graph
182                    .halfedges
183                    .get(next_he_id)
184                    .or_else(error_none!("Halfedge not found"))?;
185
186                if next_he.is_boundary() {
187                    Some(next_he_id)
188                } else {
189                    None
190                }
191            },
192            usize::MAX,
193        )
194    }
195}