Skip to main content

geop_core_topology/model/
iterate.rs

1use std::collections::HashSet;
2
3use crate::{
4    Body, CoedgeGeometry, CoedgeId, EdgeId, FaceId, Sense, SolidId, VertexId,
5    boundary::{BoundaryIndex, BoundaryType},
6};
7use geop_core_math::geop_error::{GeopError, GeopResult};
8
9use super::Model;
10
11/// The corners of one boundary of a face, each its vertex and where it is
12/// (see [`Model::face_corners`]).
13pub type Corners = Vec<(VertexId, [f64; 3])>;
14
15impl Model {
16    /// The (unordered) coedges referencing a given edge. An edge shared by a
17    /// single manifold face pair has exactly two.
18    pub fn coedges_of_edge(&self, edge: EdgeId) -> Vec<CoedgeId> {
19        self.coedges
20            .iter()
21            .filter(|(_, c)| c.geometry == CoedgeGeometry::Edge(edge))
22            .map(|(id, _)| *id)
23            .collect()
24    }
25
26    /// Every coedge of the loop anchored at `anchor`, in traversal order,
27    /// following `next` back around to `anchor`. Does not itself bound how
28    /// many coedges it yields: a corrupted `next` chain that never returns
29    /// to `anchor` makes this iterate forever, which callers checking that
30    /// invariant (see `validation::two_way_references`) detect with a
31    /// `.take(n)` of their own.
32    pub fn iterate_loop_coedges(&self, anchor: CoedgeId) -> impl Iterator<Item = CoedgeId> + '_ {
33        std::iter::successors(Some(anchor), move |c| {
34            let next = self.coedges[c].next;
35            (next != anchor).then_some(next)
36        })
37    }
38
39    /// The corners of `face_id`'s boundaries, outer loop first, each its
40    /// vertex and where it is — what an error shows of a face, to tell which
41    /// curves were imprinted into it, by vertices a named model can name
42    /// (`names_mentioned`).
43    pub fn face_corners(&self, face_id: FaceId) -> GeopResult<Vec<Corners>> {
44        let at = |v: VertexId| -> GeopResult<(VertexId, [f64; 3])> {
45            let p = self.get_vertex(v)?.point;
46            Ok((v, p.to_f64_array()))
47        };
48        self.get_face(face_id)?
49            .boundaries()
50            .map(|boundary| match boundary {
51                BoundaryType::Vertex(v) => Ok(vec![at(v)?]),
52                BoundaryType::Loop(anchor) => self
53                    .iterate_loop_coedges(anchor)
54                    .map(|c| at(self.coedge_start_vertex_id(c)?))
55                    .collect(),
56            })
57            .collect()
58    }
59
60    /// Every coedge of every boundary loop of `face_id` (its outer loop and
61    /// any holes) — a bare `Vertex` boundary contributes none.
62    pub fn iterate_face_coedges(&self, face_id: FaceId) -> impl Iterator<Item = CoedgeId> + '_ {
63        self.faces[&face_id]
64            .boundaries()
65            .filter_map(|boundary| match boundary {
66                BoundaryType::Loop(anchor) => Some(anchor),
67                BoundaryType::Vertex(_) => None,
68            })
69            .flat_map(|anchor| self.iterate_loop_coedges(anchor))
70    }
71
72    /// The edge of every edge-backed coedge of `face_id`'s boundaries, in
73    /// loop order, outer loop first: a seam the face runs along twice comes
74    /// twice.
75    pub fn face_edges(&self, face_id: FaceId) -> impl Iterator<Item = EdgeId> + '_ {
76        self.iterate_face_coedges(face_id)
77            .filter_map(|c| match self.coedges[&c].geometry {
78                CoedgeGeometry::Edge(edge) => Some(edge),
79                CoedgeGeometry::Vertex(_) => None,
80            })
81    }
82
83    /// The edges ending at `vertex`, sorted by id.
84    pub fn edges_at_vertex(&self, vertex: VertexId) -> Vec<EdgeId> {
85        self.edges
86            .iter()
87            .filter(|(_, e)| e.start_vertex == vertex || e.end_vertex == vertex)
88            .map(|(&id, _)| id)
89            .collect()
90    }
91
92    /// The faces whose boundaries run through `vertex` (along an edge
93    /// starting there), sorted by id.
94    pub fn faces_at_vertex(&self, vertex: VertexId) -> GeopResult<Vec<FaceId>> {
95        let mut faces = Vec::new();
96        for (&id, coedge) in &self.coedges {
97            if coedge.edge().is_ok() && self.coedge_start_vertex_id(id)? == vertex {
98                faces.push(coedge.face);
99            }
100        }
101        faces.sort();
102        faces.dedup();
103        Ok(faces)
104    }
105
106    /// The two faces of `edge`: the one its forward coedge bounds (on the
107    /// edge's left) first, the one running against it second. `None`
108    /// unless the edge joins two faces.
109    pub fn faces_of_edge(&self, edge: EdgeId) -> Option<[FaceId; 2]> {
110        let mut forward = None;
111        let mut reversed = None;
112        for c in self.coedges_of_edge(edge) {
113            let coedge = &self.coedges[&c];
114            match coedge.sense {
115                Sense::Forward => forward = Some(coedge.face),
116                Sense::Reversed => reversed = Some(coedge.face),
117            }
118        }
119        Some([forward?, reversed?])
120    }
121
122    /// The geometry of every coedge of `body`'s faces, in order.
123    fn body_coedge_geometry(
124        &self,
125        body: impl Into<Body>,
126    ) -> GeopResult<impl Iterator<Item = CoedgeGeometry> + '_> {
127        Ok(self
128            .body_faces(body)?
129            .into_iter()
130            .flat_map(|face| self.iterate_face_coedges(face))
131            .map(|c| self.coedges[&c].geometry))
132    }
133
134    /// The distinct vertices referenced by `body`'s faces. Borrows `self`
135    /// for as long as it lives, so a caller holding one cannot also call a
136    /// `&mut self` method like `merge_vertex` on the same model: the borrow
137    /// checker, not the caller, keeps it from going stale.
138    pub fn iter_body_vertices(
139        &self,
140        body: impl Into<Body>,
141    ) -> GeopResult<impl Iterator<Item = VertexId> + '_> {
142        let mut seen = HashSet::new();
143        Ok(self
144            .body_coedge_geometry(body)?
145            .flat_map(|geometry| match geometry {
146                CoedgeGeometry::Edge(edge_id) => {
147                    let edge = &self.edges[&edge_id];
148                    vec![edge.start_vertex, edge.end_vertex]
149                }
150                CoedgeGeometry::Vertex(vertex_id) => vec![vertex_id],
151            })
152            .filter(move |&v| seen.insert(v)))
153    }
154
155    /// The distinct edges referenced by `body`'s faces, borrowing `self`
156    /// like [`Self::iter_body_vertices`].
157    pub fn iter_body_edges(
158        &self,
159        body: impl Into<Body>,
160    ) -> GeopResult<impl Iterator<Item = EdgeId> + '_> {
161        let mut seen = HashSet::new();
162        Ok(self
163            .body_coedge_geometry(body)?
164            .filter_map(|geometry| match geometry {
165                CoedgeGeometry::Edge(edge_id) => Some(edge_id),
166                CoedgeGeometry::Vertex(_) => None,
167            })
168            .filter(move |&e| seen.insert(e)))
169    }
170
171    /// Which of `face_id`'s boundaries contains `coedge` — its outer loop or
172    /// one of its holes — found by traversing each `Loop` boundary's `.next`
173    /// chain. A face's boundaries are disjoint loops, so at most one can
174    /// contain any given coedge.
175    ///
176    /// Returns [`BoundaryIndex`] rather than a bare position, because callers
177    /// invariably need to know whether they found the outer loop: joining two
178    /// holes, joining a hole to the outer loop, and joining two points of the
179    /// outer loop are three different restructurings (see
180    /// `Model::splice_edge_into_face`).
181    pub fn find_boundary_containing(
182        &self,
183        face_id: FaceId,
184        coedge: CoedgeId,
185    ) -> GeopResult<BoundaryIndex> {
186        let face = &self.faces[&face_id];
187        let contains = |boundary: BoundaryType| match boundary {
188            BoundaryType::Loop(anchor) => self.iterate_loop_coedges(anchor).any(|c| c == coedge),
189            BoundaryType::Vertex(_) => false,
190        };
191        if contains(face.outer) {
192            return Ok(BoundaryIndex::Outer);
193        }
194        face.holes
195            .iter()
196            .position(|&h| contains(h))
197            .map(BoundaryIndex::Hole)
198            .ok_or_else(|| {
199                GeopError::new(format!(
200                    "Model::find_boundary_containing: could not find a boundary containing coedge={coedge} on face={face_id}"
201                ))
202            })
203    }
204
205    /// Drop the hole `index` names from `face_id`.
206    ///
207    /// Errors on [`BoundaryIndex::Outer`]: a face without an outer boundary
208    /// is not a face. An operation that genuinely consumes a face's outer
209    /// loop is deleting the face, and must say so rather than leaving one
210    /// behind with nothing bounding it.
211    pub fn remove_boundary(&mut self, face_id: FaceId, index: BoundaryIndex) -> GeopResult<()> {
212        let BoundaryIndex::Hole(i) = index else {
213            return Err(GeopError::new(format!(
214                "Model::remove_boundary: refusing to remove face={face_id}'s outer boundary — a face must always have exactly one"
215            )));
216        };
217        let face = self.get_face_mut(face_id)?;
218        if i >= face.holes.len() {
219            return Err(GeopError::new(format!(
220                "Model::remove_boundary: face={face_id} has no hole at index {i}"
221            )));
222        }
223        face.holes.remove(i);
224        Ok(())
225    }
226
227    /// Every `FaceId` across every shell of `solid_id`.
228    pub fn solid_faces(&self, solid_id: SolidId) -> GeopResult<Vec<FaceId>> {
229        self.body_faces(solid_id)
230    }
231
232    /// Every `FaceId` of `body`: across every shell of a solid, or of the
233    /// one shell a sheet is.
234    pub fn body_faces(&self, body: impl Into<Body>) -> GeopResult<Vec<FaceId>> {
235        let shells = match body.into() {
236            Body::Solid(solid) => self.get_solid(solid)?.shells.clone(),
237            Body::Sheet(shell) => vec![shell],
238        };
239        let mut faces = Vec::new();
240        for shell_id in shells {
241            faces.extend(self.get_shell(shell_id)?.faces.iter().copied());
242        }
243        Ok(faces)
244    }
245
246    /// The solid `face_id` is part of; `None` for a face of a sheet.
247    pub fn solid_of_face(&self, face_id: FaceId) -> GeopResult<Option<SolidId>> {
248        Ok(match self.body_of_face(face_id)? {
249            Body::Solid(solid) => Some(solid),
250            Body::Sheet(_) => None,
251        })
252    }
253
254    /// The body `face_id` is part of: its shell's solid, or its shell, if
255    /// that is a sheet.
256    pub fn body_of_face(&self, face_id: FaceId) -> GeopResult<Body> {
257        let shell_id = self.get_face(face_id)?.shell;
258        Ok(match self.get_shell(shell_id)?.solid {
259            Some(solid) => Body::Solid(solid),
260            None => Body::Sheet(shell_id),
261        })
262    }
263}