Skip to main content

mesh_graph/
lib.rs

1//! MeshGraph is a halfedge data structure for representing triangle meshes.
2//!
3//! This is heavily inspired by [SMesh](https://github.com/Bendzae/SMesh) and
4//! [OpenMesh](https://gitlab.vci.rwth-aachen.de:9000/OpenMesh/OpenMesh).
5//!
6//! ## Features
7//!
8//! - Fast spatial queries using parry3d's Bvh
9//! - High performance using slotmap
10//! - Easy integration with Bevy game engine using the `bevy` Cargo feature
11//! - Good debugging using `rerun` Cargo feature to enable the Rerun integration
12//! - Best in class documentation with illustrations
13//!
14//! ## Usage
15//!
16//! ```
17//! use mesh_graph::{MeshGraph, primitives::IcoSphere};
18//!
19//! // Create a new mesh
20//! let mesh_graph = MeshGraph::from(IcoSphere { radius: 10.0, subdivisions: 2 });
21//!
22//! // Get some vertex ID and its vertex node
23//! let (vertex_id, vertex) = mesh_graph.vertices.iter().next().unwrap();
24//!
25//! // Iterate over all outgoing halfedges of the vertex
26//! for halfedge_id in vertex.outgoing_halfedges(&mesh_graph) {
27//!     // do sth
28//! }
29//!
30//! // Get the position of the vertex
31//! let position = mesh_graph.positions[vertex_id];
32//! ```
33//!
34//! Check out the crate [freestyle-sculpt](https://github.com/Synphonyte/freestyle-sculpt) for
35//! a heavy duty example.
36//!
37//! ## Connectivity
38//!
39//! ### Halfedge
40//!
41//! <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/all.svg" alt="Connectivity" style="max-width: 28em" />
42//!
43//! ### Vertex
44//!
45//! <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/all.svg" alt="Connectivity" style="max-width: 50em" />
46
47mod access;
48mod elements;
49pub mod integrations;
50mod iter;
51mod ops;
52mod plane_slice;
53pub mod primitives;
54#[cfg(feature = "rerun")]
55mod rerun_impl;
56mod selection;
57#[cfg(feature = "serde")]
58mod serialize;
59pub mod utils;
60
61pub use elements::*;
62pub use iter::*;
63pub use ops::*;
64pub use plane_slice::*;
65pub use selection::*;
66
67use hashbrown::HashMap;
68use parry3d::partitioning::{Bvh, BvhWorkspace};
69
70use glam::Vec3;
71use slotmap::{SecondaryMap, SlotMap};
72use tracing::{error, instrument};
73
74use crate::utils::unwrap_or_return;
75
76#[cfg(feature = "rerun")]
77lazy_static::lazy_static! {
78    pub static ref RR: rerun::RecordingStream = rerun::RecordingStreamBuilder::new("mesh_graph").spawn().unwrap();
79}
80
81/// Halfedge data structure for representing triangle meshes.
82///
83/// Please see the [crate documentation](crate) for more information.
84#[derive(Clone, Default)]
85#[cfg_attr(feature = "bevy", derive(bevy::prelude::Component))]
86#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
87#[cfg_attr(
88    feature = "serde",
89    serde(from = "crate::serialize::MeshGraphIntermediate")
90)]
91pub struct MeshGraph {
92    /// Acceleration structure for fast spatial queries. Uses parry3d's Bvh to implement some of parry3d's spatial queries.
93    #[cfg_attr(feature = "serde", serde(skip))]
94    pub bvh: Bvh,
95    /// Used in conjunction with the BVH to accelerate spatial queries.
96    #[cfg_attr(feature = "serde", serde(skip))]
97    pub bvh_workspace: BvhWorkspace,
98    /// Used to map indices stored in the BVH to face IDs.
99    #[cfg_attr(feature = "serde", serde(skip))]
100    pub index_to_face_id: HashMap<u32, FaceId>,
101    /// Used to compute the next index for a new face
102    #[cfg_attr(feature = "serde", serde(skip))]
103    pub next_index: u32,
104
105    /// Maps vertex IDs to their corresponding graph node
106    pub vertices: SlotMap<VertexId, Vertex>,
107    /// Maps halfedge IDs to their corresponding graph node
108    pub halfedges: SlotMap<HalfedgeId, Halfedge>,
109    /// Maps face IDs to their corresponding graph node
110    pub faces: SlotMap<FaceId, Face>,
111
112    /// Maps vertex IDs to their corresponding positions
113    pub positions: SecondaryMap<VertexId, Vec3>,
114    /// Maps vertex IDs to their corresponding normals
115    pub vertex_normals: Option<SecondaryMap<VertexId, Vec3>>,
116
117    /// Maps vertex IDs to their corresponding outgoing halfedges (not in any particular order)
118    #[cfg_attr(feature = "serde", serde(skip))]
119    pub outgoing_halfedges: SecondaryMap<VertexId, Vec<HalfedgeId>>,
120}
121
122impl MeshGraph {
123    /// Create a new empty mesh graph
124    #[inline]
125    pub fn new() -> Self {
126        Self::default()
127    }
128
129    /// Create a triangle mesh graph from vertex positions.
130    /// Every three positions represent a triangle.
131    ///
132    /// Vertices with the same position are merged into a single vertex.
133    pub fn triangles(vertex_positions: &[Vec3]) -> Self {
134        assert!(
135            vertex_positions.len().is_multiple_of(3),
136            "Number of vertex positions should be a multiple of 3"
137        );
138
139        // Create a map to track unique vertices
140        let mut unique_positions: Vec<Vec3> = Vec::with_capacity(vertex_positions.len() / 3);
141        let mut face_indices = Vec::with_capacity(vertex_positions.len());
142
143        for vertex_pos in vertex_positions {
144            // Check if we've seen this position before using a fuzzy float comparison
145            let mut idx = None;
146            for (j, pos) in unique_positions.iter().enumerate() {
147                const EPSILON: f32 = 1e-5;
148
149                if pos.distance_squared(*vertex_pos) < EPSILON {
150                    idx = Some(j);
151                    break;
152                }
153            }
154
155            // Use the existing index or add a new vertex
156            let vertex_idx = if let Some(idx) = idx {
157                idx
158            } else {
159                let new_idx = unique_positions.len();
160                unique_positions.push(*vertex_pos);
161
162                #[cfg(feature = "rerun")]
163                RR.log(
164                    "meshgraph/construct/vertices",
165                    &rerun::Points3D::new(unique_positions.iter().map(crate::utils::vec3_array)),
166                )
167                .unwrap();
168
169                new_idx
170            };
171
172            // Add to face indices
173            face_indices.push(vertex_idx);
174        }
175
176        // Use indexed_triangles to create the mesh
177        Self::indexed_triangles(&unique_positions, &face_indices)
178    }
179
180    /// Create a triangle mesh graph from vertex positions and face indices.
181    /// Every chunk of three indices represents a triangle.
182    #[instrument]
183    pub fn indexed_triangles(vertex_positions: &[Vec3], face_indices: &[usize]) -> Self {
184        let mut mesh_graph = Self {
185            bvh: Bvh::new(),
186            bvh_workspace: BvhWorkspace::default(),
187            index_to_face_id: HashMap::with_capacity(face_indices.len() / 3),
188            next_index: 0,
189
190            vertices: SlotMap::with_capacity_and_key(vertex_positions.len()),
191            halfedges: SlotMap::with_capacity_and_key(face_indices.len()),
192            faces: SlotMap::with_capacity_and_key(face_indices.len() / 3),
193
194            positions: SecondaryMap::with_capacity(vertex_positions.len()),
195            vertex_normals: None,
196            outgoing_halfedges: SecondaryMap::with_capacity(vertex_positions.len()),
197        };
198
199        let mut vertex_ids = Vec::with_capacity(vertex_positions.len());
200
201        for pos in vertex_positions {
202            vertex_ids.push(mesh_graph.add_vertex(*pos));
203        }
204
205        for chunk in face_indices.chunks_exact(3) {
206            let a = vertex_ids[chunk[0]];
207            let b = vertex_ids[chunk[1]];
208            let c = vertex_ids[chunk[2]];
209
210            if a == b || b == c || c == a {
211                #[cfg(feature = "rerun")]
212                RR.log(
213                    "meshgraph/construct/zero_face",
214                    &rerun::Points3D::new(
215                        [
216                            mesh_graph.positions[a],
217                            mesh_graph.positions[b],
218                            mesh_graph.positions[c],
219                        ]
220                        .iter()
221                        .map(crate::utils::vec3_array),
222                    ),
223                )
224                .unwrap();
225
226                continue;
227            }
228
229            let he_a_id = mesh_graph.add_or_get_edge(a, b).start_to_end_he_id;
230            let he_b_id = mesh_graph.add_or_get_edge(b, c).start_to_end_he_id;
231            let he_c_id = mesh_graph.add_or_get_edge(c, a).start_to_end_he_id;
232
233            let _face_id = mesh_graph.add_face(he_a_id, he_b_id, he_c_id);
234        }
235
236        mesh_graph.make_all_outgoing_halfedges_boundary_if_possible();
237        mesh_graph.rebuild_bvh();
238
239        mesh_graph
240    }
241
242    /// Computes the vertex normal from neighboring faces
243    pub fn compute_vertex_normal(&mut self, vertex_id: VertexId) {
244        if self.vertex_normals.is_none() {
245            return;
246        }
247
248        let vertex = unwrap_or_return!(self.vertices.get(vertex_id), "Vertex not found");
249
250        let mut normal = Vec3::ZERO;
251
252        for face_id in vertex.faces(self) {
253            let face = unwrap_or_return!(self.faces.get(face_id), "Face not found");
254            normal += unwrap_or_return!(face.normal(self), "Face normal not found");
255        }
256
257        self.vertex_normals
258            .as_mut()
259            .unwrap()
260            .insert(vertex_id, normal.normalize());
261    }
262
263    /// Computes the vertex normals by averaging over the computed face normals
264    #[instrument(skip(self))]
265    pub fn compute_vertex_normals(&mut self) {
266        let mut normals = SecondaryMap::with_capacity(self.vertices.len());
267
268        for face in self.faces.values() {
269            let ha_a_id = face.halfedge;
270            let he_a = self.halfedges[ha_a_id];
271
272            let he_b_id = he_a
273                .next
274                .expect("Halfedge has definitely a face and thus a next halfedge");
275            let he_b = self.halfedges[he_b_id];
276
277            let a = match he_a.start_vertex(self) {
278                Some(v) => v,
279                None => {
280                    error!("Start vertex not found");
281                    continue;
282                }
283            };
284            let b = he_a.end_vertex;
285            let c = he_b.end_vertex;
286
287            let diff_a = self.positions[c] - self.positions[a];
288            let diff_b = self.positions[c] - self.positions[b];
289
290            // TODO : normalizing necessary here?
291            let face_normal = diff_a.cross(diff_b);
292
293            *normals.entry(a).unwrap().or_default() += face_normal;
294            *normals.entry(b).unwrap().or_default() += face_normal;
295            *normals.entry(c).unwrap().or_default() += face_normal;
296        }
297
298        self.vertex_normals = Some(normals);
299        self.normalize_vertex_normals();
300    }
301
302    /// Ensures that the vertex normals are all normalized
303    pub fn normalize_vertex_normals(&mut self) {
304        if let Some(normals) = &mut self.vertex_normals {
305            for normal in normals.values_mut() {
306                *normal = normal.normalize_or_zero();
307            }
308        }
309    }
310
311    /// Calls the `optimize_incremental` method of the BVH.
312    #[inline]
313    pub fn optimize_bvh_incremental(&mut self) {
314        self.bvh.optimize_incremental(&mut self.bvh_workspace);
315    }
316
317    /// Recomputes the bounding boxes of the BVH. This is necessary when the mesh is modified.
318    #[inline]
319    pub fn refit_bvh(&mut self) {
320        self.bvh.refit(&mut self.bvh_workspace);
321    }
322
323    /// Rebuilds the BVH from scratch
324    #[inline]
325    pub fn rebuild_bvh(&mut self) {
326        self.bvh = Bvh::new();
327        self.bvh_workspace = BvhWorkspace::default();
328
329        for face in self.faces.values() {
330            self.bvh
331                .insert_or_update_partially(face.aabb(self), face.index, 0.0);
332        }
333        self.bvh
334            .rebuild(&mut self.bvh_workspace, Default::default());
335    }
336
337    #[instrument(skip_all)]
338    pub fn rebuild_outgoing_halfedges(&mut self) {
339        self.outgoing_halfedges.clear();
340
341        for halfedge in self.halfedges.values() {
342            let Some(twin_id) = halfedge.twin else {
343                error!("Halfedge has no twin");
344                continue;
345            };
346
347            let Some(entry) = self.outgoing_halfedges.entry(halfedge.end_vertex) else {
348                error!("Vertex key invalid");
349                continue;
350            };
351
352            entry.or_default().push(twin_id);
353        }
354    }
355}