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_ids = Vec::with_capacity(vertex_positions.len());
188
189        for pos in vertex_positions {
190            vertex_ids.push(mesh_graph.insert_vertex(*pos));
191        }
192
193        let mut start_end_vertex_to_halfedge =
194            HashMap::<(VertexId, VertexId), HalfedgeId>::default();
195
196        for chunk in face_indices.chunks_exact(3) {
197            let a = vertex_ids[chunk[0]];
198            let b = vertex_ids[chunk[1]];
199            let c = vertex_ids[chunk[2]];
200
201            if a == b || b == c || c == a {
202                #[cfg(feature = "rerun")]
203                RR.log(
204                    "meshgraph/construct/zero_face",
205                    &rerun::Points3D::new(
206                        [
207                            mesh_graph.positions[a],
208                            mesh_graph.positions[b],
209                            mesh_graph.positions[c],
210                        ]
211                        .iter()
212                        .map(crate::utils::vec3_array),
213                    ),
214                )
215                .unwrap();
216
217                continue;
218            }
219
220            let he_a_id = mesh_graph.insert_halfedge(b);
221            let he_b_id = mesh_graph.insert_halfedge(c);
222            let he_c_id = mesh_graph.insert_halfedge(a);
223
224            start_end_vertex_to_halfedge.insert((b, c), he_b_id);
225            start_end_vertex_to_halfedge.insert((c, a), he_c_id);
226
227            let face_id = mesh_graph.insert_face(he_a_id);
228
229            let he_a = &mut mesh_graph.halfedges[he_a_id];
230            he_a.next = Some(he_b_id);
231            he_a.face = Some(face_id);
232
233            if let Some(twin_a_id) = start_end_vertex_to_halfedge.get(&(b, a)) {
234                he_a.twin = Some(*twin_a_id);
235                mesh_graph.halfedges[*twin_a_id].twin = Some(he_a_id);
236            } else {
237                start_end_vertex_to_halfedge.insert((a, b), he_a_id);
238            }
239
240            let he_b = &mut mesh_graph.halfedges[he_b_id];
241            he_b.next = Some(he_c_id);
242            he_b.face = Some(face_id);
243
244            if let Some(twin_b_id) = start_end_vertex_to_halfedge.get(&(c, b)) {
245                he_b.twin = Some(*twin_b_id);
246                mesh_graph.halfedges[*twin_b_id].twin = Some(he_b_id);
247            } else {
248                start_end_vertex_to_halfedge.insert((b, c), he_b_id);
249            }
250
251            let he_c = &mut mesh_graph.halfedges[he_c_id];
252            he_c.next = Some(he_a_id);
253            he_c.face = Some(face_id);
254
255            if let Some(twin_c_id) = start_end_vertex_to_halfedge.get(&(a, c)) {
256                he_c.twin = Some(*twin_c_id);
257                mesh_graph.halfedges[*twin_c_id].twin = Some(he_c_id);
258            } else {
259                start_end_vertex_to_halfedge.insert((c, a), he_c_id);
260            }
261
262            mesh_graph.vertices[a].outgoing_halfedge = Some(he_a_id);
263            mesh_graph.vertices[b].outgoing_halfedge = Some(he_b_id);
264            mesh_graph.vertices[c].outgoing_halfedge = Some(he_c_id);
265        }
266
267        mesh_graph.rebuild_bvh();
268
269        mesh_graph
270    }
271
272    /// Computes the vertex normals by averaging over the computed face normals
273    #[instrument(skip(self))]
274    pub fn compute_vertex_normals(&mut self) {
275        let mut normals = SecondaryMap::with_capacity(self.vertices.len());
276
277        for face in self.faces.values() {
278            let ha_a_id = face.halfedge;
279            let he_a = self.halfedges[ha_a_id];
280
281            let he_b_id = he_a
282                .next
283                .expect("Halfedge has definitely a face and thus a next halfedge");
284            let he_b = self.halfedges[he_b_id];
285
286            let a = match he_a.start_vertex(self) {
287                Some(v) => v,
288                None => {
289                    error!("Start vertex not found");
290                    continue;
291                }
292            };
293            let b = he_a.end_vertex;
294            let c = he_b.end_vertex;
295
296            let diff_a = self.positions[c] - self.positions[a];
297            let diff_b = self.positions[c] - self.positions[b];
298
299            // TODO : normalizing necessary here?
300            let face_normal = diff_a.cross(diff_b);
301
302            *normals.entry(a).unwrap().or_default() += face_normal;
303            *normals.entry(b).unwrap().or_default() += face_normal;
304            *normals.entry(c).unwrap().or_default() += face_normal;
305        }
306
307        self.vertex_normals = Some(normals);
308        self.normalize_vertex_normals();
309    }
310
311    /// Ensures that the vertex normals are all normalized
312    pub fn normalize_vertex_normals(&mut self) {
313        if let Some(normals) = &mut self.vertex_normals {
314            for normal in normals.values_mut() {
315                *normal = normal.normalize();
316            }
317        }
318    }
319
320    /// Calls the `optimize_incremental` method of the BVH.
321    #[inline]
322    pub fn optimize_bvh_incremental(&mut self) {
323        self.bvh.optimize_incremental(&mut self.bvh_workspace);
324    }
325
326    /// Recomputes the bounding boxes of the BVH. This is necessary when the mesh is modified.
327    #[inline]
328    pub fn refit_bvh(&mut self) {
329        self.bvh.refit(&mut self.bvh_workspace);
330    }
331
332    /// Rebuilds the BVH from scratch
333    #[inline]
334    pub fn rebuild_bvh(&mut self) {
335        for face in self.faces.values() {
336            self.bvh
337                .insert_or_update_partially(face.aabb(self), face.index, 0.0);
338        }
339        self.bvh
340            .rebuild(&mut self.bvh_workspace, Default::default());
341    }
342}
343
344#[cfg(feature = "rerun")]
345impl MeshGraph {
346    pub fn log_selection_rerun(&self, name: &str, selection: &Selection) {
347        use itertools::Itertools;
348
349        use crate::RR;
350        use crate::utils::*;
351
352        RR.log(
353            format!("meshgraph/selection/{name}/points"),
354            &rerun::Points3D::new(
355                selection
356                    .vertices
357                    .iter()
358                    .map(|v| vec3_array(self.positions[*v]))
359                    .collect_vec(),
360            ),
361        )
362        .unwrap();
363
364        self.log_hes_rerun_with_name(
365            format!("meshgraph/selection/{name}/halfedges"),
366            &selection.halfedges.iter().copied().collect::<Vec<_>>(),
367        );
368
369        let face_centers = selection
370            .faces
371            .iter()
372            .map(|face_id| {
373                let face = self.faces[*face_id];
374
375                let center = face
376                    .vertices(self)
377                    .into_iter()
378                    .map(|v_id| self.positions[v_id])
379                    .reduce(|acc, p| acc + p)
380                    .unwrap()
381                    / 3.0;
382
383                vec3_array(center)
384            })
385            .collect_vec();
386
387        RR.log(
388            format!("meshgraph/selection/{name}/faces"),
389            &rerun::Points3D::new(face_centers),
390        )
391        .unwrap();
392    }
393
394    pub fn log_vert_rerun(&self, name: &str, vert: VertexId) {
395        use crate::RR;
396        use crate::utils::*;
397
398        let pos = self.positions[vert];
399
400        RR.log(
401            format!("meshgraph/vertex/{name}"),
402            &rerun::Points3D::new(vec![vec3_array(pos)]),
403        )
404        .unwrap();
405    }
406
407    pub fn log_he_rerun(&self, name: &str, halfedge: HalfedgeId) {
408        use crate::RR;
409        use crate::utils::*;
410
411        let he = self.halfedges[halfedge];
412
413        let start = self.positions[he.start_vertex(self).unwrap()];
414        let end = self.positions[he.end_vertex];
415
416        RR.log(
417            format!("meshgraph/halfedge/{name}"),
418            &rerun::Arrows3D::from_vectors([vec3_array(end - start)])
419                .with_origins([vec3_array(start)]),
420        )
421        .unwrap();
422    }
423
424    pub fn log_hes_rerun(&self, name: &str, halfedges: &[HalfedgeId]) {
425        self.log_hes_rerun_with_name(format!("meshgraph/halfedge/{name}"), halfedges);
426    }
427
428    fn log_hes_rerun_with_name(&self, name: String, halfedges: &[HalfedgeId]) {
429        use crate::RR;
430        use crate::utils::*;
431
432        let mut origins = Vec::with_capacity(halfedges.len());
433        let mut vectors = Vec::with_capacity(halfedges.len());
434
435        for &he_id in halfedges {
436            let he = self.halfedges[he_id];
437
438            let start = self.positions[he.start_vertex(self).unwrap()];
439            let end = self.positions[he.end_vertex];
440
441            origins.push(vec3_array(start));
442            vectors.push(vec3_array(end - start));
443        }
444
445        RR.log(
446            name,
447            &rerun::Arrows3D::from_vectors(vectors).with_origins(origins),
448        )
449        .unwrap();
450    }
451
452    pub fn log_face_rerun(&self, name: &str, face: FaceId) {
453        use itertools::Itertools;
454
455        use crate::RR;
456        use crate::utils::*;
457
458        let mut origins = Vec::with_capacity(3);
459        let mut vectors = Vec::with_capacity(3);
460
461        let face = self.faces[face];
462
463        let pos = face.vertices(self).map(|v| self.positions[v]).collect_vec();
464
465        let center = pos.iter().copied().reduce(|a, b| a + b).unwrap() / pos.len() as f32;
466
467        let pos = face
468            .vertices(self)
469            .into_iter()
470            .zip(pos)
471            .map(|(v, p)| (v, center * 0.1 + p * 0.9))
472            .collect::<HashMap<_, _>>();
473
474        for he_id in face.halfedges(self) {
475            let he = self.halfedges[he_id];
476
477            let start = pos[&he.start_vertex(self).unwrap()];
478            let end = pos[&he.end_vertex];
479
480            origins.push(vec3_array(start));
481            vectors.push(vec3_array(end - start));
482        }
483
484        #[cfg(feature = "rerun")]
485        {
486            RR.log(
487                format!("meshgraph/face/{name}"),
488                &rerun::Arrows3D::from_vectors(vectors).with_origins(origins),
489            )
490            .unwrap();
491        }
492    }
493
494    pub fn log_rerun(&self) {
495        use crate::RR;
496        use crate::utils::*;
497
498        let buffers = crate::integrations::VertexIndexBuffers::from(self.clone());
499        RR.log(
500            "meshgraph/mesh",
501            &rerun::Mesh3D::new(
502                buffers
503                    .positions
504                    .into_iter()
505                    .zip(buffers.normals.iter().cloned())
506                    .map(|(pos, norm)| vec3_array(pos - norm * 0.1)),
507            )
508            .with_triangle_indices(
509                buffers
510                    .indices
511                    .chunks(3)
512                    .map(|chunk| rerun::datatypes::UVec3D::new(chunk[0], chunk[1], chunk[2])),
513            )
514            .with_vertex_colors(
515                buffers
516                    .normals
517                    .into_iter()
518                    .map(|v| {
519                        [
520                            (100.0 + v.x * 100.0) as u8,
521                            (100.0 + v.y * 100.0) as u8,
522                            (100.0 + v.z * 100.0) as u8,
523                        ]
524                    })
525                    .collect::<Vec<_>>(),
526            ),
527        )
528        .unwrap();
529
530        RR.log(
531            "meshgraph/positions",
532            &rerun::Points3D::new(self.positions.values().map(vec3_array))
533                .with_radii(self.positions.iter().map(|_| 0.01)),
534        )
535        .unwrap();
536
537        let mut origins = Vec::with_capacity(self.faces.len() * 3);
538        let mut vectors = Vec::with_capacity(self.faces.len() * 3);
539
540        let mut he_to_pos = HashMap::<HalfedgeId, (Vec3, Vec3)>::default();
541
542        for face in self.faces.values() {
543            use itertools::Itertools;
544
545            let pos = face.vertices(self).map(|v| self.positions[v]).collect_vec();
546
547            let center = pos.iter().copied().reduce(|a, b| a + b).unwrap() / pos.len() as f32;
548
549            let pos = face
550                .vertices(self)
551                .into_iter()
552                .zip(pos)
553                .map(|(v, p)| (v, center * 0.1 + p * 0.9))
554                .collect::<HashMap<_, _>>();
555
556            for he_id in face.halfedges(self) {
557                let he = self.halfedges[he_id];
558
559                let start = pos[&he.start_vertex(self).unwrap()];
560                let end = pos[&he.end_vertex];
561
562                he_to_pos.insert(he_id, (start, end));
563
564                origins.push(vec3_array(start));
565                vectors.push(vec3_array(end - start));
566            }
567        }
568
569        for (he_id, he) in &self.halfedges {
570            if he.is_boundary() {
571                let start_vertex = he.start_vertex(self).unwrap();
572                let end_vertex = he.end_vertex;
573
574                let start = self.positions[start_vertex];
575                let end = self.positions[end_vertex];
576
577                let offset = if let Some(normals) = self.vertex_normals.as_ref() {
578                    normals[start_vertex]
579                        .lerp(normals[end_vertex], 0.5)
580                        .cross(end - start)
581                        .normalize()
582                        * 0.1
583                } else {
584                    Vec3::ZERO
585                };
586
587                let (start, end) = (start.lerp(end, 0.1) + offset, end.lerp(start, 0.1) + offset);
588
589                he_to_pos.insert(he_id, (start, end));
590
591                origins.push(vec3_array(start));
592                vectors.push(vec3_array(end - start));
593            }
594        }
595
596        RR.log(
597            "meshgraph/halfedges",
598            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
599        )
600        .unwrap();
601
602        origins.clear();
603        vectors.clear();
604
605        for (he_id, he) in &self.halfedges {
606            let twin = he.twin.unwrap();
607
608            let (he_start, he_end) = he_to_pos[&he_id];
609            let (tw_start, tw_end) = he_to_pos[&twin];
610
611            let start = he_start * 0.8 + he_end * 0.2;
612            let end = tw_start * 0.2 + tw_end * 0.8;
613
614            origins.push(vec3_array(start));
615            vectors.push(vec3_array(end - start));
616        }
617
618        RR.log(
619            "meshgraph/twins",
620            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
621        )
622        .unwrap();
623
624        origins.clear();
625        vectors.clear();
626
627        for (v_id, v) in &self.vertices {
628            if let Some(he) = v.outgoing_halfedge.as_ref() {
629                let start = self.positions[v_id];
630
631                RR.log(
632                    "meshgraph/the_vertex",
633                    &rerun::Points3D::new([vec3_array(start)]),
634                )
635                .unwrap();
636
637                let (start_he, end_he) = he_to_pos[he];
638
639                let end = start_he.lerp(end_he, 0.05);
640
641                origins.push(vec3_array(start));
642                vectors.push(vec3_array(end - start));
643            }
644        }
645
646        RR.log(
647            "meshgraph/outgoing_halfedges",
648            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
649        )
650        .unwrap();
651
652        origins.clear();
653        vectors.clear();
654
655        for face in self.faces.values() {
656            let start = face.center(self);
657
658            let (he_start, he_end) = he_to_pos[&face.halfedge];
659            let end = he_start * 0.6 + he_end * 0.4;
660
661            origins.push(vec3_array(start));
662            vectors.push(vec3_array(end - start));
663        }
664
665        RR.log(
666            "meshgraph/face_halfedges",
667            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
668        )
669        .unwrap();
670
671        origins.clear();
672        vectors.clear();
673
674        for (he_id, he) in &self.halfedges {
675            if let Some(face_id) = he.face {
676                let (he_start, he_end) = he_to_pos[&he_id];
677                let start = he_start * 0.4 + he_end * 0.6;
678
679                let end = self.faces[face_id].center(self);
680
681                origins.push(vec3_array(start));
682                vectors.push(vec3_array((end - start) * 0.9));
683            }
684        }
685
686        RR.log(
687            "meshgraph/halfedge_faces",
688            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
689        )
690        .unwrap();
691
692        origins.clear();
693        vectors.clear();
694
695        for (he_id, he) in &self.halfedges {
696            if let Some(next_id) = he.next {
697                let (he_start, he_end) = he_to_pos[&he_id];
698                let start = he_start * 0.15 + he_end * 0.85;
699
700                let (next_start, next_end) = he_to_pos[&next_id];
701                let end = next_start * 0.85 + next_end * 0.15;
702
703                origins.push(vec3_array(start));
704                vectors.push(vec3_array(end - start));
705            }
706        }
707
708        RR.log(
709            "meshgraph/halfedge_next",
710            &rerun::Arrows3D::from_vectors(&vectors).with_origins(&origins),
711        )
712        .unwrap();
713    }
714}