geop_core_topology/face.rs
1use super::ids::ShellId;
2use crate::boundary::{BoundaryIndex, BoundaryType};
3use geop_core_geometry::nurb_surface::NurbSurface3D;
4use geop_core_math::scalars::Scalar;
5
6/// A trimmed patch of `surface`: everything inside `outer` and outside every
7/// loop in `holes`.
8///
9/// The two are deliberately separate fields rather than one list with the
10/// outer loop by convention at index 0. A face always has exactly one outer
11/// boundary — that is what makes it a face — while holes are a genuinely
12/// variable collection, and encoding that in the type means no code has to
13/// re-establish it. It also removes a whole class of bug where a hole is
14/// accidentally treated as the outer loop (or vice versa) after a
15/// split/merge reorders the list.
16#[derive(Clone, Debug)]
17pub struct Face<S: Scalar> {
18 pub surface: NurbSurface3D<S>,
19 pub outer: BoundaryType,
20 pub holes: Vec<BoundaryType>,
21 pub shell: ShellId,
22}
23
24impl<S: Scalar> Face<S> {
25 /// Every boundary, outer loop first.
26 pub fn boundaries(&self) -> impl Iterator<Item = BoundaryType> + '_ {
27 std::iter::once(self.outer).chain(self.holes.iter().copied())
28 }
29
30 /// Every boundary, outer loop first, for in-place rewriting — used by
31 /// the operations that re-anchor or rename boundaries wholesale (a
32 /// vertex being merged away, a loop's anchor coedge being deleted)
33 /// without caring which kind each one is.
34 pub fn boundaries_mut(&mut self) -> impl Iterator<Item = &mut BoundaryType> + '_ {
35 std::iter::once(&mut self.outer).chain(self.holes.iter_mut())
36 }
37
38 /// The boundary `index` names, or `None` if it names a hole this face
39 /// does not have.
40 pub fn boundary(&self, index: BoundaryIndex) -> Option<BoundaryType> {
41 match index {
42 BoundaryIndex::Outer => Some(self.outer),
43 BoundaryIndex::Hole(i) => self.holes.get(i).copied(),
44 }
45 }
46
47 /// Re-anchor the boundary `index` names on a different loop. Needed after
48 /// any restructuring that rewires `next`/`prev`, since the previous
49 /// anchor may no longer sit on the ring the boundary now describes.
50 pub fn set_boundary(&mut self, index: BoundaryIndex, boundary: BoundaryType) -> bool {
51 match index {
52 BoundaryIndex::Outer => {
53 self.outer = boundary;
54 true
55 }
56 BoundaryIndex::Hole(i) => match self.holes.get_mut(i) {
57 Some(slot) => {
58 *slot = boundary;
59 true
60 }
61 None => false,
62 },
63 }
64 }
65}