mesh_graph/
lib.rs

1//! MeshGraph is a halfedge data structure for representing triangle meshes.
2//! It uses parry3d's Qbvh to implement some of parry3d's spatial queries.
3//! It also uses slotmap to manage the graph nodes.
4//!
5//! This is heavily inspired by [SMesh](https://github.com/Bendzae/SMesh) and
6//! [OpenMesh](https://gitlab.vci.rwth-aachen.de:9000/OpenMesh/OpenMesh).
7//!
8//! ## Features
9//!
10//! - Fast spatial queries using parry3d's Qbvh
11//! - High performance using slotmap
12//! - Easy integration with Bevy game engine using the `bevy` Cargo feature
13//! - Good debugging using `rerun` Cargo feature to enable the Rerun integration
14//! - Nice documentation with illustrations
15//!
16//! ## Usage
17//!
18//! ```
19//! use mesh_graph::{MeshGraph, primitives::IcoSphere};
20//!
21//! // Create a new mesh
22//! let mesh_graph = MeshGraph::from(IcoSphere { radius: 10.0, subdivisions: 2 });
23//!
24//! // Get some vertex ID and its vertex node
25//! let (vertex_id, vertex) = mesh_graph.vertices.iter().next().unwrap();
26//!
27//! // Iterate over all outgoing halfedges of the vertex
28//! for halfedge_id in vertex.outgoing_halfedges(&mesh_graph) {
29//!     // do sth
30//! }
31//!
32//! // Get the position of the vertex
33//! let position = mesh_graph.positions[vertex_id];
34//! ```
35//!
36//! Check out the crate [freestyle-sculpt](https://github.com/Synphonyte/freestyle-sculpt) for
37//! a heavy duty example.
38//!
39//! ## Connectivity
40//!
41//! ### Halfedge
42//!
43//! <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/halfedge/all.svg" alt="Connectivity" style="max-width: 28em" />
44//!
45//! ### Vertex
46//!
47//! <img src="https://raw.githubusercontent.com/Synphonyte/mesh-graph/refs/heads/main/docs/vertex/all.svg" alt="Connectivity" style="max-width: 50em" />
48
49mod elements;
50pub mod integrations;
51mod ops;
52pub mod primitives;
53mod selection;
54#[cfg(feature = "rerun")]
55pub mod utils;
56
57pub use elements::*;
58pub use selection::*;
59
60use hashbrown::HashMap;
61use itertools::Itertools;
62use parry3d::{
63    math::Point,
64    partitioning::{Qbvh, QbvhUpdateWorkspace},
65    shape::Triangle,
66};
67
68use glam::Vec3;
69use slotmap::{SecondaryMap, SlotMap};
70
71#[cfg(feature = "rerun")]
72lazy_static::lazy_static! {
73    pub static ref RR: rerun::RecordingStream = rerun::RecordingStreamBuilder::new("mesh_graph").spawn().unwrap();
74}
75
76/// Halfedge data structure for representing triangle meshes.
77///
78/// Please see the [crate documentation](crate) for more information.
79#[derive(Clone)]
80#[cfg_attr(feature = "bevy", derive(bevy::prelude::Component))]
81pub struct MeshGraph {
82    /// Acceleration structure for fast spatial queries. Uses parry3d's Qbvh to implement some of parry3d's spatial queries.
83    pub qbvh: Qbvh<Face>,
84    /// Used in conjunction with the QBVH to accelerate spatial queries.
85    pub qbvh_workspace: QbvhUpdateWorkspace,
86    /// Used to map indices stored in the QBVH to face IDs.
87    pub index_to_face_id: Vec<FaceId>,
88
89    /// Maps vertex IDs to their corresponding graph node
90    pub vertices: SlotMap<VertexId, Vertex>,
91    /// Maps halfedge IDs to their corresponding graph node
92    pub halfedges: SlotMap<HalfedgeId, Halfedge>,
93    /// Maps face IDs to their corresponding graph node
94    pub faces: SlotMap<FaceId, Face>,
95
96    /// Maps vertex IDs to their corresponding positions
97    pub positions: SecondaryMap<VertexId, Vec3>,
98    /// Maps vertex IDs to their corresponding normals
99    pub vertex_normals: Option<SecondaryMap<VertexId, Vec3>>,
100}
101
102impl MeshGraph {
103    /// Create a triangle mesh graph from vertex positions and face indices.
104    /// Every chunk of three indices represents a triangle.
105    pub fn indexed_triangles(vertex_positions: &[Vec3], face_indices: &[usize]) -> Self {
106        let mut graph = Self {
107            qbvh: Qbvh::new(),
108            qbvh_workspace: QbvhUpdateWorkspace::default(),
109            index_to_face_id: Vec::with_capacity(face_indices.len() / 3),
110
111            vertices: SlotMap::with_capacity_and_key(vertex_positions.len()),
112            halfedges: SlotMap::with_capacity_and_key(face_indices.len()),
113            faces: SlotMap::with_capacity_and_key(face_indices.len() / 3),
114
115            positions: SecondaryMap::with_capacity(vertex_positions.len()),
116            vertex_normals: None,
117        };
118
119        let mut vertex_ids = Vec::with_capacity(vertex_positions.len());
120
121        for pos in vertex_positions {
122            vertex_ids.push(graph.insert_vertex(*pos));
123        }
124
125        let mut start_end_vertex_to_halfedge =
126            HashMap::<(VertexId, VertexId), HalfedgeId>::default();
127
128        for chunk in face_indices.chunks_exact(3) {
129            let a = vertex_ids[chunk[0]];
130            let b = vertex_ids[chunk[1]];
131            let c = vertex_ids[chunk[2]];
132
133            let he_a_id = graph.insert_halfedge(b);
134            let he_b_id = graph.insert_halfedge(c);
135            let he_c_id = graph.insert_halfedge(a);
136
137            start_end_vertex_to_halfedge.insert((b, c), he_b_id);
138            start_end_vertex_to_halfedge.insert((c, a), he_c_id);
139
140            let face_id = graph.insert_face(he_a_id);
141
142            let he_a = &mut graph.halfedges[he_a_id];
143            he_a.next = Some(he_b_id);
144            he_a.face = Some(face_id);
145
146            if let Some(twin_a_id) = start_end_vertex_to_halfedge.get(&(b, a)) {
147                he_a.twin = Some(*twin_a_id);
148                graph.halfedges[*twin_a_id].twin = Some(he_a_id);
149            } else {
150                start_end_vertex_to_halfedge.insert((a, b), he_a_id);
151            }
152
153            let he_b = &mut graph.halfedges[he_b_id];
154            he_b.next = Some(he_c_id);
155            he_b.face = Some(face_id);
156
157            if let Some(twin_b_id) = start_end_vertex_to_halfedge.get(&(c, b)) {
158                he_b.twin = Some(*twin_b_id);
159                graph.halfedges[*twin_b_id].twin = Some(he_b_id);
160            } else {
161                start_end_vertex_to_halfedge.insert((b, c), he_b_id);
162            }
163
164            let he_c = &mut graph.halfedges[he_c_id];
165            he_c.next = Some(he_a_id);
166            he_c.face = Some(face_id);
167
168            if let Some(twin_c_id) = start_end_vertex_to_halfedge.get(&(a, c)) {
169                he_c.twin = Some(*twin_c_id);
170                graph.halfedges[*twin_c_id].twin = Some(he_c_id);
171            } else {
172                start_end_vertex_to_halfedge.insert((c, a), he_c_id);
173            }
174
175            graph.vertices[a].outgoing_halfedge = Some(he_a_id);
176            graph.vertices[b].outgoing_halfedge = Some(he_b_id);
177            graph.vertices[c].outgoing_halfedge = Some(he_c_id);
178        }
179
180        graph.rebuild_qbvh();
181
182        graph
183    }
184
185    /// Computes the vertex normals by averaging over the computed face normals
186    pub fn compute_vertex_normals(&mut self) {
187        let mut normals = SecondaryMap::with_capacity(self.vertices.len());
188
189        for face in self.faces.values() {
190            let ha_a_id = face.halfedge;
191            let he_a = self.halfedges[ha_a_id];
192
193            let he_b_id = he_a
194                .next
195                .expect("Halfedge has definitely a face and thus a next halfedge");
196            let he_b = self.halfedges[he_b_id];
197
198            let a = he_a.start_vertex(self);
199            let b = he_a.end_vertex;
200            let c = he_b.end_vertex;
201
202            let diff_a = self.positions[c] - self.positions[a];
203            let diff_b = self.positions[c] - self.positions[b];
204
205            // TODO : normalizing necessary here?
206            let face_normal = diff_a.cross(diff_b);
207
208            *normals.entry(a).unwrap().or_default() += face_normal;
209            *normals.entry(b).unwrap().or_default() += face_normal;
210            *normals.entry(c).unwrap().or_default() += face_normal;
211        }
212
213        self.vertex_normals = Some(normals);
214        self.normalize_vertex_normals();
215    }
216
217    /// Ensures that the vertex normals are all normalized
218    pub fn normalize_vertex_normals(&mut self) {
219        if let Some(normals) = &mut self.vertex_normals {
220            for normal in normals.values_mut() {
221                *normal = normal.normalize();
222            }
223        }
224    }
225
226    pub fn rebalance_qbvh(&mut self) {
227        self.qbvh.rebalance(0.0, &mut self.qbvh_workspace);
228    }
229
230    /// Recomputes the bounding boxes of the QBVH. This is necessary when the mesh is modified.
231    ///
232    /// It does not no change the topology of the QBVH. Call `rebalance_qbvh` to to that.
233    pub fn refit_qbvh(&mut self) {
234        let mesh_graph = self.clone();
235
236        self.qbvh.refit(0.0, &mut self.qbvh_workspace, |face| {
237            let pos = face
238                .vertices(&mesh_graph)
239                .into_iter()
240                .map(|v| {
241                    let Vec3 { x, y, z } = mesh_graph.positions[v];
242                    Point::new(x, y, z)
243                })
244                .collect_vec();
245
246            Triangle::new(pos[0], pos[1], pos[2]).local_aabb()
247        });
248    }
249
250    fn recount_faces(&mut self) {
251        self.index_to_face_id.clear();
252        for (id, face) in &mut self.faces {
253            face.index = self.index_to_face_id.len();
254            self.index_to_face_id.push(id);
255        }
256    }
257
258    /// Clear and rebuild the QBVH.
259    pub fn rebuild_qbvh(&mut self) {
260        self.recount_faces();
261
262        let data = self
263            .faces
264            .values()
265            .map(|face| {
266                let pos = face
267                    .vertices(self)
268                    .into_iter()
269                    .map(|v| {
270                        let Vec3 { x, y, z } = self.positions[v];
271                        Point::new(x, y, z)
272                    })
273                    .collect_vec();
274
275                (*face, Triangle::new(pos[0], pos[1], pos[2]).local_aabb())
276            })
277            .collect_vec();
278
279        self.qbvh.clear_and_rebuild(data.into_iter(), 0.0);
280    }
281}
282
283#[cfg(feature = "rerun")]
284impl MeshGraph {
285    pub fn log_vert_rerun(&self, name: &str, vert: VertexId) {
286        use crate::RR;
287        use crate::utils::*;
288
289        let pos = self.positions[vert];
290
291        RR.log(
292            format!("meshgraph/vertex/{name}"),
293            &rerun::Points3D::new(vec![vec3_array(pos)]),
294        )
295        .unwrap();
296    }
297
298    pub fn log_he_rerun(&self, name: &str, halfedge: HalfedgeId) {
299        use crate::RR;
300        use crate::utils::*;
301
302        let he = self.halfedges[halfedge];
303
304        let start = self.positions[he.start_vertex(self)];
305        let end = self.positions[he.end_vertex];
306
307        RR.log(
308            format!("meshgraph/halfedge/{name}"),
309            &rerun::Arrows3D::from_vectors([vec3_array(end - start)])
310                .with_origins([vec3_array(start)]),
311        )
312        .unwrap();
313    }
314
315    pub fn log_hes_rerun(&self, name: &str, halfedges: &[HalfedgeId]) {
316        use crate::RR;
317        use crate::utils::*;
318
319        let mut origins = Vec::with_capacity(halfedges.len());
320        let mut vectors = Vec::with_capacity(halfedges.len());
321
322        for &he_id in halfedges {
323            let he = self.halfedges[he_id];
324
325            let start = self.positions[he.start_vertex(self)];
326            let end = self.positions[he.end_vertex];
327
328            origins.push(vec3_array(start));
329            vectors.push(vec3_array(end - start));
330        }
331
332        RR.log(
333            format!("meshgraph/halfedge/{name}"),
334            &rerun::Arrows3D::from_vectors(vectors).with_origins(origins),
335        )
336        .unwrap();
337    }
338
339    pub fn log_face_rerun(&self, name: &str, face: FaceId) {
340        use crate::RR;
341        use crate::utils::*;
342
343        let mut origins = Vec::with_capacity(3);
344        let mut vectors = Vec::with_capacity(3);
345
346        let face = self.faces[face];
347
348        let pos = face
349            .vertices(self)
350            .iter()
351            .map(|v| self.positions[*v])
352            .collect::<Vec<_>>();
353
354        let center = pos.iter().copied().reduce(|a, b| a + b).unwrap() / pos.len() as f32;
355
356        let pos = face
357            .vertices(self)
358            .into_iter()
359            .zip(pos)
360            .map(|(v, p)| (v, center * 0.1 + p * 0.9))
361            .collect::<HashMap<_, _>>();
362
363        for he_id in face.halfedges(self) {
364            let he = self.halfedges[he_id];
365
366            let start = pos[&he.start_vertex(self)];
367            let end = pos[&he.end_vertex];
368
369            origins.push(vec3_array(start));
370            vectors.push(vec3_array(end - start));
371        }
372
373        #[cfg(feature = "rerun")]
374        {
375            RR.log(
376                format!("meshgraph/face/{name}"),
377                &rerun::Arrows3D::from_vectors(vectors).with_origins(origins),
378            )
379            .unwrap();
380        }
381    }
382
383    pub fn log_rerun(&self) {
384        use crate::RR;
385        use crate::utils::*;
386
387        let buffers = crate::integrations::VertexIndexBuffers::from(self.clone());
388        RR.log(
389            "meshgraph/mesh",
390            &rerun::Mesh3D::new(
391                buffers
392                    .positions
393                    .into_iter()
394                    .zip(buffers.normals.iter().cloned())
395                    .map(|(pos, norm)| vec3_array(pos - norm * 0.1)),
396            )
397            .with_triangle_indices(
398                buffers
399                    .indices
400                    .chunks(3)
401                    .map(|chunk| rerun::datatypes::UVec3D::new(chunk[0], chunk[1], chunk[2])),
402            )
403            .with_vertex_colors(
404                buffers
405                    .normals
406                    .into_iter()
407                    .map(|v| {
408                        [
409                            (100.0 + v.x * 100.0) as u8,
410                            (100.0 + v.y * 100.0) as u8,
411                            (100.0 + v.z * 100.0) as u8,
412                        ]
413                    })
414                    .collect::<Vec<_>>(),
415            ),
416        )
417        .unwrap();
418
419        RR.log(
420            "meshgraph/positions",
421            &rerun::Points3D::new(self.positions.values().map(vec3_array))
422                .with_radii(self.positions.iter().map(|_| 0.1)),
423        )
424        .unwrap();
425
426        let mut origins = Vec::with_capacity(self.faces.len() * 3);
427        let mut vectors = Vec::with_capacity(self.faces.len() * 3);
428
429        let mut he_to_pos = HashMap::<HalfedgeId, (Vec3, Vec3)>::default();
430
431        for face in self.faces.values() {
432            let pos = face
433                .vertices(self)
434                .iter()
435                .map(|v| self.positions[*v])
436                .collect::<Vec<_>>();
437
438            let center = pos.iter().copied().reduce(|a, b| a + b).unwrap() / pos.len() as f32;
439
440            let pos = face
441                .vertices(self)
442                .into_iter()
443                .zip(pos)
444                .map(|(v, p)| (v, center * 0.1 + p * 0.9))
445                .collect::<HashMap<_, _>>();
446
447            for he_id in face.halfedges(self) {
448                let he = self.halfedges[he_id];
449
450                let start = pos[&he.start_vertex(self)];
451                let end = pos[&he.end_vertex];
452
453                he_to_pos.insert(he_id, (start, end));
454
455                origins.push(vec3_array(start));
456                vectors.push(vec3_array(end - start));
457            }
458        }
459
460        for (he_id, he) in &self.halfedges {
461            if he.is_boundary() {
462                let start_vertex = he.start_vertex(self);
463                let end_vertex = he.end_vertex;
464
465                let start = self.positions[start_vertex];
466                let end = self.positions[end_vertex];
467
468                let offset = if let Some(normals) = self.vertex_normals.as_ref() {
469                    normals[start_vertex]
470                        .lerp(normals[end_vertex], 0.5)
471                        .cross(end - start)
472                        .normalize()
473                        * 0.1
474                } else {
475                    Vec3::ZERO
476                };
477
478                let (start, end) = (start.lerp(end, 0.1) + offset, end.lerp(start, 0.1) + offset);
479
480                he_to_pos.insert(he_id, (start, end));
481
482                origins.push(vec3_array(start));
483                vectors.push(vec3_array(end - start));
484            }
485        }
486
487        RR.log(
488            "meshgraph/halfedges",
489            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
490        )
491        .unwrap();
492
493        origins.clear();
494        vectors.clear();
495
496        for (he_id, he) in &self.halfedges {
497            let twin = he.twin();
498
499            let (he_start, he_end) = he_to_pos[&he_id];
500            let (tw_start, tw_end) = he_to_pos[&twin];
501
502            let start = he_start * 0.8 + he_end * 0.2;
503            let end = tw_start * 0.2 + tw_end * 0.8;
504
505            origins.push(vec3_array(start));
506            vectors.push(vec3_array(end - start));
507        }
508
509        RR.log(
510            "meshgraph/twins",
511            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
512        )
513        .unwrap();
514
515        origins.clear();
516        vectors.clear();
517
518        for (v_id, v) in &self.vertices {
519            if let Some(he) = v.outgoing_halfedge.as_ref() {
520                let start = self.positions[v_id];
521
522                RR.log(
523                    "meshgraph/the_vertex",
524                    &rerun::Points3D::new([vec3_array(start)]),
525                )
526                .unwrap();
527
528                let (start_he, end_he) = he_to_pos[he];
529
530                let end = start_he.lerp(end_he, 0.05);
531
532                origins.push(vec3_array(start));
533                vectors.push(vec3_array(end - start));
534            }
535        }
536
537        RR.log(
538            "meshgraph/outgoing_halfedges",
539            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
540        )
541        .unwrap();
542
543        origins.clear();
544        vectors.clear();
545
546        for face in self.faces.values() {
547            let start = face.center(self);
548
549            let (he_start, he_end) = he_to_pos[&face.halfedge];
550            let end = he_start * 0.6 + he_end * 0.4;
551
552            origins.push(vec3_array(start));
553            vectors.push(vec3_array(end - start));
554        }
555
556        RR.log(
557            "meshgraph/face_halfedges",
558            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
559        )
560        .unwrap();
561
562        origins.clear();
563        vectors.clear();
564
565        for (he_id, he) in &self.halfedges {
566            if let Some(face_id) = he.face {
567                let (he_start, he_end) = he_to_pos[&he_id];
568                let start = he_start * 0.4 + he_end * 0.6;
569
570                let end = self.faces[face_id].center(self);
571
572                origins.push(vec3_array(start));
573                vectors.push(vec3_array((end - start) * 0.9));
574            }
575        }
576
577        RR.log(
578            "meshgraph/halfedge_faces",
579            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
580        )
581        .unwrap();
582    }
583}