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