Skip to main content

axiolid_mesh/
halfedge.rs

1//! Halfedge surface mesh: O(1) adjacency and in-place connectivity edits.
2//!
3//! # Why this exists
4//!
5//! [`EdgeAdjacency`](crate::EdgeAdjacency) answers "which triangles meet
6//! along this edge" over a [`TriMesh`] that stays the owner of the data, and
7//! every consumer that reads it writes a *new* mesh. Remeshing, hole filling,
8//! subdivision, geodesics and parameterisation instead rewrite connectivity
9//! in place, many times, and walk the one-ring of a vertex in its cyclic
10//! order. That needs a persistent structure whose links survive each edit:
11//! this is it.
12//!
13//! # Layout
14//!
15//! The layout follows CGAL's `Surface_mesh`: index-based arrays rather than
16//! pointers. Each edge `e` owns the two halfedges `2e` and `2e + 1`, so
17//! [`HalfedgeMesh::opposite`] is `h ^ 1` and stores nothing. Each halfedge
18//! stores its `next`, `prev`, `target` vertex and incident face; a vertex
19//! stores one outgoing halfedge and a face one halfedge of its loop. Every
20//! navigation query is one array read.
21//!
22//! A halfedge with no face is a *boundary* halfedge. Boundary halfedges are
23//! linked by `next`/`prev` into loops, one per hole, so a hole is walked the
24//! same way as a face. An isolated vertex has no halfedge.
25//!
26//! # Invariants
27//!
28//! [`HalfedgeMesh::validate`] checks every one of these; every constructor
29//! and edit keeps them.
30//!
31//! - `next` and `prev` are inverse permutations of the live halfedges, and
32//!   `face(next(h)) == face(h)`.
33//! - `source(h) == target(opposite(h))` and an edge never joins a vertex to
34//!   itself.
35//! - Every face loop has at least three halfedges and no repeated vertex.
36//! - No edge has a boundary on both sides.
37//! - The mesh is *simple*: two vertices are joined by at most one edge.
38//! - Every vertex is manifold: rotating around it reaches every outgoing
39//!   halfedge, at most one of them is a boundary halfedge, and when one is,
40//!   it is the vertex's stored halfedge. So [`HalfedgeMesh::is_boundary_vertex`]
41//!   is O(1) and a vertex circulation starts at the boundary.
42//!
43//! Non-manifold input is refused by name ([`HalfedgeBuildError`]) rather
44//! than represented: an edge shared by three faces, a vertex pinched between
45//! two fans, and two faces disagreeing on winding each have their own
46//! variant.
47//!
48//! # Identity
49//!
50//! A mesh built from a [`TriMesh`] or face list keeps the input's numbering:
51//! vertex `i` is position `i` and face `j` is input face `j`. Edits that
52//! remove elements mark them removed instead of renumbering, so ids held by
53//! a caller stay valid across edits; [`HalfedgeMesh::compact`] renumbers
54//! densely and returns the mapping.
55
56mod build;
57mod check;
58mod edit;
59
60use core::fmt;
61
62use axiolid_core::Point3;
63
64use crate::TriMesh;
65
66pub use build::HalfedgeBuildError;
67pub use check::HalfedgeInvariantError;
68pub use edit::HalfedgeEditError;
69
70/// Sentinel for "no element" in the link arrays.
71const NONE: u32 = u32::MAX;
72
73macro_rules! element_id {
74    ($(#[$meta:meta])* $name:ident) => {
75        $(#[$meta])*
76        #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
77        pub struct $name(u32);
78
79        impl $name {
80            /// Id with the given index.
81            #[must_use]
82            pub const fn new(index: u32) -> Self {
83                Self(index)
84            }
85
86            /// Index into the mesh's arrays.
87            #[must_use]
88            pub const fn index(self) -> usize {
89                self.0 as usize
90            }
91
92            /// Raw index.
93            #[must_use]
94            pub const fn get(self) -> u32 {
95                self.0
96            }
97        }
98
99        impl fmt::Display for $name {
100            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
101                write!(f, "{}", self.0)
102            }
103        }
104    };
105}
106
107element_id!(
108    /// A vertex of a [`HalfedgeMesh`].
109    VertexId
110);
111element_id!(
112    /// A directed halfedge of a [`HalfedgeMesh`].
113    HalfedgeId
114);
115element_id!(
116    /// An undirected edge of a [`HalfedgeMesh`]; it owns halfedges `2e` and `2e + 1`.
117    EdgeId
118);
119element_id!(
120    /// A face of a [`HalfedgeMesh`].
121    FaceId
122);
123
124/// One halfedge's links.
125#[derive(Debug, Clone, Copy, PartialEq, Eq)]
126struct Link {
127    next: u32,
128    prev: u32,
129    target: u32,
130    /// `NONE` for a boundary halfedge.
131    face: u32,
132}
133
134const DEAD_LINK: Link = Link {
135    next: NONE,
136    prev: NONE,
137    target: NONE,
138    face: NONE,
139};
140
141/// Halfedge (doubly-connected edge list) surface mesh of a 2-manifold, with
142/// or without boundary, with polygonal faces.
143///
144/// See the [module documentation](self) for the layout and the invariants.
145/// Navigation queries take ids of live elements; passing an id of a removed
146/// element returns stale data rather than panicking, and an id past the
147/// arrays panics like slice indexing. Edits check liveness and refuse by
148/// name.
149#[derive(Debug, Clone, PartialEq)]
150pub struct HalfedgeMesh {
151    positions: Vec<Point3>,
152    /// One outgoing halfedge per vertex; `NONE` for an isolated vertex.
153    vertex_out: Vec<u32>,
154    vertex_live: Vec<bool>,
155    links: Vec<Link>,
156    edge_live: Vec<bool>,
157    face_halfedge: Vec<u32>,
158    face_live: Vec<bool>,
159    live_vertices: usize,
160    live_edges: usize,
161    live_faces: usize,
162}
163
164/// Old-to-new id mapping returned by [`HalfedgeMesh::compact`].
165///
166/// `None` marks an element that was removed. Halfedges follow their edge:
167/// halfedge `2e + s` becomes `2 * edges[e] + s`.
168#[derive(Debug, Clone, Default, PartialEq, Eq)]
169pub struct HalfedgeRemap {
170    /// New id of each old vertex.
171    pub vertices: Vec<Option<VertexId>>,
172    /// New id of each old edge.
173    pub edges: Vec<Option<EdgeId>>,
174    /// New id of each old face.
175    pub faces: Vec<Option<FaceId>>,
176}
177
178impl HalfedgeMesh {
179    /// Build from an indexed triangle mesh.
180    ///
181    /// Vertex `i` is `mesh.positions[i]` and face `j` is triangle `j`.
182    /// Normals and attribute channels are not carried: the halfedge mesh is
183    /// connectivity plus positions.
184    ///
185    /// # Errors
186    ///
187    /// A ragged index buffer, an out-of-range or repeated corner, and every
188    /// non-manifold or inconsistently wound configuration, each by name.
189    pub fn from_tri_mesh(mesh: &TriMesh) -> Result<Self, HalfedgeBuildError> {
190        if !mesh.indices.len().is_multiple_of(3) {
191            return Err(HalfedgeBuildError::IncompleteTriangle {
192                index_count: mesh.indices.len(),
193            });
194        }
195        let triangles: Vec<[u32; 3]> = mesh.triangles().collect();
196        Self::from_faces(mesh.positions.clone(), &triangles)
197    }
198
199    /// Build from positions and polygonal faces, each a loop of position
200    /// indices wound consistently with its neighbours.
201    ///
202    /// # Errors
203    ///
204    /// As [`HalfedgeMesh::from_tri_mesh`], plus faces with fewer than three
205    /// corners.
206    pub fn from_faces<F: AsRef<[u32]>>(
207        positions: Vec<Point3>,
208        faces: &[F],
209    ) -> Result<Self, HalfedgeBuildError> {
210        build::build(positions, faces)
211    }
212
213    /// Indexed triangle mesh of the live elements.
214    ///
215    /// Live vertices keep their relative order, so a mesh that has had no
216    /// vertex removed keeps its numbering; each triangle starts at its face's
217    /// stored halfedge, so building from a [`TriMesh`] and converting back
218    /// reproduces its index buffer exactly.
219    ///
220    /// # Errors
221    ///
222    /// [`HalfedgeEditError::NotATriangle`] for the first face that is not a
223    /// triangle; split it with [`HalfedgeMesh::split_face`] or
224    /// [`HalfedgeMesh::split_face_diagonal`] first.
225    pub fn to_tri_mesh(&self) -> Result<TriMesh, HalfedgeEditError> {
226        let mut remap = vec![NONE; self.positions.len()];
227        let mut positions = Vec::with_capacity(self.live_vertices);
228        for v in self.vertices() {
229            remap[v.index()] = positions.len() as u32;
230            positions.push(self.positions[v.index()]);
231        }
232        let mut indices = Vec::with_capacity(self.live_faces * 3);
233        for f in self.faces() {
234            let degree = self.face_degree(f);
235            if degree != 3 {
236                return Err(HalfedgeEditError::NotATriangle { face: f, degree });
237            }
238            let h = self.face_halfedge(f);
239            for corner in [self.source(h), self.target(h), self.target(self.next(h))] {
240                indices.push(remap[corner.index()]);
241            }
242        }
243        Ok(TriMesh::new(positions, indices))
244    }
245
246    // ---- counts -----------------------------------------------------------
247
248    /// Live vertices, including isolated ones.
249    #[must_use]
250    pub fn vertex_count(&self) -> usize {
251        self.live_vertices
252    }
253
254    /// Live edges.
255    #[must_use]
256    pub fn edge_count(&self) -> usize {
257        self.live_edges
258    }
259
260    /// Live halfedges: twice the edge count.
261    #[must_use]
262    pub fn halfedge_count(&self) -> usize {
263        self.live_edges * 2
264    }
265
266    /// Live faces.
267    #[must_use]
268    pub fn face_count(&self) -> usize {
269        self.live_faces
270    }
271
272    /// `V - E + F` over the live elements (isolated vertices count).
273    #[must_use]
274    pub fn euler_characteristic(&self) -> i64 {
275        self.live_vertices as i64 - self.live_edges as i64 + self.live_faces as i64
276    }
277
278    /// Whether the vertex id names a live vertex.
279    #[must_use]
280    pub fn contains_vertex(&self, v: VertexId) -> bool {
281        self.vertex_live.get(v.index()).copied().unwrap_or(false)
282    }
283
284    /// Whether the edge id names a live edge.
285    #[must_use]
286    pub fn contains_edge(&self, e: EdgeId) -> bool {
287        self.edge_live.get(e.index()).copied().unwrap_or(false)
288    }
289
290    /// Whether the halfedge id names a halfedge of a live edge.
291    #[must_use]
292    pub fn contains_halfedge(&self, h: HalfedgeId) -> bool {
293        self.contains_edge(EdgeId(h.0 / 2))
294    }
295
296    /// Whether the face id names a live face.
297    #[must_use]
298    pub fn contains_face(&self, f: FaceId) -> bool {
299        self.face_live.get(f.index()).copied().unwrap_or(false)
300    }
301
302    // ---- element iteration ------------------------------------------------
303
304    /// Live vertices in id order.
305    pub fn vertices(&self) -> impl Iterator<Item = VertexId> + '_ {
306        live_ids(&self.vertex_live).map(VertexId)
307    }
308
309    /// Live edges in id order.
310    pub fn edges(&self) -> impl Iterator<Item = EdgeId> + '_ {
311        live_ids(&self.edge_live).map(EdgeId)
312    }
313
314    /// Live halfedges in id order.
315    pub fn halfedges(&self) -> impl Iterator<Item = HalfedgeId> + '_ {
316        self.edges()
317            .flat_map(|e| [HalfedgeId(e.0 * 2), HalfedgeId(e.0 * 2 + 1)])
318    }
319
320    /// Live faces in id order.
321    pub fn faces(&self) -> impl Iterator<Item = FaceId> + '_ {
322        live_ids(&self.face_live).map(FaceId)
323    }
324
325    /// Live boundary halfedges in id order.
326    pub fn boundary_halfedges(&self) -> impl Iterator<Item = HalfedgeId> + '_ {
327        self.halfedges().filter(|&h| self.is_boundary_halfedge(h))
328    }
329
330    /// One boundary halfedge per hole, each the lowest id on its loop.
331    #[must_use]
332    pub fn boundary_loops(&self) -> Vec<HalfedgeId> {
333        let mut seen = vec![false; self.links.len()];
334        let mut loops = Vec::new();
335        for h in self.boundary_halfedges() {
336            if seen[h.index()] {
337                continue;
338            }
339            loops.push(h);
340            for g in self.loop_halfedges(h) {
341                seen[g.index()] = true;
342            }
343        }
344        loops
345    }
346
347    // ---- O(1) navigation --------------------------------------------------
348
349    /// Position of a vertex.
350    #[must_use]
351    pub fn position(&self, v: VertexId) -> Point3 {
352        self.positions[v.index()]
353    }
354
355    /// Move a vertex. Connectivity is untouched.
356    pub fn set_position(&mut self, v: VertexId, position: Point3) {
357        self.positions[v.index()] = position;
358    }
359
360    /// The position array, indexed by vertex id (removed vertices included).
361    #[must_use]
362    pub fn positions(&self) -> &[Point3] {
363        &self.positions
364    }
365
366    /// Next halfedge around the same face or hole.
367    #[must_use]
368    pub fn next(&self, h: HalfedgeId) -> HalfedgeId {
369        HalfedgeId(self.links[h.index()].next)
370    }
371
372    /// Previous halfedge around the same face or hole.
373    #[must_use]
374    pub fn prev(&self, h: HalfedgeId) -> HalfedgeId {
375        HalfedgeId(self.links[h.index()].prev)
376    }
377
378    /// The other halfedge of the same edge.
379    #[must_use]
380    pub const fn opposite(&self, h: HalfedgeId) -> HalfedgeId {
381        HalfedgeId(h.0 ^ 1)
382    }
383
384    /// Vertex the halfedge points to.
385    #[must_use]
386    pub fn target(&self, h: HalfedgeId) -> VertexId {
387        VertexId(self.links[h.index()].target)
388    }
389
390    /// Vertex the halfedge leaves.
391    #[must_use]
392    pub fn source(&self, h: HalfedgeId) -> VertexId {
393        VertexId(self.links[(h.0 ^ 1) as usize].target)
394    }
395
396    /// Face to the left of the halfedge; `None` on a boundary halfedge.
397    #[must_use]
398    pub fn face(&self, h: HalfedgeId) -> Option<FaceId> {
399        let face = self.links[h.index()].face;
400        (face != NONE).then_some(FaceId(face))
401    }
402
403    /// Edge owning the halfedge.
404    #[must_use]
405    pub const fn edge(&self, h: HalfedgeId) -> EdgeId {
406        EdgeId(h.0 / 2)
407    }
408
409    /// One of the edge's two halfedges: `side` 0 or 1.
410    ///
411    /// # Panics
412    ///
413    /// When `side` is greater than 1.
414    #[must_use]
415    pub fn edge_halfedge(&self, e: EdgeId, side: u32) -> HalfedgeId {
416        assert!(side < 2, "an edge has two halfedges");
417        HalfedgeId(e.0 * 2 + side)
418    }
419
420    /// Both endpoints of an edge, in the direction of its halfedge `2e`.
421    #[must_use]
422    pub fn edge_vertices(&self, e: EdgeId) -> [VertexId; 2] {
423        let h = self.edge_halfedge(e, 0);
424        [self.source(h), self.target(h)]
425    }
426
427    /// Stored outgoing halfedge of a vertex; `None` when isolated.
428    ///
429    /// For a boundary vertex it is the boundary halfedge leaving it.
430    #[must_use]
431    pub fn vertex_halfedge(&self, v: VertexId) -> Option<HalfedgeId> {
432        let h = self.vertex_out[v.index()];
433        (h != NONE).then_some(HalfedgeId(h))
434    }
435
436    /// Stored halfedge of a face's loop.
437    #[must_use]
438    pub fn face_halfedge(&self, f: FaceId) -> HalfedgeId {
439        HalfedgeId(self.face_halfedge[f.index()])
440    }
441
442    /// Whether the halfedge has no face.
443    #[must_use]
444    pub fn is_boundary_halfedge(&self, h: HalfedgeId) -> bool {
445        self.links[h.index()].face == NONE
446    }
447
448    /// Whether either side of the edge is a boundary.
449    #[must_use]
450    pub fn is_boundary_edge(&self, e: EdgeId) -> bool {
451        self.is_boundary_halfedge(HalfedgeId(e.0 * 2))
452            || self.is_boundary_halfedge(HalfedgeId(e.0 * 2 + 1))
453    }
454
455    /// Whether the vertex lies on a hole. Isolated vertices are not boundary.
456    #[must_use]
457    pub fn is_boundary_vertex(&self, v: VertexId) -> bool {
458        self.vertex_halfedge(v)
459            .is_some_and(|h| self.is_boundary_halfedge(h))
460    }
461
462    /// Whether the vertex has no incident edge.
463    #[must_use]
464    pub fn is_isolated(&self, v: VertexId) -> bool {
465        self.vertex_out[v.index()] == NONE
466    }
467
468    /// Halfedge from `from` to `to`, if the edge exists. O(degree).
469    #[must_use]
470    pub fn find_halfedge(&self, from: VertexId, to: VertexId) -> Option<HalfedgeId> {
471        self.outgoing_halfedges(from)
472            .find(|&h| self.target(h) == to)
473    }
474
475    // ---- circulators ------------------------------------------------------
476
477    /// Outgoing halfedges of a vertex in counter-clockwise order (seen with
478    /// faces wound counter-clockwise), starting at the stored halfedge, so a
479    /// boundary vertex starts at its boundary halfedge.
480    pub fn outgoing_halfedges(&self, v: VertexId) -> impl Iterator<Item = HalfedgeId> + '_ {
481        let start = self.vertex_halfedge(v);
482        let mut current = start;
483        core::iter::from_fn(move || {
484            let h = current?;
485            let rotated = self.rotate(h);
486            current = (Some(rotated) != start).then_some(rotated);
487            Some(h)
488        })
489    }
490
491    /// Incoming halfedges of a vertex, the opposites of
492    /// [`HalfedgeMesh::outgoing_halfedges`] in the same order.
493    pub fn incoming_halfedges(&self, v: VertexId) -> impl Iterator<Item = HalfedgeId> + '_ {
494        self.outgoing_halfedges(v).map(|h| self.opposite(h))
495    }
496
497    /// Neighbouring vertices in counter-clockwise order.
498    pub fn vertex_vertices(&self, v: VertexId) -> impl Iterator<Item = VertexId> + '_ {
499        self.outgoing_halfedges(v).map(|h| self.target(h))
500    }
501
502    /// Faces around a vertex in counter-clockwise order, holes skipped.
503    pub fn vertex_faces(&self, v: VertexId) -> impl Iterator<Item = FaceId> + '_ {
504        self.outgoing_halfedges(v).filter_map(|h| self.face(h))
505    }
506
507    /// Number of edges at a vertex. O(degree).
508    #[must_use]
509    pub fn degree(&self, v: VertexId) -> usize {
510        self.outgoing_halfedges(v).count()
511    }
512
513    /// Halfedges of the loop containing `h` (a face or a hole), from `h`.
514    pub fn loop_halfedges(&self, h: HalfedgeId) -> impl Iterator<Item = HalfedgeId> + '_ {
515        let mut current = Some(h);
516        core::iter::from_fn(move || {
517            let g = current?;
518            let next = self.next(g);
519            current = (next != h).then_some(next);
520            Some(g)
521        })
522    }
523
524    /// Halfedges of a face's loop, from its stored halfedge.
525    pub fn face_halfedges(&self, f: FaceId) -> impl Iterator<Item = HalfedgeId> + '_ {
526        self.loop_halfedges(self.face_halfedge(f))
527    }
528
529    /// Corners of a face in winding order, starting at the source of its
530    /// stored halfedge.
531    pub fn face_vertices(&self, f: FaceId) -> impl Iterator<Item = VertexId> + '_ {
532        self.face_halfedges(f).map(|h| self.source(h))
533    }
534
535    /// Faces across each edge of a face, `None` across a boundary.
536    pub fn face_faces(&self, f: FaceId) -> impl Iterator<Item = Option<FaceId>> + '_ {
537        self.face_halfedges(f).map(|h| self.face(self.opposite(h)))
538    }
539
540    /// Number of sides of a face. O(degree).
541    #[must_use]
542    pub fn face_degree(&self, f: FaceId) -> usize {
543        self.face_halfedges(f).count()
544    }
545
546    // ---- maintenance ------------------------------------------------------
547
548    /// Renumber live elements densely in their current order and drop the
549    /// removed ones. Returns the old-to-new mapping so per-element data held
550    /// by a caller can follow.
551    pub fn compact(&mut self) -> HalfedgeRemap {
552        let vertices = dense_map(&self.vertex_live);
553        let edges = dense_map(&self.edge_live);
554        let faces = dense_map(&self.face_live);
555        let map_halfedge = |h: u32| -> u32 {
556            if h == NONE {
557                return NONE;
558            }
559            edges[(h / 2) as usize].map_or(NONE, |e| e * 2 + (h & 1))
560        };
561        let map = |table: &[Option<u32>], i: u32| -> u32 {
562            if i == NONE {
563                NONE
564            } else {
565                table[i as usize].unwrap_or(NONE)
566            }
567        };
568
569        let mut positions = Vec::with_capacity(self.live_vertices);
570        let mut vertex_out = Vec::with_capacity(self.live_vertices);
571        for (v, slot) in vertices.iter().enumerate() {
572            if slot.is_some() {
573                positions.push(self.positions[v]);
574                vertex_out.push(map_halfedge(self.vertex_out[v]));
575            }
576        }
577        let mut links = Vec::with_capacity(self.live_edges * 2);
578        for (h, link) in self.links.iter().enumerate() {
579            if edges[h / 2].is_some() {
580                links.push(Link {
581                    next: map_halfedge(link.next),
582                    prev: map_halfedge(link.prev),
583                    target: map(&vertices, link.target),
584                    face: map(&faces, link.face),
585                });
586            }
587        }
588        let mut face_halfedge = Vec::with_capacity(self.live_faces);
589        for (f, slot) in faces.iter().enumerate() {
590            if slot.is_some() {
591                face_halfedge.push(map_halfedge(self.face_halfedge[f]));
592            }
593        }
594
595        self.vertex_live = vec![true; positions.len()];
596        self.edge_live = vec![true; links.len() / 2];
597        self.face_live = vec![true; face_halfedge.len()];
598        self.positions = positions;
599        self.vertex_out = vertex_out;
600        self.links = links;
601        self.face_halfedge = face_halfedge;
602
603        HalfedgeRemap {
604            vertices: vertices.into_iter().map(|v| v.map(VertexId)).collect(),
605            edges: edges.into_iter().map(|e| e.map(EdgeId)).collect(),
606            faces: faces.into_iter().map(|f| f.map(FaceId)).collect(),
607        }
608    }
609
610    /// Check every structural invariant listed in the
611    /// [module documentation](self). O(halfedges).
612    ///
613    /// # Errors
614    ///
615    /// The first violated invariant found.
616    pub fn validate(&self) -> Result<(), HalfedgeInvariantError> {
617        check::validate(self)
618    }
619
620    // ---- internals --------------------------------------------------------
621
622    /// Next outgoing halfedge counter-clockwise around `source(h)`.
623    fn rotate(&self, h: HalfedgeId) -> HalfedgeId {
624        self.opposite(self.prev(h))
625    }
626
627    fn link(&mut self, from: u32, to: u32) {
628        self.links[from as usize].next = to;
629        self.links[to as usize].prev = from;
630    }
631}
632
633fn live_ids(live: &[bool]) -> impl Iterator<Item = u32> + '_ {
634    live.iter()
635        .enumerate()
636        .filter(|(_, &alive)| alive)
637        .map(|(i, _)| i as u32)
638}
639
640fn dense_map(live: &[bool]) -> Vec<Option<u32>> {
641    let mut next = 0u32;
642    live.iter()
643        .map(|&alive| {
644            alive.then(|| {
645                next += 1;
646                next - 1
647            })
648        })
649        .collect()
650}