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;
52mod plane_slice;
53pub mod primitives;
54mod selection;
55#[cfg(feature = "rerun")]
56pub mod utils;
57
58pub use elements::*;
59pub use plane_slice::*;
60pub use selection::*;
61
62use hashbrown::HashMap;
63use itertools::Itertools;
64use parry3d::{
65    math::Point,
66    partitioning::{Qbvh, QbvhUpdateWorkspace},
67    shape::Triangle,
68};
69
70use glam::Vec3;
71use slotmap::{SecondaryMap, SlotMap};
72
73#[cfg(feature = "rerun")]
74lazy_static::lazy_static! {
75    pub static ref RR: rerun::RecordingStream = rerun::RecordingStreamBuilder::new("mesh_graph").spawn().unwrap();
76}
77
78/// Halfedge data structure for representing triangle meshes.
79///
80/// Please see the [crate documentation](crate) for more information.
81#[derive(Clone)]
82#[cfg_attr(feature = "bevy", derive(bevy::prelude::Component))]
83#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
84pub struct MeshGraph {
85    /// Acceleration structure for fast spatial queries. Uses parry3d's Qbvh to implement some of parry3d's spatial queries.
86    #[cfg_attr(feature = "serde", serde(skip))]
87    pub qbvh: Qbvh<Face>,
88    /// Used in conjunction with the QBVH to accelerate spatial queries.
89    #[cfg_attr(feature = "serde", serde(skip))]
90    pub qbvh_workspace: QbvhUpdateWorkspace,
91    /// Used to map indices stored in the QBVH to face IDs.
92    #[cfg_attr(feature = "serde", serde(skip))]
93    pub index_to_face_id: Vec<FaceId>,
94
95    /// Maps vertex IDs to their corresponding graph node
96    pub vertices: SlotMap<VertexId, Vertex>,
97    /// Maps halfedge IDs to their corresponding graph node
98    pub halfedges: SlotMap<HalfedgeId, Halfedge>,
99    /// Maps face IDs to their corresponding graph node
100    pub faces: SlotMap<FaceId, Face>,
101
102    /// Maps vertex IDs to their corresponding positions
103    pub positions: SecondaryMap<VertexId, Vec3>,
104    /// Maps vertex IDs to their corresponding normals
105    pub vertex_normals: Option<SecondaryMap<VertexId, Vec3>>,
106}
107
108impl MeshGraph {
109    /// Create a triangle mesh graph from vertex positions.
110    /// Every three positions represent a triangle.
111    ///
112    /// Vertices with the same position are merged into a single vertex.
113    pub fn triangles(vertex_positions: &[Vec3]) -> Self {
114        assert!(
115            vertex_positions.len().is_multiple_of(3),
116            "Number of vertex positions should be a multiple of 3"
117        );
118
119        // Create a map to track unique vertices
120        let mut unique_positions: Vec<Vec3> = Vec::with_capacity(vertex_positions.len() / 3);
121        let mut face_indices = Vec::with_capacity(vertex_positions.len());
122
123        for vertex_pos in vertex_positions {
124            // Check if we've seen this position before using a fuzzy float comparison
125            let mut idx = None;
126            for (j, pos) in unique_positions.iter().enumerate() {
127                const EPSILON: f32 = 1e-5;
128
129                if pos.distance_squared(*vertex_pos) < EPSILON {
130                    idx = Some(j);
131                    break;
132                }
133            }
134
135            // Use the existing index or add a new vertex
136            let vertex_idx = if let Some(idx) = idx {
137                idx
138            } else {
139                let new_idx = unique_positions.len();
140                unique_positions.push(*vertex_pos);
141
142                #[cfg(feature = "rerun")]
143                RR.log(
144                    "meshgraph/construct/vertices",
145                    &rerun::Points3D::new(unique_positions.iter().map(crate::utils::vec3_array)),
146                )
147                .unwrap();
148
149                new_idx
150            };
151
152            // Add to face indices
153            face_indices.push(vertex_idx);
154        }
155
156        // Use indexed_triangles to create the mesh
157        Self::indexed_triangles(&unique_positions, &face_indices)
158    }
159
160    /// Create a triangle mesh graph from vertex positions and face indices.
161    /// Every chunk of three indices represents a triangle.
162    pub fn indexed_triangles(vertex_positions: &[Vec3], face_indices: &[usize]) -> Self {
163        let mut graph = Self {
164            qbvh: Qbvh::new(),
165            qbvh_workspace: QbvhUpdateWorkspace::default(),
166            index_to_face_id: Vec::with_capacity(face_indices.len() / 3),
167
168            vertices: SlotMap::with_capacity_and_key(vertex_positions.len()),
169            halfedges: SlotMap::with_capacity_and_key(face_indices.len()),
170            faces: SlotMap::with_capacity_and_key(face_indices.len() / 3),
171
172            positions: SecondaryMap::with_capacity(vertex_positions.len()),
173            vertex_normals: None,
174        };
175
176        let mut vertex_ids = Vec::with_capacity(vertex_positions.len());
177
178        for pos in vertex_positions {
179            vertex_ids.push(graph.insert_vertex(*pos));
180        }
181
182        let mut start_end_vertex_to_halfedge =
183            HashMap::<(VertexId, VertexId), HalfedgeId>::default();
184
185        for chunk in face_indices.chunks_exact(3) {
186            let a = vertex_ids[chunk[0]];
187            let b = vertex_ids[chunk[1]];
188            let c = vertex_ids[chunk[2]];
189
190            if a == b || b == c || c == a {
191                #[cfg(feature = "rerun")]
192                RR.log(
193                    "meshgraph/construct/zero_face",
194                    &rerun::Points3D::new(
195                        [graph.positions[a], graph.positions[b], graph.positions[c]]
196                            .iter()
197                            .map(crate::utils::vec3_array),
198                    ),
199                )
200                .unwrap();
201
202                continue;
203            }
204
205            let he_a_id = graph.insert_halfedge(b);
206            let he_b_id = graph.insert_halfedge(c);
207            let he_c_id = graph.insert_halfedge(a);
208
209            start_end_vertex_to_halfedge.insert((b, c), he_b_id);
210            start_end_vertex_to_halfedge.insert((c, a), he_c_id);
211
212            let face_id = graph.insert_face(he_a_id);
213
214            let he_a = &mut graph.halfedges[he_a_id];
215            he_a.next = Some(he_b_id);
216            he_a.face = Some(face_id);
217
218            if let Some(twin_a_id) = start_end_vertex_to_halfedge.get(&(b, a)) {
219                he_a.twin = Some(*twin_a_id);
220                graph.halfedges[*twin_a_id].twin = Some(he_a_id);
221            } else {
222                start_end_vertex_to_halfedge.insert((a, b), he_a_id);
223            }
224
225            let he_b = &mut graph.halfedges[he_b_id];
226            he_b.next = Some(he_c_id);
227            he_b.face = Some(face_id);
228
229            if let Some(twin_b_id) = start_end_vertex_to_halfedge.get(&(c, b)) {
230                he_b.twin = Some(*twin_b_id);
231                graph.halfedges[*twin_b_id].twin = Some(he_b_id);
232            } else {
233                start_end_vertex_to_halfedge.insert((b, c), he_b_id);
234            }
235
236            let he_c = &mut graph.halfedges[he_c_id];
237            he_c.next = Some(he_a_id);
238            he_c.face = Some(face_id);
239
240            if let Some(twin_c_id) = start_end_vertex_to_halfedge.get(&(a, c)) {
241                he_c.twin = Some(*twin_c_id);
242                graph.halfedges[*twin_c_id].twin = Some(he_c_id);
243            } else {
244                start_end_vertex_to_halfedge.insert((c, a), he_c_id);
245            }
246
247            graph.vertices[a].outgoing_halfedge = Some(he_a_id);
248            graph.vertices[b].outgoing_halfedge = Some(he_b_id);
249            graph.vertices[c].outgoing_halfedge = Some(he_c_id);
250        }
251
252        graph.rebuild_qbvh();
253
254        graph
255    }
256
257    /// Computes the vertex normals by averaging over the computed face normals
258    pub fn compute_vertex_normals(&mut self) {
259        let mut normals = SecondaryMap::with_capacity(self.vertices.len());
260
261        for face in self.faces.values() {
262            let ha_a_id = face.halfedge;
263            let he_a = self.halfedges[ha_a_id];
264
265            let he_b_id = he_a
266                .next
267                .expect("Halfedge has definitely a face and thus a next halfedge");
268            let he_b = self.halfedges[he_b_id];
269
270            let a = he_a.start_vertex(self);
271            let b = he_a.end_vertex;
272            let c = he_b.end_vertex;
273
274            let diff_a = self.positions[c] - self.positions[a];
275            let diff_b = self.positions[c] - self.positions[b];
276
277            // TODO : normalizing necessary here?
278            let face_normal = diff_a.cross(diff_b);
279
280            *normals.entry(a).unwrap().or_default() += face_normal;
281            *normals.entry(b).unwrap().or_default() += face_normal;
282            *normals.entry(c).unwrap().or_default() += face_normal;
283        }
284
285        self.vertex_normals = Some(normals);
286        self.normalize_vertex_normals();
287    }
288
289    /// Ensures that the vertex normals are all normalized
290    pub fn normalize_vertex_normals(&mut self) {
291        if let Some(normals) = &mut self.vertex_normals {
292            for normal in normals.values_mut() {
293                *normal = normal.normalize();
294            }
295        }
296    }
297
298    pub fn rebalance_qbvh(&mut self) {
299        self.qbvh.rebalance(0.0, &mut self.qbvh_workspace);
300    }
301
302    /// Recomputes the bounding boxes of the QBVH. This is necessary when the mesh is modified.
303    ///
304    /// It does not no change the topology of the QBVH. Call `rebalance_qbvh` to to that.
305    pub fn refit_qbvh(&mut self) {
306        let mesh_graph = self.clone();
307
308        self.qbvh.refit(0.0, &mut self.qbvh_workspace, |face| {
309            let pos = face
310                .vertices(&mesh_graph)
311                .into_iter()
312                .map(|v| {
313                    let Vec3 { x, y, z } = mesh_graph.positions[v];
314                    Point::new(x, y, z)
315                })
316                .collect_vec();
317
318            Triangle::new(pos[0], pos[1], pos[2]).local_aabb()
319        });
320    }
321
322    fn recount_faces(&mut self) {
323        self.index_to_face_id.clear();
324        for (id, face) in &mut self.faces {
325            face.index = self.index_to_face_id.len();
326            self.index_to_face_id.push(id);
327        }
328    }
329
330    /// Clear and rebuild the QBVH.
331    pub fn rebuild_qbvh(&mut self) {
332        self.recount_faces();
333
334        let data = self
335            .faces
336            .values()
337            .map(|face| {
338                let pos = face
339                    .vertices(self)
340                    .into_iter()
341                    .map(|v| {
342                        let Vec3 { x, y, z } = self.positions[v];
343                        Point::new(x, y, z)
344                    })
345                    .collect_vec();
346
347                (*face, Triangle::new(pos[0], pos[1], pos[2]).local_aabb())
348            })
349            .collect_vec();
350
351        self.qbvh.clear_and_rebuild(data.into_iter(), 0.0);
352    }
353}
354
355#[cfg(feature = "rerun")]
356impl MeshGraph {
357    pub fn log_selection_rerun(&self, name: &str, selection: &Selection) {
358        use crate::RR;
359        use crate::utils::*;
360
361        RR.log(
362            format!("meshgraph/selection/{name}/points"),
363            &rerun::Points3D::new(
364                selection
365                    .vertices
366                    .iter()
367                    .map(|v| vec3_array(self.positions[*v]))
368                    .collect_vec(),
369            ),
370        )
371        .unwrap();
372
373        self.log_hes_rerun_with_name(
374            format!("meshgraph/selection/{name}/halfedges"),
375            &selection.halfedges.iter().copied().collect::<Vec<_>>(),
376        );
377
378        let face_centers = selection
379            .faces
380            .iter()
381            .map(|face_id| {
382                let face = self.faces[*face_id];
383
384                let center = face
385                    .vertices(self)
386                    .into_iter()
387                    .map(|v_id| self.positions[v_id])
388                    .reduce(|acc, p| acc + p)
389                    .unwrap()
390                    / 3.0;
391
392                vec3_array(center)
393            })
394            .collect_vec();
395
396        RR.log(
397            format!("meshgraph/selection/{name}/faces"),
398            &rerun::Points3D::new(face_centers),
399        )
400        .unwrap();
401    }
402
403    pub fn log_vert_rerun(&self, name: &str, vert: VertexId) {
404        use crate::RR;
405        use crate::utils::*;
406
407        let pos = self.positions[vert];
408
409        RR.log(
410            format!("meshgraph/vertex/{name}"),
411            &rerun::Points3D::new(vec![vec3_array(pos)]),
412        )
413        .unwrap();
414    }
415
416    pub fn log_he_rerun(&self, name: &str, halfedge: HalfedgeId) {
417        use crate::RR;
418        use crate::utils::*;
419
420        let he = self.halfedges[halfedge];
421
422        let start = self.positions[he.start_vertex(self)];
423        let end = self.positions[he.end_vertex];
424
425        RR.log(
426            format!("meshgraph/halfedge/{name}"),
427            &rerun::Arrows3D::from_vectors([vec3_array(end - start)])
428                .with_origins([vec3_array(start)]),
429        )
430        .unwrap();
431    }
432
433    pub fn log_hes_rerun(&self, name: &str, halfedges: &[HalfedgeId]) {
434        self.log_hes_rerun_with_name(format!("meshgraph/halfedge/{name}"), halfedges);
435    }
436
437    fn log_hes_rerun_with_name(&self, name: String, halfedges: &[HalfedgeId]) {
438        use crate::RR;
439        use crate::utils::*;
440
441        let mut origins = Vec::with_capacity(halfedges.len());
442        let mut vectors = Vec::with_capacity(halfedges.len());
443
444        for &he_id in halfedges {
445            let he = self.halfedges[he_id];
446
447            let start = self.positions[he.start_vertex(self)];
448            let end = self.positions[he.end_vertex];
449
450            origins.push(vec3_array(start));
451            vectors.push(vec3_array(end - start));
452        }
453
454        RR.log(
455            name,
456            &rerun::Arrows3D::from_vectors(vectors).with_origins(origins),
457        )
458        .unwrap();
459    }
460
461    pub fn log_face_rerun(&self, name: &str, face: FaceId) {
462        use crate::RR;
463        use crate::utils::*;
464
465        let mut origins = Vec::with_capacity(3);
466        let mut vectors = Vec::with_capacity(3);
467
468        let face = self.faces[face];
469
470        let pos = face
471            .vertices(self)
472            .iter()
473            .map(|v| self.positions[*v])
474            .collect::<Vec<_>>();
475
476        let center = pos.iter().copied().reduce(|a, b| a + b).unwrap() / pos.len() as f32;
477
478        let pos = face
479            .vertices(self)
480            .into_iter()
481            .zip(pos)
482            .map(|(v, p)| (v, center * 0.1 + p * 0.9))
483            .collect::<HashMap<_, _>>();
484
485        for he_id in face.halfedges(self) {
486            let he = self.halfedges[he_id];
487
488            let start = pos[&he.start_vertex(self)];
489            let end = pos[&he.end_vertex];
490
491            origins.push(vec3_array(start));
492            vectors.push(vec3_array(end - start));
493        }
494
495        #[cfg(feature = "rerun")]
496        {
497            RR.log(
498                format!("meshgraph/face/{name}"),
499                &rerun::Arrows3D::from_vectors(vectors).with_origins(origins),
500            )
501            .unwrap();
502        }
503    }
504
505    pub fn log_rerun(&self) {
506        use crate::RR;
507        use crate::utils::*;
508
509        let buffers = crate::integrations::VertexIndexBuffers::from(self.clone());
510        RR.log(
511            "meshgraph/mesh",
512            &rerun::Mesh3D::new(
513                buffers
514                    .positions
515                    .into_iter()
516                    .zip(buffers.normals.iter().cloned())
517                    .map(|(pos, norm)| vec3_array(pos - norm * 0.1)),
518            )
519            .with_triangle_indices(
520                buffers
521                    .indices
522                    .chunks(3)
523                    .map(|chunk| rerun::datatypes::UVec3D::new(chunk[0], chunk[1], chunk[2])),
524            )
525            .with_vertex_colors(
526                buffers
527                    .normals
528                    .into_iter()
529                    .map(|v| {
530                        [
531                            (100.0 + v.x * 100.0) as u8,
532                            (100.0 + v.y * 100.0) as u8,
533                            (100.0 + v.z * 100.0) as u8,
534                        ]
535                    })
536                    .collect::<Vec<_>>(),
537            ),
538        )
539        .unwrap();
540
541        RR.log(
542            "meshgraph/positions",
543            &rerun::Points3D::new(self.positions.values().map(vec3_array))
544                .with_radii(self.positions.iter().map(|_| 0.01)),
545        )
546        .unwrap();
547
548        let mut origins = Vec::with_capacity(self.faces.len() * 3);
549        let mut vectors = Vec::with_capacity(self.faces.len() * 3);
550
551        let mut he_to_pos = HashMap::<HalfedgeId, (Vec3, Vec3)>::default();
552
553        for face in self.faces.values() {
554            let pos = face
555                .vertices(self)
556                .iter()
557                .map(|v| self.positions[*v])
558                .collect::<Vec<_>>();
559
560            let center = pos.iter().copied().reduce(|a, b| a + b).unwrap() / pos.len() as f32;
561
562            let pos = face
563                .vertices(self)
564                .into_iter()
565                .zip(pos)
566                .map(|(v, p)| (v, center * 0.1 + p * 0.9))
567                .collect::<HashMap<_, _>>();
568
569            for he_id in face.halfedges(self) {
570                let he = self.halfedges[he_id];
571
572                let start = pos[&he.start_vertex(self)];
573                let end = pos[&he.end_vertex];
574
575                he_to_pos.insert(he_id, (start, end));
576
577                origins.push(vec3_array(start));
578                vectors.push(vec3_array(end - start));
579            }
580        }
581
582        for (he_id, he) in &self.halfedges {
583            if he.is_boundary() {
584                let start_vertex = he.start_vertex(self);
585                let end_vertex = he.end_vertex;
586
587                let start = self.positions[start_vertex];
588                let end = self.positions[end_vertex];
589
590                let offset = if let Some(normals) = self.vertex_normals.as_ref() {
591                    normals[start_vertex]
592                        .lerp(normals[end_vertex], 0.5)
593                        .cross(end - start)
594                        .normalize()
595                        * 0.1
596                } else {
597                    Vec3::ZERO
598                };
599
600                let (start, end) = (start.lerp(end, 0.1) + offset, end.lerp(start, 0.1) + offset);
601
602                he_to_pos.insert(he_id, (start, end));
603
604                origins.push(vec3_array(start));
605                vectors.push(vec3_array(end - start));
606            }
607        }
608
609        RR.log(
610            "meshgraph/halfedges",
611            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
612        )
613        .unwrap();
614
615        origins.clear();
616        vectors.clear();
617
618        for (he_id, he) in &self.halfedges {
619            let twin = he.twin();
620
621            let (he_start, he_end) = he_to_pos[&he_id];
622            let (tw_start, tw_end) = he_to_pos[&twin];
623
624            let start = he_start * 0.8 + he_end * 0.2;
625            let end = tw_start * 0.2 + tw_end * 0.8;
626
627            origins.push(vec3_array(start));
628            vectors.push(vec3_array(end - start));
629        }
630
631        RR.log(
632            "meshgraph/twins",
633            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
634        )
635        .unwrap();
636
637        origins.clear();
638        vectors.clear();
639
640        for (v_id, v) in &self.vertices {
641            if let Some(he) = v.outgoing_halfedge.as_ref() {
642                let start = self.positions[v_id];
643
644                RR.log(
645                    "meshgraph/the_vertex",
646                    &rerun::Points3D::new([vec3_array(start)]),
647                )
648                .unwrap();
649
650                let (start_he, end_he) = he_to_pos[he];
651
652                let end = start_he.lerp(end_he, 0.05);
653
654                origins.push(vec3_array(start));
655                vectors.push(vec3_array(end - start));
656            }
657        }
658
659        RR.log(
660            "meshgraph/outgoing_halfedges",
661            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
662        )
663        .unwrap();
664
665        origins.clear();
666        vectors.clear();
667
668        for face in self.faces.values() {
669            let start = face.center(self);
670
671            let (he_start, he_end) = he_to_pos[&face.halfedge];
672            let end = he_start * 0.6 + he_end * 0.4;
673
674            origins.push(vec3_array(start));
675            vectors.push(vec3_array(end - start));
676        }
677
678        RR.log(
679            "meshgraph/face_halfedges",
680            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
681        )
682        .unwrap();
683
684        origins.clear();
685        vectors.clear();
686
687        for (he_id, he) in &self.halfedges {
688            if let Some(face_id) = he.face {
689                let (he_start, he_end) = he_to_pos[&he_id];
690                let start = he_start * 0.4 + he_end * 0.6;
691
692                let end = self.faces[face_id].center(self);
693
694                origins.push(vec3_array(start));
695                vectors.push(vec3_array((end - start) * 0.9));
696            }
697        }
698
699        RR.log(
700            "meshgraph/halfedge_faces",
701            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
702        )
703        .unwrap();
704
705        origins.clear();
706        vectors.clear();
707
708        for (he_id, he) in &self.halfedges {
709            if let Some(next_id) = he.next {
710                let (he_start, he_end) = he_to_pos[&he_id];
711                let start = he_start * 0.15 + he_end * 0.85;
712
713                let (next_start, next_end) = he_to_pos[&next_id];
714                let end = next_start * 0.85 + next_end * 0.15;
715
716                origins.push(vec3_array(start));
717                vectors.push(vec3_array(end - start));
718            }
719        }
720
721        RR.log(
722            "meshgraph/halfedge_next",
723            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
724        )
725        .unwrap();
726    }
727}