1use 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#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct Ring {
19 pub edges: Vec<(u32, u32)>,
23 pub quads: Vec<usize>,
27 pub closed: bool,
28}
29
30impl Cage {
31 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 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 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 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 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 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 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 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}