1use std::collections::{BTreeMap, BTreeSet, HashMap};
12
13use geop_core_math::{
14 geop_error::{GeopError, GeopResult},
15 solvers::walk::{Take, flood, walk},
16};
17use serde::{Deserialize, Serialize};
18
19#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct CageVertex {
22 pub id: u32,
23 pub at: [f64; 3],
24}
25
26#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
28pub struct CageFace {
29 pub id: u32,
30 pub vertices: Vec<u32>,
31}
32
33#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
36pub struct Crease {
37 pub edge: [u32; 2],
39 pub sharpness: f64,
44}
45
46#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
51pub struct Cage {
52 pub vertices: Vec<CageVertex>,
53 pub faces: Vec<CageFace>,
54 #[serde(default)]
56 pub creases: Vec<Crease>,
57 pub next_id: u32,
58}
59
60#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case")]
65pub enum Mirror {
66 #[default]
67 None,
68 X,
69 Y,
70 Z,
71}
72
73impl Mirror {
74 pub fn axis(self) -> Option<usize> {
76 match self {
77 Mirror::None => None,
78 Mirror::X => Some(0),
79 Mirror::Y => Some(1),
80 Mirror::Z => Some(2),
81 }
82 }
83}
84
85pub fn vertex_key(id: u32) -> String {
87 format!("v{id}")
88}
89
90pub fn face_key(id: u32) -> String {
92 format!("f{id}")
93}
94
95pub fn next(faces: &[Vec<usize>], (f, k): (usize, usize)) -> usize {
97 faces[f][(k + 1) % faces[f].len()]
98}
99
100pub fn prev(faces: &[Vec<usize>], (f, k): (usize, usize)) -> usize {
102 let n = faces[f].len();
103 faces[f][(k + n - 1) % n]
104}
105
106pub fn undirected<T: Ord + Copy>(a: T, b: T) -> [T; 2] {
108 [a.min(b), a.max(b)]
109}
110
111pub fn edge_key(a: u32, b: u32) -> String {
114 let [a, b] = undirected(a, b);
115 format!("e{a}-{b}")
116}
117
118impl Cage {
119 pub fn vertex(&self, id: u32) -> GeopResult<&CageVertex> {
121 self.vertices
122 .iter()
123 .find(|v| v.id == id)
124 .ok_or_else(|| GeopError::new(format!("the cage has no vertex {}", vertex_key(id))))
125 }
126
127 pub fn vertex_mut(&mut self, id: u32) -> GeopResult<&mut CageVertex> {
128 self.vertices
129 .iter_mut()
130 .find(|v| v.id == id)
131 .ok_or_else(|| GeopError::new(format!("the cage has no vertex {}", vertex_key(id))))
132 }
133
134 pub fn face(&self, id: u32) -> GeopResult<&CageFace> {
136 self.faces
137 .iter()
138 .find(|f| f.id == id)
139 .ok_or_else(|| GeopError::new(format!("the cage has no face {}", face_key(id))))
140 }
141
142 pub fn fresh_id(&mut self) -> u32 {
144 let id = self.next_id;
145 self.next_id += 1;
146 id
147 }
148
149 pub fn edges(&self) -> BTreeSet<[u32; 2]> {
151 self.faces
152 .iter()
153 .flat_map(|f| {
154 let n = f.vertices.len();
155 (0..n).map(move |k| {
156 let (a, b) = (f.vertices[k], f.vertices[(k + 1) % n]);
157 undirected(a, b)
158 })
159 })
160 .collect()
161 }
162
163 pub fn sharpness(&self, a: u32, b: u32) -> f64 {
166 let edge = undirected(a, b);
167 (self.creases.iter())
168 .find(|c| c.edge == edge)
169 .map_or(0.0, |c| c.sharpness)
170 }
171
172 pub fn is_crease(&self, a: u32, b: u32) -> bool {
174 self.sharpness(a, b) > 0.0
175 }
176
177 pub fn directed_edges(&self) -> HashMap<(u32, u32), (usize, usize)> {
182 let mut directed = HashMap::new();
183 for (f, face) in self.faces.iter().enumerate() {
184 let n = face.vertices.len();
185 for k in 0..n {
186 directed.insert((face.vertices[k], face.vertices[(k + 1) % n]), (f, k));
187 }
188 }
189 directed
190 }
191
192 pub fn indexed(&self) -> GeopResult<(Mesh, Topology)> {
196 let mesh = Mesh::new(self, Mirror::None)?;
197 let topology = Topology::new(
198 mesh.positions.len(),
199 &mesh.faces,
200 &mesh.vertex_names,
201 &mesh.face_names,
202 )?;
203 Ok((mesh, topology))
204 }
205}
206
207#[derive(Clone, Debug)]
210pub struct Mesh {
211 pub vertex_names: Vec<String>,
213 pub positions: Vec<[f64; 3]>,
214 pub face_names: Vec<String>,
217 pub faces: Vec<Vec<usize>>,
218 pub sharp: BTreeSet<[usize; 2]>,
220 pub soft: BTreeMap<[usize; 2], f64>,
223}
224
225impl Mesh {
226 pub fn new(cage: &Cage, mirror: Mirror) -> GeopResult<Self> {
232 let mut index: HashMap<u32, usize> = HashMap::new();
233 let mut mesh = Mesh {
234 vertex_names: Vec::new(),
235 positions: Vec::new(),
236 face_names: Vec::new(),
237 faces: Vec::new(),
238 sharp: BTreeSet::new(),
239 soft: BTreeMap::new(),
240 };
241 for v in &cage.vertices {
242 if !v.at.iter().all(|c| c.is_finite()) {
243 return Err(GeopError::new(format!(
244 "vertex {} is not at a finite point: {:?}",
245 vertex_key(v.id),
246 v.at
247 )));
248 }
249 if index.insert(v.id, mesh.positions.len()).is_some() {
250 return Err(GeopError::new(format!(
251 "the cage has two vertices {}",
252 vertex_key(v.id)
253 )));
254 }
255 mesh.vertex_names.push(vertex_key(v.id));
256 mesh.positions.push(v.at);
257 }
258 let mut face_ids = BTreeSet::new();
259 for f in &cage.faces {
260 if !face_ids.insert(f.id) {
261 return Err(GeopError::new(format!(
262 "the cage has two faces {}",
263 face_key(f.id)
264 )));
265 }
266 let vertices = f
267 .vertices
268 .iter()
269 .map(|id| {
270 index.get(id).copied().ok_or_else(|| {
271 GeopError::new(format!(
272 "face {} runs through vertex {}, which the cage does not have",
273 face_key(f.id),
274 vertex_key(*id)
275 ))
276 })
277 })
278 .collect::<GeopResult<Vec<_>>>()?;
279 mesh.face_names.push(face_key(f.id));
280 mesh.faces.push(vertices);
281 }
282 for crease in &cage.creases {
283 let [a, b] = crease.edge;
284 if !(crease.sharpness > 0.0 && crease.sharpness <= 1.0) {
285 return Err(GeopError::new(format!(
286 "crease {} has a sharpness of {}: it must be above zero and at most one",
287 edge_key(a, b),
288 crease.sharpness
289 )));
290 }
291 match (index.get(&a), index.get(&b)) {
292 (Some(&i), Some(&j)) if crease.sharpness == 1.0 => {
293 mesh.sharp.insert(undirected(i, j));
294 }
295 (Some(&i), Some(&j)) => {
296 mesh.soft.insert(undirected(i, j), crease.sharpness);
297 }
298 _ => {
299 return Err(GeopError::new(format!(
300 "crease {} runs between vertices the cage does not have",
301 edge_key(a, b)
302 )));
303 }
304 }
305 }
306 if let Some(axis) = mirror.axis() {
307 mesh.add_mirror_image(axis)?;
308 }
309 Ok(mesh)
310 }
311
312 fn add_mirror_image(&mut self, axis: usize) -> GeopResult<()> {
315 let plane = ["x", "y", "z"][axis];
316 let n = self.positions.len();
317 let mut image = vec![0; n];
318 for (i, mirrored) in image.iter_mut().enumerate() {
319 let mut at = self.positions[i];
320 if at[axis] == 0.0 {
321 *mirrored = i;
322 continue;
323 }
324 if at[axis] < 0.0 {
325 return Err(GeopError::new(format!(
326 "vertex {} is on the far side of the mirror plane {plane} = 0: the cage holds the half where {plane} >= 0",
327 self.vertex_names[i]
328 )));
329 }
330 at[axis] = -at[axis];
331 *mirrored = self.positions.len();
332 self.vertex_names.push(format!("{}m", self.vertex_names[i]));
333 self.positions.push(at);
334 }
335 let faces = self.faces.len();
336 for f in 0..faces {
337 if self.faces[f].iter().all(|&v| image[v] == v) {
338 return Err(GeopError::new(format!(
339 "face {} lies in the mirror plane {plane} = 0",
340 self.face_names[f]
341 )));
342 }
343 let mirrored: Vec<usize> = self.faces[f].iter().rev().map(|&v| image[v]).collect();
344 self.face_names.push(format!("{}m", self.face_names[f]));
345 self.faces.push(mirrored);
346 }
347 let sharp: Vec<[usize; 2]> = self.sharp.iter().copied().collect();
348 for [a, b] in sharp {
349 let (i, j) = (image[a], image[b]);
350 self.sharp.insert(undirected(i, j));
351 }
352 let soft: Vec<([usize; 2], f64)> = self.soft.iter().map(|(e, s)| (*e, *s)).collect();
353 for ([a, b], s) in soft {
354 let (i, j) = (image[a], image[b]);
355 self.soft.insert(undirected(i, j), s);
356 }
357 Ok(())
358 }
359
360 pub fn edge_name(&self, a: usize, b: usize) -> String {
363 let [a, b] = undirected(a, b);
364 format!(
365 "e{}-{}",
366 &self.vertex_names[a][1..],
367 &self.vertex_names[b][1..]
368 )
369 }
370}
371
372#[derive(Clone, Debug)]
375pub struct Topology {
376 pub directed: HashMap<(usize, usize), (usize, usize)>,
379 pub fans: Vec<(Vec<(usize, usize)>, bool)>,
384}
385
386impl Topology {
387 pub fn new(
395 vertex_count: usize,
396 faces: &[Vec<usize>],
397 vertex_names: &[String],
398 face_names: &[String],
399 ) -> GeopResult<Self> {
400 let mut directed = HashMap::new();
401 let mut corners: Vec<Vec<(usize, usize)>> = vec![Vec::new(); vertex_count];
402 for (f, face) in faces.iter().enumerate() {
403 let n = face.len();
404 if n < 3 {
405 return Err(GeopError::new(format!(
406 "face {} has {n} vertices: a face needs at least three",
407 face_names[f]
408 )));
409 }
410 for k in 0..n {
411 let (a, b) = (face[k], face[(k + 1) % n]);
412 if face[..k].contains(&a) {
413 return Err(GeopError::new(format!(
414 "face {} runs through vertex {} twice",
415 face_names[f], vertex_names[a]
416 )));
417 }
418 if let Some((g, _)) = directed.insert((a, b), (f, k)) {
419 return Err(GeopError::new(format!(
420 "faces {} and {} both run from {} to {}: they are turned against each other, or more than two faces meet at that edge",
421 face_names[g], face_names[f], vertex_names[a], vertex_names[b]
422 )));
423 }
424 corners[a].push((f, k));
425 }
426 }
427 let mut fans = Vec::with_capacity(vertex_count);
428 for (v, around) in corners.iter().enumerate() {
429 if around.is_empty() {
430 return Err(GeopError::new(format!(
431 "vertex {} belongs to no face",
432 vertex_names[v]
433 )));
434 }
435 let start = around
438 .iter()
439 .find(|&&c| !directed.contains_key(&(next(faces, c), v)))
440 .copied();
441 let closed = start.is_none();
442 let fan = walk(start.unwrap_or(around[0]), |&last| {
447 Ok::<_, GeopError>(directed.get(&(v, prev(faces, last))).copied())
448 })?
449 .nodes;
450 if fan.len() != around.len() {
451 return Err(GeopError::new(format!(
452 "the faces around vertex {} do not make one fan: the surface pinches there",
453 vertex_names[v]
454 )));
455 }
456 if closed && fan.len() < 3 {
457 return Err(GeopError::new(format!(
458 "inner vertex {} belongs to {} faces: it needs at least three",
459 vertex_names[v],
460 fan.len()
461 )));
462 }
463 fans.push((fan, closed));
464 }
465 Ok(Self { directed, fans })
466 }
467
468 pub fn is_boundary(&self, a: usize, b: usize) -> bool {
470 self.directed.contains_key(&(a, b)) != self.directed.contains_key(&(b, a))
471 }
472
473 pub fn edges(&self) -> BTreeMap<[usize; 2], Vec<usize>> {
475 let mut edges: BTreeMap<[usize; 2], Vec<usize>> = BTreeMap::new();
476 for (&(a, b), &(f, _)) in &self.directed {
477 edges.entry(undirected(a, b)).or_default().push(f);
478 }
479 for faces in edges.values_mut() {
480 faces.sort();
481 }
482 edges
483 }
484}
485
486impl Mesh {
487 pub fn topology(&self) -> GeopResult<Topology> {
490 if self.faces.is_empty() {
491 return Err(GeopError::new("the cage has no faces"));
492 }
493 let topology = Topology::new(
494 self.positions.len(),
495 &self.faces,
496 &self.vertex_names,
497 &self.face_names,
498 )?;
499 let mut seen = vec![false; self.positions.len()];
501 let reached = flood([0], Take::Newest, |&v| {
502 Ok::<_, GeopError>(
503 (topology.fans[v].0.iter())
504 .flat_map(|&(f, _)| self.faces[f].iter().copied())
505 .collect(),
506 )
507 })?;
508 for v in reached {
509 seen[v] = true;
510 }
511 if let Some(apart) = seen.iter().position(|s| !s) {
512 return Err(GeopError::new(format!(
513 "the cage falls apart: vertex {} is not connected to vertex {}",
514 self.vertex_names[apart], self.vertex_names[0]
515 )));
516 }
517 for &[a, b] in self.sharp.iter().chain(self.soft.keys()) {
518 if !topology.directed.contains_key(&(a, b)) && !topology.directed.contains_key(&(b, a))
519 {
520 return Err(GeopError::new(format!(
521 "crease {} is no edge of the cage",
522 self.edge_name(a, b)
523 )));
524 }
525 }
526 Ok(topology)
527 }
528}