Skip to main content

geop_ops_subd/
select.rs

1//! Walks over a cage's edges, for selecting and for the edits that follow
2//! them: the *ring* across the quads either side of an edge, the *loop*
3//! straight on through its vertices, and the boundary run of an open cage.
4
5use std::collections::{BTreeSet, HashMap};
6
7use geop_core_math::{
8    geop_error::{GeopError, GeopResult},
9    solvers::walk::{Walked, walk_by},
10};
11
12use crate::cage::{Cage, edge_key, undirected};
13
14/// The edge ring of an edge: the edges reached from it by crossing quads
15/// to their opposite side, until the ring closes, or meets the cage's
16/// boundary or a face that is not a quad on both ends.
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Ring {
19    /// Its edges in order, each directed so that the same side of the
20    /// ring comes first: splitting every edge at a fraction `t` from its
21    /// first vertex puts the new loop at `t` of the way across the ring.
22    pub edges: Vec<(u32, u32)>,
23    /// The quads crossed, by index, in the same order: the one between
24    /// `edges[i]` and `edges[i + 1]` — and, for a closed ring, between the
25    /// last edge and the first.
26    pub quads: Vec<usize>,
27    pub closed: bool,
28}
29
30impl Cage {
31    /// The edge ring through the edge between `a` and `b` (see [`Ring`]).
32    pub fn edge_ring(&self, a: u32, b: u32) -> GeopResult<Ring> {
33        let directed = self.directed_edges();
34        if !directed.contains_key(&(a, b)) && !directed.contains_key(&(b, a)) {
35            return Err(GeopError::new(format!(
36                "the cage has no edge {}",
37                edge_key(a, b)
38            )));
39        }
40        // Across each quad to the opposite edge, directed so that the side
41        // of the entering edge's first vertex comes first again.
42        let across = |(x, y): (u32, u32), taken: &[usize]| -> Option<((u32, u32), usize)> {
43            let &(f, k) = directed.get(&(x, y))?;
44            let face = &self.faces[f].vertices;
45            if face.len() != 4 || taken.contains(&f) {
46                return None;
47            }
48            Some(((face[(k + 3) % 4], face[(k + 2) % 4]), f))
49        };
50        // Forward from `(a, b)`.
51        let mut quads = Vec::new();
52        let forward = walk_by(
53            (a, b),
54            |&(x, y)| undirected(x, y),
55            |&edge| {
56                Ok::<_, GeopError>(across(edge, &quads).map(|(next, f)| {
57                    quads.push(f);
58                    next
59                }))
60            },
61        )?;
62        if forward.end == Walked::Closed {
63            return Ok(Ring {
64                edges: forward.nodes,
65                quads,
66                closed: true,
67            });
68        }
69        // Then the other way from `(b, a)`, its edges turned round to keep
70        // `a`'s side first, and put before the forward ones.
71        let mut back_quads = Vec::new();
72        let back = walk_by(
73            (b, a),
74            |&(x, y)| undirected(x, y),
75            |&edge| {
76                Ok::<_, GeopError>(across(edge, &quads).and_then(|(next, f)| {
77                    if back_quads.contains(&f) {
78                        return None;
79                    }
80                    back_quads.push(f);
81                    Some(next)
82                }))
83            },
84        )?;
85        let mut edges: Vec<(u32, u32)> =
86            back.nodes[1..].iter().rev().map(|&(x, y)| (y, x)).collect();
87        edges.extend(forward.nodes);
88        back_quads.reverse();
89        back_quads.extend(quads);
90        Ok(Ring {
91            edges,
92            quads: back_quads,
93            closed: false,
94        })
95    }
96
97    /// The edge loop through the edge between `a` and `b`: on through
98    /// each vertex of four edges by the edge opposite — the one sharing no
99    /// face with the edge arrived by — until the loop closes or reaches a
100    /// vertex of any other number of edges, or the boundary. A boundary
101    /// edge's loop is the boundary it is on (see [`Cage::boundary_run`]).
102    pub fn edge_loop(&self, a: u32, b: u32) -> GeopResult<Vec<[u32; 2]>> {
103        let directed = self.directed_edges();
104        if !directed.contains_key(&(a, b)) && !directed.contains_key(&(b, a)) {
105            return Err(GeopError::new(format!(
106                "the cage has no edge {}",
107                edge_key(a, b)
108            )));
109        }
110        if self.is_boundary(a, b) {
111            let run = self.boundary_run(a, b)?;
112            let n = run.len();
113            return Ok((0..n)
114                .map(|i| undirected(run[i], run[(i + 1) % n]))
115                .collect());
116        }
117        let mut neighbours: HashMap<u32, BTreeSet<u32>> = HashMap::new();
118        for &(x, y) in directed.keys() {
119            neighbours.entry(x).or_default().insert(y);
120            neighbours.entry(y).or_default().insert(x);
121        }
122        let faces_of = |x: u32, y: u32| -> BTreeSet<usize> {
123            [(x, y), (y, x)]
124                .into_iter()
125                .filter_map(|e| directed.get(&e).map(|&(f, _)| f))
126                .collect()
127        };
128        // From `(from, to)`, the edge on from `to`.
129        let onward = |(from, to): (u32, u32)| -> Option<(u32, u32)> {
130            let around = &neighbours[&to];
131            if around.len() != 4 {
132                return None;
133            }
134            let shared = faces_of(from, to);
135            let mut opposite = around
136                .iter()
137                .filter(|&&w| w != from && faces_of(to, w).is_disjoint(&shared));
138            let w = *opposite.next()?;
139            opposite.next().is_none().then_some((to, w))
140        };
141        let forward = walk_by(
142            (a, b),
143            |&(x, y)| undirected(x, y),
144            |&e| Ok::<_, GeopError>(onward(e)),
145        )?;
146        let mut edges: Vec<[u32; 2]> = forward
147            .nodes
148            .iter()
149            .map(|&(x, y)| undirected(x, y))
150            .collect();
151        if forward.end != Walked::Closed {
152            let back = walk_by(
153                (b, a),
154                |&(x, y)| undirected(x, y),
155                |&e| Ok::<_, GeopError>(onward(e)),
156            )?;
157            edges.extend(back.nodes[1..].iter().map(|&(x, y)| undirected(x, y)));
158        }
159        Ok(edges)
160    }
161
162    /// Whether only one face runs along the edge between `a` and `b`.
163    pub fn is_boundary(&self, a: u32, b: u32) -> bool {
164        let directed = self.directed_edges();
165        directed.contains_key(&(a, b)) != directed.contains_key(&(b, a))
166    }
167
168    /// The boundary the open edge between `a` and `b` is on, as the
169    /// vertices around it, in the order the faces along it run: a face
170    /// closing it runs the other way. Fails for an edge that is not on the
171    /// boundary, and for a boundary that touches itself at a vertex, by
172    /// name.
173    pub fn boundary_run(&self, a: u32, b: u32) -> GeopResult<Vec<u32>> {
174        let directed = self.directed_edges();
175        let open =
176            |x: u32, y: u32| directed.contains_key(&(x, y)) && !directed.contains_key(&(y, x));
177        let (a, b) = if open(a, b) {
178            (a, b)
179        } else if open(b, a) {
180            (b, a)
181        } else {
182            return Err(GeopError::new(format!(
183                "edge {} is not on the cage's boundary",
184                edge_key(a, b)
185            )));
186        };
187        let run = walk_by(
188            (a, b),
189            |&(x, _)| x,
190            |&(_, y)| {
191                let mut next = directed.keys().filter(|&&(x, z)| x == y && open(x, z));
192                let Some(&(_, z)) = next.next() else {
193                    return Ok(None);
194                };
195                if next.next().is_some() {
196                    return Err(GeopError::new(format!(
197                        "the boundary touches itself at vertex {}",
198                        crate::cage::vertex_key(y)
199                    )));
200                }
201                Ok(Some((y, z)))
202            },
203        )?;
204        if run.end != Walked::Closed {
205            return Err(GeopError::new(format!(
206                "the boundary through edge {} does not close",
207                edge_key(a, b)
208            )));
209        }
210        Ok(run.nodes.into_iter().map(|(x, _)| x).collect())
211    }
212}