mesh_graph/elements/
halfedge.rs

1use tracing::{error, instrument};
2
3use crate::{MeshGraph, error_none};
4
5use super::{FaceId, HalfedgeId, VertexId};
6
7/// A directional edge that points from one vertex to another and is (optionally) part of a face.
8/// If it's not part of a face, it's called a boundary halfedge.
9///
10/// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/all.svg" alt="Connectivity" style="max-width: 28em" />
11#[derive(Debug, Clone, Copy)]
12#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
13pub struct Halfedge {
14    /// The vertex that this halfedge points to.
15    ///
16    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/end_vertex.svg" alt="Connectivity" style="max-width: 28em" />
17    pub end_vertex: VertexId,
18
19    /// The face associated to this halfedge. `None` if `self` is a boundary halfedge.
20    ///
21    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/face.svg" alt="Connectivity" style="max-width: 28em" />
22    pub face: Option<FaceId>,
23
24    /// This is the halfedge opposite.
25    /// It points backwards compared to this halfedge (from end_vertex to start_vertex).
26    /// After the mesh graph is constructed, this field is always `Some(...)`, meaning
27    /// that every halfedge has a twin.
28    ///
29    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/twin.svg" alt="Connectivity" style="max-width: 28em" />
30    pub twin: Option<HalfedgeId>,
31
32    /// The next halfedge in the face. `None` if `self` is a boundary halfedge.
33    ///
34    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/next.svg" alt="Connectivity" style="max-width: 28em" />
35    pub next: Option<HalfedgeId>,
36}
37
38impl Halfedge {
39    /// Start vertex from which this halfedge points away
40    ///
41    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/start_vertex.svg" alt="Connectivity" style="max-width: 28em" />
42    #[instrument(skip(mesh_graph))]
43    pub fn start_vertex(&self, mesh_graph: &MeshGraph) -> Option<VertexId> {
44        mesh_graph
45            .halfedges
46            .get(self.twin.or_else(error_none!("No twin"))?)
47            .or_else(error_none!("Twin halfedge not found"))
48            .map(|t| t.end_vertex)
49    }
50
51    /// Previous halfedge that shares the same face. `None` if `self` is a boundary halfedge.
52    ///
53    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/prev.svg" alt="Connectivity" style="max-width: 28em" />
54    #[instrument(skip(mesh_graph))]
55    pub fn prev(&self, mesh_graph: &MeshGraph) -> Option<HalfedgeId> {
56        // TODO : this only works for triangle meshes
57        self.next.and_then(|next_id| {
58            mesh_graph
59                .halfedges
60                .get(next_id)
61                .or_else(error_none!("Next halfedge not found"))
62                .and_then(|h| h.next)
63        })
64    }
65
66    /// In counter-clockwise order next halfedge that has the same start vertex
67    ///
68    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/ccw_rotated_neighbour.svg" alt="Connectivity" style="max-width: 28em" />
69    #[instrument(skip(mesh_graph))]
70    pub fn ccw_rotated_neighbour(&self, mesh_graph: &MeshGraph) -> Option<HalfedgeId> {
71        self.prev(mesh_graph).and_then(|prev| {
72            mesh_graph
73                .halfedges
74                .get(prev)
75                .or_else(error_none!("Previous halfedge not found"))
76                .and_then(|h| h.twin.or_else(error_none!("Twin missing")))
77        })
78    }
79
80    /// In clockwise order next halfedge that has the same start vertex
81    ///
82    /// <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/cw_rotated_neighbour.svg" alt="Connectivity" style="max-width: 28em" />
83    #[instrument(skip(mesh_graph))]
84    pub fn cw_rotated_neighbour(&self, mesh_graph: &MeshGraph) -> Option<HalfedgeId> {
85        mesh_graph
86            .halfedges
87            .get(self.twin.or_else(error_none!("Twin missing"))?)
88            .and_then(|h| h.next)
89    }
90
91    /// Length of the halfedge squared.
92    #[instrument(skip(mesh_graph))]
93    pub fn length_squared(&self, mesh_graph: &MeshGraph) -> f32 {
94        self.len_sqr_inner(mesh_graph).unwrap_or_else(|| {
95            error!("Halfedge invalid. Defaulting to zero length");
96            0.0
97        })
98    }
99
100    fn len_sqr_inner(&self, mesh_graph: &MeshGraph) -> Option<f32> {
101        let start = mesh_graph.positions.get(self.start_vertex(mesh_graph)?)?;
102        let end = mesh_graph.positions.get(self.end_vertex)?;
103
104        Some(start.distance_squared(*end))
105    }
106
107    /// Length of the halfedge.
108    #[inline(always)]
109    pub fn length(&self, mesh_graph: &MeshGraph) -> f32 {
110        self.length_squared(mesh_graph).sqrt()
111    }
112
113    /// Returns `true` if there is no face adjacent to this halfedge.
114    #[inline]
115    pub fn is_boundary(&self) -> bool {
116        self.face.is_none()
117    }
118
119    /// Returns `true` if the start or end vertex is a boundary vertex.
120    #[instrument(skip(mesh_graph))]
121    pub fn is_adjacent_to_boundary(&self, mesh_graph: &MeshGraph) -> bool {
122        if let Some(start_vertex) = self.start_vertex(mesh_graph) {
123            mesh_graph
124                .vertices
125                .get(start_vertex)
126                .or_else(error_none!("Start vertex not found"))
127                .map(|v| v.is_boundary(mesh_graph))
128                .unwrap_or(false)
129                || mesh_graph
130                    .vertices
131                    .get(self.end_vertex)
132                    .or_else(error_none!("End vertex not found"))
133                    .map(|v| v.is_boundary(mesh_graph))
134                    .unwrap_or(false)
135        } else {
136            error!("Start vertex ID not found");
137            false
138        }
139    }
140}