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