Skip to main content

mesh_graph/
selection.rs

1use hashbrown::HashSet;
2use tracing::{error, instrument};
3
4use super::{FaceId, HalfedgeId, MeshGraph, VertexId};
5
6#[derive(Debug, Clone, Default)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct Selection {
9    pub vertices: HashSet<VertexId>,
10    pub halfedges: HashSet<HalfedgeId>,
11    pub faces: HashSet<FaceId>,
12}
13
14impl Selection {
15    pub fn select_all(mesh_graph: &MeshGraph) -> Self {
16        Self {
17            faces: mesh_graph.faces.keys().collect(),
18            ..Default::default()
19        }
20    }
21
22    /// Resolves the selection to the halfedges it covers: the explicitly selected
23    /// ones, every halfedge of a selected face, and every outgoing halfedge of a
24    /// selected vertex.
25    ///
26    /// Ids that are no longer live are skipped rather than panicking — a `Selection`
27    /// holds bare keys and any topology change can invalidate them.
28    pub fn resolve_to_halfedges(&self, mesh_graph: &MeshGraph) -> HashSet<HalfedgeId> {
29        let mut halfedges = self.halfedges.clone();
30
31        for face in &self.faces {
32            let Some(face) = mesh_graph.faces.get(*face) else {
33                error!("Face not found");
34                continue;
35            };
36            halfedges.extend(face.halfedges(mesh_graph));
37        }
38
39        for vertex in &self.vertices {
40            let Some(vertex) = mesh_graph.vertices.get(*vertex) else {
41                error!("Vertex not found");
42                continue;
43            };
44            halfedges.extend(vertex.outgoing_halfedges(mesh_graph));
45        }
46
47        halfedges
48    }
49
50    pub fn resolve_to_vertices(&self, mesh_graph: &MeshGraph) -> HashSet<VertexId> {
51        let mut vertices = self.vertices.clone();
52
53        for halfedge in &self.halfedges {
54            let Some(he) = mesh_graph.halfedges.get(*halfedge) else {
55                error!("Halfedge not found");
56                continue;
57            };
58            if let Some(start_vertex) = he.start_vertex(mesh_graph) {
59                vertices.insert(start_vertex);
60            } else {
61                error!("Start vertex not found");
62            }
63            vertices.insert(he.end_vertex);
64        }
65
66        for face in &self.faces {
67            let Some(face) = mesh_graph.faces.get(*face) else {
68                error!("Face not found");
69                continue;
70            };
71            vertices.extend(face.vertices(mesh_graph));
72        }
73
74        vertices
75    }
76
77    // TODO : also resolve to faces
78
79    #[instrument(skip(mesh_graph))]
80    /// Grows the selection by neighboring vertices. It returns the new vertices.
81    pub fn grow(&mut self, mesh_graph: &MeshGraph) -> HashSet<VertexId> {
82        let existing_verts = self.resolve_to_vertices(mesh_graph);
83
84        let mut new_verts = HashSet::new();
85
86        for vert_id in &existing_verts {
87            if let Some(vert) = mesh_graph.vertices.get(*vert_id) {
88                for neighbor in vert.neighbours(mesh_graph) {
89                    if !existing_verts.contains(&neighbor) {
90                        new_verts.insert(neighbor);
91                        self.insert(neighbor);
92                    }
93                }
94            } else {
95                error!("Vertex not found");
96            }
97        }
98
99        new_verts
100    }
101}
102
103pub trait SelectionOps<T> {
104    fn insert(&mut self, item: T);
105    fn remove(&mut self, item: T);
106}
107
108impl SelectionOps<VertexId> for Selection {
109    fn insert(&mut self, item: VertexId) {
110        self.vertices.insert(item);
111    }
112
113    fn remove(&mut self, item: VertexId) {
114        self.vertices.remove(&item);
115    }
116}
117
118impl SelectionOps<HalfedgeId> for Selection {
119    fn insert(&mut self, item: HalfedgeId) {
120        self.halfedges.insert(item);
121    }
122
123    fn remove(&mut self, item: HalfedgeId) {
124        self.halfedges.remove(&item);
125    }
126}
127
128impl SelectionOps<FaceId> for Selection {
129    fn insert(&mut self, item: FaceId) {
130        self.faces.insert(item);
131    }
132
133    fn remove(&mut self, item: FaceId) {
134        self.faces.remove(&item);
135    }
136}
137
138impl From<VertexId> for Selection {
139    fn from(value: VertexId) -> Self {
140        Self::from_iter(vec![value])
141    }
142}
143
144impl From<HalfedgeId> for Selection {
145    fn from(value: HalfedgeId) -> Self {
146        Self::from_iter(vec![value])
147    }
148}
149
150impl From<FaceId> for Selection {
151    fn from(value: FaceId) -> Self {
152        Self::from_iter(vec![value])
153    }
154}
155
156impl FromIterator<VertexId> for Selection {
157    fn from_iter<T: IntoIterator<Item = VertexId>>(iter: T) -> Self {
158        Selection {
159            vertices: HashSet::from_iter(iter),
160            ..Default::default()
161        }
162    }
163}
164
165impl FromIterator<HalfedgeId> for Selection {
166    fn from_iter<T: IntoIterator<Item = HalfedgeId>>(iter: T) -> Self {
167        Selection {
168            halfedges: HashSet::from_iter(iter),
169            ..Default::default()
170        }
171    }
172}
173
174impl FromIterator<FaceId> for Selection {
175    fn from_iter<T: IntoIterator<Item = FaceId>>(iter: T) -> Self {
176        Selection {
177            faces: HashSet::from_iter(iter),
178            ..Default::default()
179        }
180    }
181}
182
183macro_rules! impl_from_for_selection {
184    ($type:ident) => {
185        impl From<$type<VertexId>> for Selection {
186            fn from(value: $type<VertexId>) -> Self {
187                Self::from_iter(value)
188            }
189        }
190        impl From<$type<HalfedgeId>> for Selection {
191            fn from(value: $type<HalfedgeId>) -> Self {
192                Self::from_iter(value)
193            }
194        }
195        impl From<$type<FaceId>> for Selection {
196            fn from(value: $type<FaceId>) -> Self {
197                Self::from_iter(value)
198            }
199        }
200    };
201}
202
203impl_from_for_selection!(Vec);
204impl_from_for_selection!(HashSet);
205
206#[cfg(test)]
207mod tests {
208    use super::*;
209    use crate::utils::build_grid;
210
211    /// `select_all` seeds *faces only*, so every id `resolve_to_halfedges` and
212    /// `resolve_to_vertices` hand back here is derived (a selected face's own
213    /// halfedges/vertices), never one of the selection's unfiltered explicit ids.
214    /// That is what makes the strict "every resolved id is live" assertion
215    /// legitimate for this fixture. Panics on unfixed code, at the first stale face.
216    #[test]
217    fn test_resolve_tolerates_dead_faces_in_the_selection() {
218        let mut mg = build_grid(4);
219        let selection = Selection::select_all(&mg);
220
221        mg.collapse_until_edges_above_min_length(1.5, &mut HashSet::new());
222
223        assert!(
224            selection
225                .faces
226                .iter()
227                .any(|f_id| !mg.faces.contains_key(*f_id)),
228            "fixture failed to invalidate anything - test is vacuous"
229        );
230
231        for he_id in selection.resolve_to_halfedges(&mg) {
232            assert!(
233                mg.halfedges.contains_key(he_id),
234                "resolved halfedge {he_id:?} is dead"
235            );
236        }
237        for v_id in selection.resolve_to_vertices(&mg) {
238            assert!(
239                mg.vertices.contains_key(v_id),
240                "resolved vertex {v_id:?} is dead"
241            );
242        }
243    }
244
245    /// A selection naming every vertex, halfedge and face reaches the stale-halfedge
246    /// and stale-vertex lookup sites the faces-only fixture above cannot. Both
247    /// resolves must complete without panicking, and every id that was still live in
248    /// the selection must still appear in the corresponding result - the unfiltered
249    /// seed (`self.vertices.clone()` / `self.halfedges.clone()`) passes live explicit
250    /// ids straight through by design, and this pins that behaviour rather than
251    /// asserting every resolved id is live (which is false here on purpose).
252    #[test]
253    fn test_resolve_keeps_live_ids_when_the_selection_is_stale() {
254        let mut mg = build_grid(4);
255        let selection = Selection {
256            vertices: mg.vertices.keys().collect(),
257            halfedges: mg.halfedges.keys().collect(),
258            faces: mg.faces.keys().collect(),
259        };
260
261        mg.collapse_until_edges_above_min_length(1.5, &mut HashSet::new());
262
263        let live_vertices_before: HashSet<VertexId> = selection
264            .vertices
265            .iter()
266            .copied()
267            .filter(|v_id| mg.vertices.contains_key(*v_id))
268            .collect();
269        let live_halfedges_before: HashSet<HalfedgeId> = selection
270            .halfedges
271            .iter()
272            .copied()
273            .filter(|he_id| mg.halfedges.contains_key(*he_id))
274            .collect();
275
276        assert!(
277            live_vertices_before.len() < selection.vertices.len(),
278            "fixture failed to invalidate anything - test is vacuous"
279        );
280
281        let resolved_halfedges = selection.resolve_to_halfedges(&mg);
282        let resolved_vertices = selection.resolve_to_vertices(&mg);
283
284        for v_id in &live_vertices_before {
285            assert!(
286                resolved_vertices.contains(v_id),
287                "live vertex {v_id:?} missing from resolve_to_vertices"
288            );
289        }
290        for he_id in &live_halfedges_before {
291            assert!(
292                resolved_halfedges.contains(he_id),
293                "live halfedge {he_id:?} missing from resolve_to_halfedges"
294            );
295        }
296    }
297}