Skip to main content

mesh_graph/ops/
remove.rs

1use hashbrown::HashSet;
2use itertools::Itertools;
3use tracing::{error, instrument};
4
5use crate::{FaceId, HalfedgeId, MeshGraph, VertexId};
6
7impl MeshGraph {
8    /// Deletes a face from the mesh graph.
9    ///
10    /// It also deletes the vertices and halfedges that are no
11    /// longer connected to any other faces.
12    ///
13    /// Returns the ids of the removed vertices and halfedges.
14    #[instrument(skip(self))]
15    pub fn remove_face(&mut self, face_id: FaceId) -> (Vec<VertexId>, Vec<HalfedgeId>) {
16        if !self.faces.contains_key(face_id) {
17            return (vec![], vec![]);
18        }
19
20        let halfedges = self
21            .halfedges
22            .iter()
23            .filter_map(|(he_id, he)| {
24                if he.face == Some(face_id) {
25                    Some((he_id, *he))
26                } else {
27                    None
28                }
29            })
30            .collect_vec();
31
32        #[cfg(feature = "instrumentation")]
33        crate::record_op_trace!(
34            "remove_face({face_id:?}) members={:?}",
35            halfedges.iter().map(|(id, _)| id).collect_vec()
36        );
37
38        let mut removed_halfedges = HashSet::with_capacity(4);
39
40        for (he_id, he) in halfedges {
41            // A twinless member (left over by a re-pair sever or a dangling-twin clear)
42            // must not abort the face removal: remove the member itself. Same for a
43            // member whose twin reference is dead.
44            let Some(twin_id) = he.twin else {
45                removed_halfedges.insert(he_id);
46                continue;
47            };
48            let Some(twin) = self.halfedges.get(twin_id) else {
49                removed_halfedges.insert(he_id);
50                continue;
51            };
52            if twin.is_boundary() {
53                removed_halfedges.insert(he_id);
54                removed_halfedges.insert(twin_id);
55            } else if twin.twin != Some(he_id) {
56                removed_halfedges.insert(he_id);
57            } else {
58                // already checked above
59                self.halfedges[he_id].face = None;
60                self.halfedges[he_id].next = None;
61            }
62        }
63
64        #[cfg(feature = "rerun")]
65        self.log_hes_rerun(
66            "deleted_by_face_deletion",
67            &removed_halfedges.iter().copied().collect::<Vec<_>>(),
68        );
69
70        let mut removed_vertices = Vec::with_capacity(3);
71        let mut touched_start_vertices = HashSet::with_capacity(3);
72
73        // Update connections from vertices to deleted halfedges
74        for &he_id in &removed_halfedges {
75            // The start of a removed member is derived from its twin; for a member
76            // whose twin is missing/dead/twinless it cannot be resolved. Skipping the
77            // cleanup leaves a stale entry in `outgoing_halfedges` that the periodic
78            // `contains_key` retain passes purge; aborting the removal instead would
79            // leave the face half-detached (some members already nulled) and break
80            // its chain, which is the layer-4 corruption.
81            let Some(start_v_id) = self.halfedges[he_id].start_vertex(self) else {
82                continue;
83            };
84            touched_start_vertices.insert(start_v_id);
85            if let Some(out_he_ids) = self.outgoing_halfedges.get_mut(start_v_id) {
86                out_he_ids.retain(|&id| id != he_id);
87            } else {
88                error!("No outgoing halfedges found for vertex {start_v_id:?}");
89            }
90        }
91
92        for start_v_id in touched_start_vertices {
93            // A vertex survives if it still has a live outgoing halfedge; in that
94            // case refresh its seed pointer. Otherwise it is now isolated.
95            let surviving_out_he = self
96                .outgoing_halfedges
97                .get(start_v_id)
98                .and_then(|out_he_ids| out_he_ids.first().copied());
99
100            if let Some(out_he_id) = surviving_out_he {
101                if let Some(v) = self.vertices.get_mut(start_v_id) {
102                    v.outgoing_halfedge = Some(out_he_id);
103                } else {
104                    error!("Vertex {start_v_id:?} not found");
105                }
106            } else {
107                self.remove_only_vertex(start_v_id);
108                removed_vertices.push(start_v_id);
109            }
110        }
111
112        #[cfg(feature = "instrumentation")]
113        self.probe_live_face_removal(
114            &removed_halfedges.iter().copied().collect::<Vec<_>>(),
115            "remove_face_tail",
116        );
117
118        for he_id in &removed_halfedges {
119            self.halfedges.remove(*he_id);
120        }
121
122        // already checked at the start of the function
123        self.bvh.remove(self.faces[face_id].index);
124        self.faces.remove(face_id);
125        #[cfg(feature = "instrumentation")]
126        crate::record_face_death(face_id);
127
128        (removed_vertices, Vec::from_iter(removed_halfedges))
129    }
130
131    /// Deletes only a vertex, without deleting any faces or halfedges connected to it.
132    pub fn remove_only_vertex(&mut self, vertex_id: VertexId) {
133        self.outgoing_halfedges.remove(vertex_id);
134        self.positions.remove(vertex_id);
135        if let Some(normals) = &mut self.vertex_normals {
136            normals.remove(vertex_id);
137        }
138        self.vertices.remove(vertex_id);
139    }
140
141    /// Deletes only a halfedge, without deleting any faces or vertices connected to it.
142    /// No other halfedge is modified: the twin (if any) keeps its `twin` pointer,
143    /// which now refers to a removed halfedge. The caller must re-pair the twin or
144    /// remove it before its operation terminates (flap cleanup does both below).
145    pub fn remove_only_halfedge(&mut self, he_id: HalfedgeId) {
146        if let Some(he) = self.halfedges.get(he_id) {
147            if let Some(start_v_id) = he.start_vertex(self)
148                && let Some(hes) = self.outgoing_halfedges.get_mut(start_v_id)
149            {
150                hes.retain(|out_he_id| *out_he_id != he_id);
151            }
152
153            #[cfg(feature = "instrumentation")]
154            self.probe_live_face_removal(&[he_id], "remove_only_halfedge");
155            self.halfedges.remove(he_id);
156        }
157    }
158
159    pub fn remove_only_halfedge_and_twin(&mut self, he_id: HalfedgeId) {
160        if let Some(he) = self.halfedges.get(he_id) {
161            if let Some(start_v_id) = he.start_vertex(self)
162                && let Some(hes) = self.outgoing_halfedges.get_mut(start_v_id)
163            {
164                hes.retain(|out_he_id| *out_he_id != he_id);
165            }
166
167            if let Some(twin_he_id) = he.twin {
168                self.remove_only_halfedge(twin_he_id);
169            }
170
171            // The twin was just removed. `he_id`'s own twin pointer is left as-is
172            // (dead reference inside this call); nothing else survives to re-pair.
173            #[cfg(feature = "instrumentation")]
174            self.probe_live_face_removal(&[he_id], "remove_only_halfedge_and_twin");
175            self.halfedges.remove(he_id);
176        }
177    }
178}
179
180#[cfg(test)]
181mod tests {
182    use glam::{Mat4, vec3};
183
184    use crate::{
185        utils::{extend_with, get_tracing_subscriber},
186        *,
187    };
188
189    #[test]
190    fn test_remove_face() {
191        get_tracing_subscriber();
192        let mut meshgraph = MeshGraph::new();
193        let p_c = vec3(0.0, 0.0, 0.0);
194        let p_1 = vec3(0.0, 1.0, 0.0);
195        let p_2 = vec3(-1.0, 0.5, 0.0);
196        let p_3 = vec3(-1.0, -0.5, 0.0);
197        let p_4 = vec3(0.0, -1.0, 0.0);
198        let p_5 = vec3(1.0, -0.5, 0.0);
199        let p_6 = vec3(1.0, 0.5, 0.0);
200
201        let points = vec![p_c, p_1, p_2, p_3, p_4, p_5, p_6];
202        let vertex_ids = extend_with(&mut meshgraph, &points.clone(), Mat4::default(), 5.0, 0);
203
204        #[cfg(feature = "rerun")]
205        {
206            meshgraph.log_rerun();
207            RR.flush_blocking().unwrap();
208        }
209
210        let face_count = meshgraph.faces.len();
211        let face_id = meshgraph.vertices[vertex_ids[6]]
212            .faces(&meshgraph)
213            .next()
214            .unwrap();
215        meshgraph.remove_face(face_id);
216        #[cfg(feature = "rerun")]
217        {
218            meshgraph.log_rerun();
219            RR.flush_blocking().unwrap();
220        }
221
222        assert_eq!(meshgraph.faces.len(), face_count - 1);
223    }
224}