1use std::collections::{BTreeMap, BTreeSet, HashMap};
15
16use geop_core_math::{
17 geop_error::{GeopError, GeopResult},
18 plain::{add, centroid, cross, dot, norm, normalize, scale, sub},
19};
20
21use crate::cage::{
22 Cage, CageFace, CageVertex, Crease, Mesh, Mirror, edge_key, face_key, next, prev, undirected,
23 vertex_key,
24};
25
26#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum Element {
31 Vertex(u32),
32 Edge(u32, u32),
34 Face(u32),
35}
36
37impl Element {
38 pub fn parse(key: &str) -> Option<Element> {
40 let (kind, rest) = key.split_at_checked(1)?;
41 match kind {
42 "v" => rest.parse().ok().map(Element::Vertex),
43 "f" => rest.parse().ok().map(Element::Face),
44 "e" => {
45 let (a, b) = rest.split_once('-')?;
46 let (a, b): (u32, u32) = (a.parse().ok()?, b.parse().ok()?);
47 Some(Element::Edge(a.min(b), a.max(b)))
48 }
49 _ => None,
50 }
51 }
52
53 pub fn key(self) -> String {
55 match self {
56 Element::Vertex(v) => vertex_key(v),
57 Element::Edge(a, b) => edge_key(a, b),
58 Element::Face(f) => face_key(f),
59 }
60 }
61}
62
63fn cage_of(points: Vec<[f64; 3]>, faces: Vec<Vec<usize>>) -> Cage {
66 let n = points.len() as u32;
67 Cage {
68 vertices: points
69 .into_iter()
70 .enumerate()
71 .map(|(i, at)| CageVertex { id: i as u32, at })
72 .collect(),
73 faces: faces
74 .into_iter()
75 .enumerate()
76 .map(|(i, f)| CageFace {
77 id: n + i as u32,
78 vertices: f.into_iter().map(|v| v as u32).collect(),
79 })
80 .collect(),
81 creases: Vec::new(),
82 next_id: 0,
83 }
84 .numbered()
85}
86
87type DirectedEdges = BTreeSet<(u32, u32)>;
89
90fn rotated(face: &[u32], at: u32) -> Vec<u32> {
93 let k = face.iter().position(|&v| v == at).expect("on its face");
94 face[k..].iter().chain(&face[..k]).copied().collect()
95}
96
97fn deduplicated(cycle: &[u32]) -> Vec<u32> {
100 let mut out: Vec<u32> = Vec::with_capacity(cycle.len());
101 for &v in cycle {
102 if out.last() != Some(&v) {
103 out.push(v);
104 }
105 }
106 while out.len() > 1 && out.first() == out.last() {
107 out.pop();
108 }
109 out
110}
111
112fn repeated(cycle: &[u32]) -> Option<u32> {
114 let mut seen = BTreeSet::new();
115 cycle.iter().copied().find(|&v| !seen.insert(v))
116}
117
118impl Cage {
119 fn numbered(mut self) -> Self {
121 let ids = self
122 .vertices
123 .iter()
124 .map(|v| v.id)
125 .chain(self.faces.iter().map(|f| f.id));
126 self.next_id = ids.max().map_or(0, |m| m + 1);
127 self
128 }
129
130 pub fn cuboid(size: [f64; 3]) -> Self {
133 let [x, y, z] = size.map(|s| s / 2.0);
134 let points = vec![
135 [-x, -y, -z],
136 [x, -y, -z],
137 [x, y, -z],
138 [-x, y, -z],
139 [-x, -y, z],
140 [x, -y, z],
141 [x, y, z],
142 [-x, y, z],
143 ];
144 let faces = vec![
145 vec![0, 3, 2, 1],
146 vec![4, 5, 6, 7],
147 vec![0, 1, 5, 4],
148 vec![1, 2, 6, 5],
149 vec![2, 3, 7, 6],
150 vec![3, 0, 4, 7],
151 ];
152 cage_of(points, faces)
153 }
154
155 pub fn cylinder(radius: f64, height: f64, segments: usize) -> Self {
158 let rows = 2;
159 let mut points = Vec::new();
160 for r in 0..=rows {
161 let z = height * (r as f64 / rows as f64 - 0.5);
162 for i in 0..segments {
163 let (s, c) = match (4 * i) % segments {
167 0 => match 4 * i / segments {
168 0 => (0.0, 1.0),
169 1 => (1.0, 0.0),
170 2 => (0.0, -1.0),
171 _ => (-1.0, 0.0),
172 },
173 _ => (2.0 * std::f64::consts::PI * i as f64 / segments as f64).sin_cos(),
174 };
175 points.push([radius * c, radius * s, z]);
176 }
177 }
178 let at = |r: usize, i: usize| r * segments + i % segments;
179 let mut faces = vec![(0..segments).rev().map(|i| at(0, i)).collect::<Vec<_>>()];
180 faces.push((0..segments).map(|i| at(rows, i)).collect());
181 for r in 0..rows {
182 for i in 0..segments {
183 faces.push(vec![at(r, i), at(r, i + 1), at(r + 1, i + 1), at(r + 1, i)]);
184 }
185 }
186 cage_of(points, faces)
187 }
188
189 pub fn sphere(radius: f64) -> Self {
192 let mut index: HashMap<[i32; 3], usize> = HashMap::new();
193 let mut points = Vec::new();
194 let mut faces = Vec::new();
195 for axis in 0..3 {
196 for sign in [-1, 1] {
197 let (u, v) = ((axis + 1) % 3, (axis + 2) % 3);
199 let (u, v) = if sign > 0 { (u, v) } else { (v, u) };
200 let mut vertex = |a: i32, b: i32| {
201 let mut c = [0; 3];
202 c[axis] = sign;
203 c[u] = a;
204 c[v] = b;
205 *index.entry(c).or_insert_with(|| {
206 let p = c.map(f64::from);
207 let length = norm(p);
208 points.push(p.map(|x| radius * x / length));
209 points.len() - 1
210 })
211 };
212 for a in -1..1 {
213 for b in -1..1 {
214 faces.push(vec![
215 vertex(a, b),
216 vertex(a + 1, b),
217 vertex(a + 1, b + 1),
218 vertex(a, b + 1),
219 ]);
220 }
221 }
222 }
223 }
224 cage_of(points, faces)
225 }
226
227 pub fn plane(size: f64, n: usize) -> Self {
231 let mut points = Vec::new();
232 for j in 0..=n {
233 for i in 0..=n {
234 let at = |k: usize| size * (k as f64 / n as f64 - 0.5);
235 points.push([at(i), at(j), 0.0]);
236 }
237 }
238 let at = |i: usize, j: usize| j * (n + 1) + i;
239 let mut faces = Vec::new();
240 for j in 0..n {
241 for i in 0..n {
242 faces.push(vec![at(i, j), at(i + 1, j), at(i + 1, j + 1), at(i, j + 1)]);
243 }
244 }
245 cage_of(points, faces)
246 }
247
248 pub fn has(&self, element: Element) -> bool {
250 match element {
251 Element::Vertex(v) => self.vertices.iter().any(|x| x.id == v),
252 Element::Face(f) => self.faces.iter().any(|x| x.id == f),
253 Element::Edge(a, b) => self.edges().contains(&[a, b]),
254 }
255 }
256
257 pub fn vertices_of(&self, keys: &[String]) -> BTreeSet<u32> {
259 let mut vertices = BTreeSet::new();
260 for key in keys {
261 match Element::parse(key) {
262 Some(Element::Vertex(v)) => {
263 vertices.insert(v);
264 }
265 Some(Element::Edge(a, b)) => {
266 vertices.extend([a, b]);
267 }
268 Some(Element::Face(f)) => {
269 if let Ok(face) = self.face(f) {
270 vertices.extend(face.vertices.iter().copied());
271 }
272 }
273 None => {}
274 }
275 }
276 vertices.retain(|&v| self.vertex(v).is_ok());
277 vertices
278 }
279
280 pub fn centre(&self, vertices: &BTreeSet<u32>) -> Option<[f64; 3]> {
282 let ats = vertices
283 .iter()
284 .map(|&v| Some(self.vertex(v).ok()?.at))
285 .collect::<Option<Vec<_>>>()?;
286 centroid(ats)
287 }
288
289 fn on_plane(&self, v: u32, mirror: Mirror) -> GeopResult<bool> {
292 let at = self.vertex(v)?.at;
293 Ok(mirror.axis().is_some_and(|a| at[a] == 0.0))
294 }
295
296 pub fn transform(
299 &mut self,
300 vertices: &BTreeSet<u32>,
301 mirror: Mirror,
302 f: impl Fn([f64; 3]) -> [f64; 3],
303 ) {
304 for vertex in &mut self.vertices {
305 if vertices.contains(&vertex.id) {
306 let on_plane = mirror.axis().filter(|&a| vertex.at[a] == 0.0);
307 vertex.at = f(vertex.at);
308 if let Some(axis) = on_plane {
309 vertex.at[axis] = 0.0;
310 }
311 }
312 }
313 }
314
315 fn transaction(&mut self, edit: impl FnOnce(&mut Cage) -> GeopResult<()>) -> GeopResult<()> {
318 let mut edited = self.clone();
319 edit(&mut edited)?;
320 *self = edited;
321 Ok(())
322 }
323
324 pub fn set_sharpness(&mut self, edges: &[[u32; 2]], sharpness: f64) {
327 for &[a, b] in edges {
328 let edge = undirected(a, b);
329 self.creases.retain(|c| c.edge != edge);
330 if sharpness > 0.0 {
331 self.creases.push(Crease {
332 edge,
333 sharpness: sharpness.min(1.0),
334 });
335 }
336 }
337 self.creases.sort_by_key(|c| c.edge);
338 }
339
340 fn rename_creases(&mut self, to: impl Fn([u32; 2]) -> [u32; 2]) {
343 let mut creases: Vec<Crease> = std::mem::take(&mut self.creases);
344 for c in &mut creases {
345 let [a, b] = to(c.edge);
346 c.edge = undirected(a, b);
347 }
348 creases.retain(|c| c.edge[0] != c.edge[1]);
349 creases.sort_by_key(|c| c.edge);
350 creases.dedup_by_key(|c| c.edge);
351 self.creases = creases;
352 }
353
354 fn tidy(&mut self) {
357 let used: BTreeSet<u32> = self
358 .faces
359 .iter()
360 .flat_map(|f| f.vertices.iter().copied())
361 .collect();
362 self.vertices.retain(|v| used.contains(&v.id));
363 let edges = self.edges();
364 self.creases.retain(|c| edges.contains(&c.edge));
365 }
366
367 pub fn delete_faces(&mut self, faces: &BTreeSet<u32>) {
370 self.faces.retain(|f| !faces.contains(&f.id));
371 self.tidy();
372 }
373
374 pub fn faces_along(&self, edges: &[[u32; 2]]) -> BTreeSet<u32> {
376 let directed = self.directed_edges();
377 edges
378 .iter()
379 .flat_map(|&[a, b]| [(a, b), (b, a)])
380 .filter_map(|e| directed.get(&e).map(|&(f, _)| self.faces[f].id))
381 .collect()
382 }
383
384 pub fn faces_around(&self, vertices: &BTreeSet<u32>) -> BTreeSet<u32> {
386 (self.faces.iter())
387 .filter(|f| f.vertices.iter().any(|v| vertices.contains(v)))
388 .map(|f| f.id)
389 .collect()
390 }
391
392 pub fn frame(&self, faces: &BTreeSet<u32>) -> Option<[[f64; 3]; 3]> {
397 let mut normal = [0.0; 3];
398 for &f in faces {
399 let n = self.normal(self.face(f).ok()?).ok()?;
400 (0..3).for_each(|i| normal[i] += n[i]);
401 }
402 let w = normalize(normal)?;
403 let first = self.face(*faces.first()?).ok()?;
404 let a = self.vertex(*first.vertices.first()?).ok()?.at;
405 let b = self.vertex(*first.vertices.get(1)?).ok()?.at;
406 let edge = sub(b, a);
407 let u = normalize(sub(edge, scale(w, dot(edge, w))))?;
408 Some([u, cross(w, u), w])
409 }
410
411 fn normal(&self, face: &CageFace) -> GeopResult<[f64; 3]> {
414 let mut normal = [0.0; 3];
415 let n = face.vertices.len();
416 for k in 0..n {
417 let a = self.vertex(face.vertices[k])?.at;
418 let b = self.vertex(face.vertices[(k + 1) % n])?.at;
419 let c = cross(a, b);
420 for i in 0..3 {
421 normal[i] += c[i] / 2.0;
422 }
423 }
424 Ok(normal)
425 }
426
427 fn inward(&self, face: &CageFace, a: u32, b: u32) -> GeopResult<Option<[f64; 3]>> {
431 let n = self.normal(face)?;
432 let d = sub(self.vertex(b)?.at, self.vertex(a)?.at);
433 let runs_a_to_b = (face.vertices.iter())
434 .position(|&v| v == a)
435 .is_some_and(|k| face.vertices[(k + 1) % face.vertices.len()] == b);
436 let d = if runs_a_to_b { d } else { scale(d, -1.0) };
437 Ok(normalize(cross(n, d)))
438 }
439
440 fn rim(&self, faces: &BTreeSet<u32>) -> (Vec<(u32, u32)>, DirectedEdges) {
444 let mut directed: BTreeSet<(u32, u32)> = BTreeSet::new();
445 let mut everywhere: BTreeSet<(u32, u32)> = BTreeSet::new();
446 for face in &self.faces {
447 let n = face.vertices.len();
448 for k in 0..n {
449 let edge = (face.vertices[k], face.vertices[(k + 1) % n]);
450 everywhere.insert(edge);
451 if faces.contains(&face.id) {
452 directed.insert(edge);
453 }
454 }
455 }
456 let rim = directed
457 .iter()
458 .copied()
459 .filter(|&(a, b)| !directed.contains(&(b, a)))
460 .collect();
461 (rim, everywhere)
462 }
463
464 fn offset_region(
473 &mut self,
474 name: &str,
475 faces: &BTreeSet<u32>,
476 mirror: Mirror,
477 offset: impl Fn(&Cage, u32) -> GeopResult<[f64; 3]>,
478 ) -> GeopResult<()> {
479 if faces.is_empty() {
480 return Err(GeopError::new(format!("{name}: no face selected")));
481 }
482 for &f in faces {
483 self.face(f)?;
484 }
485 let (rim, everywhere) = self.rim(faces);
486 let region: BTreeSet<u32> = (self.faces.iter())
487 .filter(|f| faces.contains(&f.id))
488 .flat_map(|f| f.vertices.iter().copied())
489 .collect();
490 let mut offsets: BTreeMap<u32, [f64; 3]> = BTreeMap::new();
491 for &v in ®ion {
492 let mut d = offset(self, v)?;
493 if self.on_plane(v, mirror)? {
494 d[mirror.axis().expect("on a plane")] = 0.0;
495 }
496 if !d.iter().all(|c| c.is_finite()) {
497 return Err(GeopError::new(format!(
498 "{name}: vertex {} cannot be moved: {d:?}",
499 vertex_key(v)
500 )));
501 }
502 offsets.insert(v, d);
503 }
504 let rim_vertices: BTreeSet<u32> = rim.iter().flat_map(|&(a, b)| [a, b]).collect();
505 let mut moved: BTreeMap<u32, u32> = BTreeMap::new();
506 for (&v, d) in &offsets {
507 let to = add(self.vertex(v)?.at, *d);
508 if rim_vertices.contains(&v) {
509 let id = self.fresh_id();
510 self.vertices.push(CageVertex { id, at: to });
511 moved.insert(v, id);
512 } else {
513 self.vertex_mut(v)?.at = to;
514 }
515 }
516 for face in &mut self.faces {
517 if faces.contains(&face.id) {
518 for v in &mut face.vertices {
519 if let Some(&m) = moved.get(v) {
520 *v = m;
521 }
522 }
523 }
524 }
525 for (a, b) in rim {
526 let open = !everywhere.contains(&(b, a));
527 if open && self.on_plane(a, mirror)? && self.on_plane(b, mirror)? {
528 continue;
529 }
530 let id = self.fresh_id();
531 self.faces.push(CageFace {
532 id,
533 vertices: vec![a, b, moved[&b], moved[&a]],
534 });
535 }
536 let edges = self.edges();
537 let to = |v: u32| moved.get(&v).copied().unwrap_or(v);
538 self.rename_creases(|edge| {
539 if edges.contains(&edge) {
540 edge
541 } else {
542 [to(edge[0]), to(edge[1])]
543 }
544 });
545 self.tidy();
546 Ok(())
547 }
548
549 pub fn extrude(
552 &mut self,
553 faces: &BTreeSet<u32>,
554 distance: f64,
555 mirror: Mirror,
556 ) -> GeopResult<()> {
557 let mut normal: BTreeMap<u32, [f64; 3]> = BTreeMap::new();
558 for face in self.faces.iter().filter(|f| faces.contains(&f.id)) {
559 let n = self.normal(face)?;
560 for &v in &face.vertices {
561 let sum = normal.entry(v).or_insert([0.0; 3]);
562 *sum = add(*sum, n);
563 }
564 }
565 self.offset_region("extrude", faces, mirror, |cage, v| {
566 let mut n = normal[&v];
567 if cage.on_plane(v, mirror)? {
568 n[mirror.axis().expect("on a plane")] = 0.0;
569 }
570 normalize(n).map(|n| scale(n, distance)).ok_or_else(|| {
571 GeopError::new(format!(
572 "extrude: the faces around vertex {} face no one way",
573 vertex_key(v)
574 ))
575 })
576 })
577 }
578
579 pub fn inset(
584 &mut self,
585 faces: &BTreeSet<u32>,
586 thickness: f64,
587 mirror: Mirror,
588 ) -> GeopResult<()> {
589 if faces.is_empty() {
590 return Err(GeopError::new("inset: no face selected"));
591 }
592 let (rim, _) = self.rim(faces);
593 let mut inward: BTreeMap<u32, Vec<[f64; 3]>> = BTreeMap::new();
594 let directed = self.directed_edges();
595 for &(a, b) in &rim {
596 let (f, _) = directed[&(a, b)];
597 let Some(d) = self.inward(&self.faces[f], a, b)? else {
598 return Err(GeopError::new(format!(
599 "inset: face {} has no area",
600 face_key(self.faces[f].id)
601 )));
602 };
603 inward.entry(a).or_default().push(d);
604 inward.entry(b).or_default().push(d);
605 }
606 let mut offsets: BTreeMap<u32, [f64; 3]> = BTreeMap::new();
607 for (&v, ds) in &inward {
608 let mut d = match ds[..] {
609 [d] => scale(d, thickness),
610 [d, e] => {
611 let along = 1.0 + dot(d, e);
613 if along <= 0.0 {
614 return Err(GeopError::new(format!(
615 "inset: the rim turns back on itself at vertex {}",
616 vertex_key(v)
617 )));
618 }
619 scale(add(d, e), thickness / along)
620 }
621 _ => {
622 return Err(GeopError::new(format!(
623 "inset: the rim passes vertex {} more than once",
624 vertex_key(v)
625 )));
626 }
627 };
628 if self.on_plane(v, mirror)? {
629 d[mirror.axis().expect("on a plane")] = 0.0;
630 }
631 offsets.insert(v, d);
632 }
633 let moved = |cage: &Cage, v: u32| -> GeopResult<[f64; 3]> {
634 Ok(add(
635 cage.vertex(v)?.at,
636 offsets.get(&v).copied().unwrap_or([0.0; 3]),
637 ))
638 };
639 let region = self.faces.iter().filter(|f| faces.contains(&f.id));
640 self.check_faces_keep_shape("inset", thickness, mirror, region, |face, k| {
641 let v = face.vertices[k];
642 offsets.contains_key(&v).then(|| moved(self, v)).transpose()
643 })?;
644 self.offset_region("inset", faces, mirror, |_, v| {
645 Ok(offsets.get(&v).copied().unwrap_or([0.0; 3]))
646 })
647 }
648
649 fn check_faces_keep_shape<'f>(
657 &self,
658 name: &str,
659 amount: f64,
660 mirror: Mirror,
661 faces: impl IntoIterator<Item = &'f CageFace>,
662 corner_at: impl Fn(&CageFace, usize) -> GeopResult<Option<[f64; 3]>>,
663 ) -> GeopResult<()> {
664 for face in faces {
665 let n = face.vertices.len();
666 let normal = self.normal(face)?;
667 let at = |k: usize| self.vertex(face.vertices[k % n]).map(|v| v.at);
668 let to = |k: usize| -> GeopResult<[f64; 3]> {
669 Ok(corner_at(face, k % n)?.unwrap_or(at(k)?))
670 };
671 let on_plane = |k: usize| self.on_plane(face.vertices[k % n], mirror);
672 for k in 0..n {
673 let before = sub(at(k + 1)?, at(k)?);
674 let after = sub(to(k + 1)?, to(k)?);
675 if dot(before, after) <= 0.0 {
676 return Err(GeopError::new(format!(
677 "{name}: face {} is too small for {amount}: its edge {} would turn round",
678 face_key(face.id),
679 edge_key(face.vertices[k], face.vertices[(k + 1) % n])
680 )));
681 }
682 let Some(to) = corner_at(face, k)? else {
683 continue;
684 };
685 for e in 0..n {
686 let own = e == k || (e + 1) % n == k;
689 if own || on_plane(k)? && on_plane(e)? && on_plane(e + 1)? {
690 continue;
691 }
692 let (p, q) = (at(e)?, at(e + 1)?);
693 let inward = cross(normal, sub(q, p));
694 if dot(sub(to, p), inward) <= 0.0 {
695 return Err(GeopError::new(format!(
696 "{name}: face {} is too small for {amount}: its corner {} would leave it across edge {}",
697 face_key(face.id),
698 vertex_key(face.vertices[k]),
699 edge_key(face.vertices[e], face.vertices[(e + 1) % n])
700 )));
701 }
702 }
703 }
704 }
705 Ok(())
706 }
707
708 pub fn insert_loop(&mut self, a: u32, b: u32, t: f64) -> GeopResult<()> {
713 self.transaction(|cage| cage.insert_loop_in_place(a, b, t))
714 }
715
716 fn insert_loop_in_place(&mut self, a: u32, b: u32, t: f64) -> GeopResult<()> {
718 if !(0.0 < t && t < 1.0) {
719 return Err(GeopError::new(format!(
720 "loop cut: the cut must be between the edge's ends, not at {t} of the way"
721 )));
722 }
723 let ring = self.edge_ring(a, b)?;
724 if ring.quads.is_empty() {
725 return Err(GeopError::new(format!(
726 "loop cut: edge {} has no quad on either side to cut across",
727 edge_key(a, b)
728 )));
729 }
730 let mut middle: HashMap<[u32; 2], u32> = HashMap::new();
731 for &(x, y) in &ring.edges {
732 let (p, q) = (self.vertex(x)?.at, self.vertex(y)?.at);
733 let id = self.fresh_id();
734 self.vertices.push(CageVertex {
735 id,
736 at: [0, 1, 2].map(|c| p[c] + t * (q[c] - p[c])),
737 });
738 middle.insert(undirected(x, y), id);
739 let sharpness = self.sharpness(x, y);
740 if sharpness > 0.0 {
741 self.set_sharpness(&[[x, y]], 0.0);
742 self.set_sharpness(&[[x, id], [id, y]], sharpness);
743 }
744 }
745 for face in &mut self.faces {
746 let n = face.vertices.len();
747 let mut vertices = Vec::with_capacity(n + 2);
748 for k in 0..n {
749 let (x, y) = (face.vertices[k], face.vertices[(k + 1) % n]);
750 vertices.push(x);
751 if let Some(&m) = middle.get(&undirected(x, y)) {
752 vertices.push(m);
753 }
754 }
755 face.vertices = vertices;
756 }
757 for f in ring.quads {
758 let vertices = self.faces[f].vertices.clone();
759 let cuts: Vec<usize> = (0..vertices.len())
760 .filter(|&i| middle.values().any(|&m| m == vertices[i]))
761 .collect();
762 let [i, j] = cuts[..] else {
763 return Err(GeopError::new(format!(
764 "loop cut: face {} is crossed by the loop {} times",
765 face_key(self.faces[f].id),
766 cuts.len()
767 )));
768 };
769 let first: Vec<u32> = vertices[i..=j].to_vec();
770 let second: Vec<u32> = vertices[j..]
771 .iter()
772 .chain(&vertices[..=i])
773 .copied()
774 .collect();
775 self.faces[f].vertices = first;
776 let id = self.fresh_id();
777 self.faces.push(CageFace {
778 id,
779 vertices: second,
780 });
781 }
782 Ok(())
783 }
784
785 pub fn edge_slide(
793 &mut self,
794 edges: &[[u32; 2]],
795 factor: f64,
796 mirror: Mirror,
797 ) -> GeopResult<()> {
798 if edges.is_empty() {
799 return Err(GeopError::new("edge slide: no edge selected"));
800 }
801 if !(-1.0 < factor && factor < 1.0) {
802 return Err(GeopError::new(format!(
803 "edge slide: the slide must stay short of the next loop, not {factor} of the way"
804 )));
805 }
806 let selected: BTreeSet<[u32; 2]> = edges.iter().map(|&[a, b]| undirected(a, b)).collect();
807 let directed = self.directed_edges();
808 let mut at: BTreeMap<u32, Vec<[u32; 2]>> = BTreeMap::new();
810 for &e in &selected {
811 at.entry(e[0]).or_default().push(e);
812 at.entry(e[1]).or_default().push(e);
813 }
814 let mut oriented: BTreeMap<[u32; 2], (u32, u32)> = BTreeMap::new();
816 for &start in &selected {
817 if oriented.contains_key(&start) {
818 continue;
819 }
820 let mut pending = vec![(start[0], start[1])];
821 while let Some((x, y)) = pending.pop() {
822 if oriented.insert(undirected(x, y), (x, y)).is_some() {
823 continue;
824 }
825 for (from, to) in [(x, y), (y, x)] {
826 let others: Vec<[u32; 2]> = at[&to]
827 .iter()
828 .copied()
829 .filter(|e| *e != undirected(x, y))
830 .collect();
831 if let [e] = others[..]
832 && !oriented.contains_key(&e)
833 {
834 let z = if e[0] == to { e[1] } else { e[0] };
835 pending.push(if from == x { (to, z) } else { (z, to) });
837 }
838 }
839 }
840 }
841 let mut target: BTreeMap<u32, u32> = BTreeMap::new();
844 for &(x, y) in oriented.values() {
845 let side = if factor > 0.0 { (x, y) } else { (y, x) };
846 let Some(&(f, k)) = directed.get(&side) else {
847 return Err(GeopError::new(format!(
848 "edge slide: edge {} has no face on that side",
849 edge_key(x, y)
850 )));
851 };
852 let face = &self.faces[f].vertices;
853 let n = face.len();
854 let pairs = [(side.1, face[(k + 2) % n]), (side.0, face[(k + n - 1) % n])];
857 for (v, across) in pairs {
858 if let Some(&other) = target.get(&v)
859 && other != across
860 {
861 return Err(GeopError::new(format!(
862 "edge slide: the faces along the chain disagree at vertex {}: it would slide to {} or to {}",
863 vertex_key(v),
864 vertex_key(other),
865 vertex_key(across)
866 )));
867 }
868 target.insert(v, across);
869 }
870 }
871 for (v, t) in target {
872 let p = self.vertex(v)?.at;
873 let q = self.vertex(t)?.at;
874 let mut to = [0, 1, 2].map(|c| p[c] + factor.abs() * (q[c] - p[c]));
875 if self.on_plane(v, mirror)? {
876 to[mirror.axis().expect("on a plane")] = 0.0;
877 }
878 self.vertex_mut(v)?.at = to;
879 }
880 Ok(())
881 }
882
883 pub fn bevel(&mut self, edges: &[[u32; 2]], width: f64, mirror: Mirror) -> GeopResult<()> {
895 self.transaction(|cage| cage.bevel_in_place(edges, width, mirror))
896 }
897
898 fn bevel_in_place(&mut self, edges: &[[u32; 2]], width: f64, mirror: Mirror) -> GeopResult<()> {
900 if edges.is_empty() {
901 return Err(GeopError::new("bevel: no edge selected"));
902 }
903 let (mesh, topology) = self.indexed()?;
904 let ids: Vec<u32> = self.vertices.iter().map(|v| v.id).collect();
905 let id_of = |i: usize| ids[i];
906 let index_of: HashMap<u32, usize> = (self.vertices.iter().enumerate())
907 .map(|(i, v)| (v.id, i))
908 .collect();
909 let mut bevelled: BTreeSet<[usize; 2]> = BTreeSet::new();
910 for &[a, b] in edges {
911 let (Some(&i), Some(&j)) = (index_of.get(&a), index_of.get(&b)) else {
912 return Err(GeopError::new(format!(
913 "bevel: the cage has no edge {}",
914 edge_key(a, b)
915 )));
916 };
917 if !topology.directed.contains_key(&(i, j)) && !topology.directed.contains_key(&(j, i))
918 {
919 return Err(GeopError::new(format!(
920 "bevel: the cage has no edge {}",
921 edge_key(a, b)
922 )));
923 }
924 if topology.is_boundary(i, j) {
925 return Err(GeopError::new(format!(
926 "bevel: edge {} is open — on the boundary or along the mirror plane — and has no second face to bevel between",
927 edge_key(a, b)
928 )));
929 }
930 bevelled.insert(undirected(i, j));
931 }
932 let faces = &mesh.faces;
933 let face_ids: Vec<u32> = self.faces.iter().map(|f| f.id).collect();
934 let normals: Vec<[f64; 3]> = (self.faces.iter())
935 .map(|f| self.normal(f))
936 .collect::<GeopResult<_>>()?;
937 let into = |(f, k): (usize, usize), w: usize| -> GeopResult<[f64; 3]> {
941 let face = &faces[f];
942 let d = sub(mesh.positions[w], mesh.positions[face[k]]);
943 let d = if face[(k + 1) % face.len()] == w {
944 d
945 } else {
946 scale(d, -1.0)
947 };
948 normalize(cross(normals[f], d)).ok_or_else(|| {
949 GeopError::new(format!("bevel: face {} has no area", face_key(face_ids[f])))
950 })
951 };
952 let mut sector_of: HashMap<(usize, usize), u32> = HashMap::new();
955 let mut sectors: BTreeMap<usize, (Vec<u32>, bool)> = BTreeMap::new();
958 let mut caps: HashMap<(usize, usize), u32> = HashMap::new();
963 let mut splits: HashMap<[usize; 2], u32> = HashMap::new();
964 let mut cap_faces: Vec<[u32; 3]> = Vec::new();
965 let mut capped: Vec<u32> = Vec::new();
966 let touched: BTreeSet<usize> = bevelled.iter().flat_map(|&[a, b]| [a, b]).collect();
967 for &v in &touched {
968 let (fan, closed) = &topology.fans[v];
969 let m = fan.len();
970 let cut = |i: usize| -> Option<usize> {
973 let w = prev(faces, fan[i]);
974 (bevelled.contains(&undirected(v, w)) || (!closed && i + 1 == m)).then_some(w)
975 };
976 let first_cut = (0..m)
977 .find(|&i| cut(i).is_some())
978 .expect("a bevelled edge at v");
979 let start = if *closed { (first_cut + 1) % m } else { 0 };
980 let mut runs: Vec<Vec<usize>> = Vec::new();
981 let mut run = Vec::new();
982 for step in 0..m {
983 let i = (start + step) % m;
984 run.push(i);
985 if cut(i).is_some() || step + 1 == m {
986 runs.push(std::mem::take(&mut run));
987 }
988 }
989 let p = |i: usize| mesh.positions[i];
990 let placed = |at: [f64; 3]| -> [f64; 3] {
991 let mut at = at;
992 if let Some(axis) = mirror.axis()
993 && p(v)[axis] == 0.0
994 {
995 at[axis] = 0.0;
996 }
997 at
998 };
999 if let [run] = &runs[..]
1000 && *closed
1001 {
1002 let (first, last) = (fan[run[0]], fan[run[run.len() - 1]]);
1007 let mut cap = [id_of(v), 0, 0];
1008 for (corner, other, slot) in
1009 [(first, prev(faces, first), 2), (last, next(faces, last), 1)]
1010 {
1011 let along = sub(p(other), p(v));
1012 let length = norm(along);
1013 if width >= length {
1014 return Err(GeopError::new(format!(
1015 "bevel: edge {} is {length} long, too short for a width of {width}",
1016 edge_key(id_of(v), id_of(other))
1017 )));
1018 }
1019 let at = add(p(v), scale(along, width / length));
1020 let id = self.fresh_id();
1021 self.vertices.push(CageVertex { id, at });
1022 caps.insert(corner, id);
1023 splits.insert(undirected(v, other), id);
1024 cap[slot] = id;
1025 }
1026 cap_faces.push(cap);
1027 capped.push(id_of(v));
1028 continue;
1029 }
1030 let mut ids = Vec::with_capacity(runs.len());
1031 for run in &runs {
1032 let first = fan[run[0]];
1033 let last = fan[run[run.len() - 1]];
1034 let before = next(faces, first);
1037 let after = prev(faces, last);
1038 let before_bevelled = bevelled.contains(&undirected(v, before));
1039 let after_bevelled = bevelled.contains(&undirected(v, after));
1040 let offset = match (before_bevelled, after_bevelled) {
1041 (true, true) => {
1042 let (d, e) = (into(first, before)?, into(last, after)?);
1043 let along = 1.0 + dot(d, e);
1044 if along <= 0.0 {
1045 return Err(GeopError::new(format!(
1046 "bevel: the bevelled edges fold back at vertex {}",
1047 vertex_key(id_of(v))
1048 )));
1049 }
1050 scale(add(d, e), width / along)
1051 }
1052 (true, false) => scale(into(first, before)?, width),
1053 (false, true) => scale(into(last, after)?, width),
1054 (false, false) => unreachable!("a sector is bounded by a bevelled edge"),
1055 };
1056 let at = placed(add(p(v), offset));
1057 let id = self.fresh_id();
1058 self.vertices.push(CageVertex { id, at });
1059 for &i in run {
1060 sector_of.insert(fan[i], id);
1061 }
1062 ids.push(id);
1063 }
1064 sectors.insert(v, (ids, *closed));
1065 }
1066 let corners: HashMap<(u32, usize), u32> = (sector_of.iter())
1068 .map(|(&(f, k), &id)| ((face_ids[f], k), id))
1069 .collect();
1070 let touched = (self.faces.iter())
1071 .filter(|face| (0..face.vertices.len()).any(|k| corners.contains_key(&(face.id, k))));
1072 self.check_faces_keep_shape("bevel", width, mirror, touched, |face, k| {
1073 (corners.get(&(face.id, k)))
1074 .map(|&id| Ok(self.vertex(id)?.at))
1075 .transpose()
1076 })?;
1077 for (f, face) in self.faces.iter_mut().enumerate() {
1080 let n = face.vertices.len();
1081 let mut vertices = Vec::with_capacity(n + 2);
1082 for (k, &v) in face.vertices.iter().enumerate() {
1083 match sector_of.get(&(f, k)) {
1084 Some(&id) => vertices.push(id),
1085 None if caps.contains_key(&(f, k)) => {}
1086 None => vertices.push(v),
1087 }
1088 let w = face.vertices[(k + 1) % n];
1089 if let Some(&cap) = splits.get(&undirected(index_of[&v], index_of[&w])) {
1090 vertices.push(cap);
1091 }
1092 }
1093 face.vertices = vertices;
1094 }
1095 let new_corner = |f: usize, k: usize| -> u32 {
1098 (sector_of.get(&(f, k)).or_else(|| caps.get(&(f, k))))
1099 .copied()
1100 .unwrap_or_else(|| id_of(faces[f][k]))
1101 };
1102 let corner = |x: usize, y: usize| -> u32 {
1103 let &(f, k) = &topology.directed[&(x, y)];
1104 new_corner(f, k)
1105 };
1106 let corner_after = |x: usize, y: usize| -> u32 {
1107 let &(f, k) = &topology.directed[&(x, y)];
1109 new_corner(f, (k + 1) % faces[f].len())
1110 };
1111 for &[a, b] in &bevelled {
1112 let strip = deduplicated(&[
1113 corner_after(a, b),
1114 corner(a, b),
1115 corner_after(b, a),
1116 corner(b, a),
1117 ]);
1118 if strip.len() >= 3 {
1119 let id = self.fresh_id();
1120 self.faces.push(CageFace {
1121 id,
1122 vertices: strip,
1123 });
1124 }
1125 }
1126 for (ids, closed) in sectors.values() {
1127 if *closed && ids.len() >= 3 {
1128 let id = self.fresh_id();
1129 self.faces.push(CageFace {
1130 id,
1131 vertices: ids.clone(),
1132 });
1133 }
1134 }
1135 for cap in cap_faces {
1136 let id = self.fresh_id();
1137 self.faces.push(CageFace {
1138 id,
1139 vertices: cap.to_vec(),
1140 });
1141 }
1142 let creases = std::mem::take(&mut self.creases);
1145 for c in creases {
1146 let [a, b] = c.edge;
1147 let (i, j) = (index_of[&a], index_of[&b]);
1148 if bevelled.contains(&undirected(i, j)) {
1149 continue;
1150 }
1151 let &(f, k) = topology
1152 .directed
1153 .get(&(i, j))
1154 .or_else(|| topology.directed.get(&(j, i)))
1155 .expect("a crease is on an edge");
1156 let n = faces[f].len();
1157 let x = sector_of
1158 .get(&(f, k))
1159 .copied()
1160 .unwrap_or(id_of(faces[f][k]));
1161 let y = sector_of
1162 .get(&(f, (k + 1) % n))
1163 .copied()
1164 .unwrap_or(id_of(faces[f][(k + 1) % n]));
1165 match splits.get(&undirected(i, j)) {
1166 Some(&cap) => self.set_sharpness(&[[x, cap], [cap, y]], c.sharpness),
1167 None => self.set_sharpness(&[[x, y]], c.sharpness),
1168 }
1169 }
1170 self.tidy();
1171 self.drop_two_edged(&capped, mirror)
1174 }
1175
1176 pub fn merge_vertices(&mut self, vertices: &BTreeSet<u32>, mirror: Mirror) -> GeopResult<()> {
1181 self.transaction(|cage| cage.merge_vertices_in_place(vertices, mirror))
1182 }
1183
1184 fn merge_vertices_in_place(
1186 &mut self,
1187 vertices: &BTreeSet<u32>,
1188 mirror: Mirror,
1189 ) -> GeopResult<()> {
1190 if vertices.len() < 2 {
1191 return Err(GeopError::new("merge: select at least two vertices"));
1192 }
1193 let kept = *vertices.first().expect("two at least");
1194 let mut centre = self.centre(vertices).ok_or_else(|| {
1195 GeopError::new(format!(
1196 "merge: the cage has no vertex {}",
1197 vertex_key(
1198 *vertices
1199 .iter()
1200 .find(|&&v| self.vertex(v).is_err())
1201 .expect("one is missing")
1202 )
1203 ))
1204 })?;
1205 if let Some(axis) = mirror.axis()
1206 && vertices
1207 .iter()
1208 .any(|&v| self.vertex(v).is_ok_and(|x| x.at[axis] == 0.0))
1209 {
1210 centre[axis] = 0.0;
1211 }
1212 self.vertex_mut(kept)?.at = centre;
1213 let to = |v: u32| if vertices.contains(&v) { kept } else { v };
1214 let mut faces = Vec::with_capacity(self.faces.len());
1215 for face in &self.faces {
1216 let cycle: Vec<u32> = face.vertices.iter().map(|&v| to(v)).collect();
1217 let cycle = deduplicated(&cycle);
1218 if cycle.len() < 3 {
1219 continue;
1220 }
1221 if let Some(v) = repeated(&cycle) {
1222 return Err(GeopError::new(format!(
1223 "merge: face {} would run through vertex {} twice",
1224 face_key(face.id),
1225 vertex_key(v)
1226 )));
1227 }
1228 faces.push(CageFace {
1229 id: face.id,
1230 vertices: cycle,
1231 });
1232 }
1233 self.faces = faces;
1234 self.rename_creases(|[a, b]| [to(a), to(b)]);
1235 self.tidy();
1236 let around: Vec<u32> = (self.faces.iter())
1238 .filter(|f| f.vertices.contains(&kept))
1239 .flat_map(|f| f.vertices.iter().copied())
1240 .collect();
1241 self.drop_two_edged(&around, mirror)
1242 }
1243
1244 fn drop_two_edged(&mut self, candidates: &[u32], mirror: Mirror) -> GeopResult<()> {
1251 let directed = self.directed_edges();
1252 let mut drop: BTreeSet<u32> = BTreeSet::new();
1253 for &v in candidates {
1254 let around: BTreeSet<u32> = (directed.keys())
1256 .filter_map(|&(x, y)| (x == v).then_some(y).or((y == v).then_some(x)))
1257 .collect();
1258 let around: Vec<u32> = around.into_iter().collect();
1259 let [y, z] = around[..] else { continue };
1260 let two_faced = if self.on_plane(v, mirror)? {
1261 self.on_plane(y, mirror)? && self.on_plane(z, mirror)?
1262 } else {
1263 directed.contains_key(&(y, v)) && directed.contains_key(&(z, v))
1264 };
1265 if two_faced {
1266 drop.insert(v);
1267 }
1268 }
1269 if !drop.is_empty() {
1270 for face in &mut self.faces {
1271 face.vertices.retain(|v| !drop.contains(v));
1272 }
1273 self.faces.retain(|f| f.vertices.len() >= 3);
1274 self.tidy();
1275 }
1276 Ok(())
1277 }
1278
1279 pub fn dissolve_edges(&mut self, edges: &[[u32; 2]], mirror: Mirror) -> GeopResult<()> {
1284 self.transaction(|cage| cage.dissolve_edges_in_place(edges, mirror))
1285 }
1286
1287 fn dissolve_edges_in_place(&mut self, edges: &[[u32; 2]], mirror: Mirror) -> GeopResult<()> {
1289 if edges.is_empty() {
1290 return Err(GeopError::new("dissolve: no edge selected"));
1291 }
1292 let mut edges: Vec<[u32; 2]> = edges.iter().map(|&[a, b]| undirected(a, b)).collect();
1293 edges.sort();
1294 edges.dedup();
1295 for [a, b] in edges {
1296 let directed = self.directed_edges();
1297 let (Some(&(f, _)), Some(&(g, _))) = (directed.get(&(a, b)), directed.get(&(b, a)))
1298 else {
1299 return Err(GeopError::new(format!(
1300 "dissolve: edge {} has one face only: delete the face instead",
1301 edge_key(a, b)
1302 )));
1303 };
1304 let first = rotated(&self.faces[f].vertices, b);
1305 let second = rotated(&self.faces[g].vertices, a);
1306 let mut merged = first;
1307 merged.extend(&second[1..second.len() - 1]);
1308 if let Some(v) = repeated(&merged) {
1309 return Err(GeopError::new(format!(
1310 "dissolve: faces {} and {} meet at vertex {} as well as along edge {}",
1311 face_key(self.faces[f].id),
1312 face_key(self.faces[g].id),
1313 vertex_key(v),
1314 edge_key(a, b)
1315 )));
1316 }
1317 let (keep, drop) = if self.faces[f].id < self.faces[g].id {
1318 (f, g)
1319 } else {
1320 (g, f)
1321 };
1322 self.faces[keep].vertices = merged;
1323 self.faces.remove(drop);
1324 self.drop_two_edged(&[a, b], mirror)?;
1325 }
1326 self.tidy();
1327 Ok(())
1328 }
1329
1330 pub fn dissolve_vertices(
1335 &mut self,
1336 vertices: &BTreeSet<u32>,
1337 mirror: Mirror,
1338 ) -> GeopResult<()> {
1339 self.transaction(|cage| cage.dissolve_vertices_in_place(vertices, mirror))
1340 }
1341
1342 fn dissolve_vertices_in_place(
1344 &mut self,
1345 vertices: &BTreeSet<u32>,
1346 mirror: Mirror,
1347 ) -> GeopResult<()> {
1348 if vertices.is_empty() {
1349 return Err(GeopError::new("dissolve: no vertex selected"));
1350 }
1351 for &v in vertices {
1352 let (mesh, topology) = self.indexed()?;
1353 let Some(i) = self.vertices.iter().position(|x| x.id == v) else {
1354 continue;
1356 };
1357 let (fan, _) = &topology.fans[i];
1358 let mut merged: Vec<u32> = Vec::new();
1359 for &(f, k) in fan {
1360 let face = &mesh.faces[f];
1361 let n = face.len();
1362 for j in 1..n - 1 {
1365 merged.push(self.vertices[face[(k + j) % n]].id);
1366 }
1367 }
1368 if !topology.fans[i].1 {
1371 let (f, k) = fan[fan.len() - 1];
1372 let n = mesh.faces[f].len();
1373 merged.push(self.vertices[mesh.faces[f][(k + n - 1) % n]].id);
1374 }
1375 let merged = deduplicated(&merged);
1376 if merged.len() < 3 {
1377 return Err(GeopError::new(format!(
1378 "dissolve: the faces around vertex {} leave no face",
1379 vertex_key(v)
1380 )));
1381 }
1382 if let Some(w) = repeated(&merged) {
1383 return Err(GeopError::new(format!(
1384 "dissolve: the faces around vertex {} meet at vertex {} too",
1385 vertex_key(v),
1386 vertex_key(w)
1387 )));
1388 }
1389 let around: BTreeSet<u32> = fan.iter().map(|&(f, _)| self.faces[f].id).collect();
1390 let keep = *around.first().expect("a vertex has a face");
1391 self.faces
1392 .retain(|face| face.id == keep || !around.contains(&face.id));
1393 self.face_mut(keep)?.vertices = merged.clone();
1394 self.drop_two_edged(&merged, mirror)?;
1395 }
1396 self.tidy();
1397 Ok(())
1398 }
1399
1400 fn face_mut(&mut self, id: u32) -> GeopResult<&mut CageFace> {
1401 self.faces
1402 .iter_mut()
1403 .find(|f| f.id == id)
1404 .ok_or_else(|| GeopError::new(format!("the cage has no face {}", face_key(id))))
1405 }
1406
1407 pub fn bridge(&mut self, a: u32, b: u32, mirror: Mirror) -> GeopResult<()> {
1414 let first = self.face(a)?.vertices.clone();
1415 let second = self.face(b)?.vertices.clone();
1416 let n = first.len();
1417 if n != second.len() {
1418 return Err(GeopError::new(format!(
1419 "bridge: face {} has {n} corners and face {} has {}: they need the same number",
1420 face_key(a),
1421 face_key(b),
1422 second.len()
1423 )));
1424 }
1425 if first.iter().any(|v| second.contains(v)) {
1426 return Err(GeopError::new(format!(
1427 "bridge: faces {} and {} share a vertex",
1428 face_key(a),
1429 face_key(b)
1430 )));
1431 }
1432 let reversed: Vec<u32> = second.iter().rev().copied().collect();
1435 let at = |v: u32| self.vertex(v).map(|x| x.at);
1436 let mut best: Option<(f64, usize)> = None;
1437 for r in 0..n {
1438 let mut total = 0.0;
1439 for i in 0..n {
1440 total += norm(sub(at(first[i])?, at(reversed[(i + r) % n])?));
1441 }
1442 if best.is_none_or(|(d, _)| total < d) {
1443 best = Some((total, r));
1444 }
1445 }
1446 let (_, r) = best.expect("a face has corners");
1447 self.faces.retain(|f| f.id != a && f.id != b);
1448 for i in 0..n {
1449 let vertices = vec![
1450 first[i],
1451 first[(i + 1) % n],
1452 reversed[(i + 1 + r) % n],
1453 reversed[(i + r) % n],
1454 ];
1455 let in_plane = mirror.axis().is_some()
1456 && (vertices.iter()).all(|&v| self.on_plane(v, mirror).unwrap_or(false));
1457 if in_plane {
1458 continue;
1459 }
1460 let id = self.fresh_id();
1461 self.faces.push(CageFace { id, vertices });
1462 }
1463 self.tidy();
1464 Ok(())
1465 }
1466
1467 pub fn fill_hole(&mut self, a: u32, b: u32, mirror: Mirror) -> GeopResult<()> {
1472 let run = self.boundary_run(a, b)?;
1473 if mirror.axis().is_some()
1474 && run
1475 .iter()
1476 .all(|&v| self.on_plane(v, mirror).unwrap_or(false))
1477 {
1478 return Err(GeopError::new(format!(
1479 "fill: the hole through edge {} lies in the mirror plane, where the mirror image closes it",
1480 edge_key(a, b)
1481 )));
1482 }
1483 let id = self.fresh_id();
1484 self.faces.push(CageFace {
1485 id,
1486 vertices: run.into_iter().rev().collect(),
1487 });
1488 Ok(())
1489 }
1490
1491 pub fn halve(&mut self, axis: usize) -> GeopResult<()> {
1497 let side = |at: [f64; 3]| at[axis];
1498 let mut crossing: HashMap<[u32; 2], u32> = HashMap::new();
1499 for [a, b] in self.edges() {
1500 let (p, q) = (self.vertex(a)?.at, self.vertex(b)?.at);
1501 if side(p) * side(q) < 0.0 {
1502 let t = side(p) / (side(p) - side(q));
1503 let mut at = [0, 1, 2].map(|c| p[c] + t * (q[c] - p[c]));
1504 at[axis] = 0.0;
1505 let id = self.fresh_id();
1506 self.vertices.push(CageVertex { id, at });
1507 crossing.insert([a, b], id);
1508 let sharpness = self.sharpness(a, b);
1509 if sharpness > 0.0 {
1510 self.set_sharpness(&[[a, id], [id, b]], sharpness);
1511 }
1512 }
1513 }
1514 let position: HashMap<u32, f64> =
1515 self.vertices.iter().map(|v| (v.id, side(v.at))).collect();
1516 let mut kept = Vec::new();
1517 for face in &self.faces {
1518 let n = face.vertices.len();
1519 let mut ring = Vec::with_capacity(n + 2);
1520 for k in 0..n {
1521 let (x, y) = (face.vertices[k], face.vertices[(k + 1) % n]);
1522 ring.push(x);
1523 if let Some(&m) = crossing.get(&undirected(x, y)) {
1524 ring.push(m);
1525 }
1526 }
1527 let at = |v: u32| position[&v];
1528 if ring.iter().all(|&v| at(v) <= 0.0) {
1529 continue;
1530 }
1531 if ring.iter().all(|&v| at(v) >= 0.0) {
1532 kept.push(CageFace {
1533 id: face.id,
1534 vertices: ring,
1535 });
1536 continue;
1537 }
1538 let m = ring.len();
1541 let first = (0..m)
1542 .find(|&i| at(ring[i]) < 0.0 && at(ring[(i + 1) % m]) >= 0.0)
1543 .expect("mixed");
1544 let run: Vec<u32> = (1..=m)
1546 .map(|j| ring[(first + j) % m])
1547 .take_while(|&v| at(v) >= 0.0)
1548 .collect();
1549 let positive = ring.iter().filter(|&&v| at(v) > 0.0).count();
1550 if run.iter().filter(|&&v| at(v) > 0.0).count() != positive || run.len() < 3 {
1551 return Err(GeopError::new(format!(
1552 "the mirror plane crosses face {} more than twice",
1553 face_key(face.id)
1554 )));
1555 }
1556 kept.push(CageFace {
1557 id: face.id,
1558 vertices: run,
1559 });
1560 }
1561 self.faces = kept;
1562 self.tidy();
1563 Ok(())
1564 }
1565
1566 pub fn unmirrored(&self, mirror: Mirror) -> GeopResult<Cage> {
1569 if mirror == Mirror::None {
1570 return Ok(self.clone());
1571 }
1572 let mesh = Mesh::new(self, mirror)?;
1573 let mut cage = self.clone();
1574 let mut ids: Vec<u32> = Vec::with_capacity(mesh.positions.len());
1575 for (i, name) in mesh.vertex_names.iter().enumerate() {
1576 match name.strip_suffix('m') {
1577 Some(_) => {
1578 let id = cage.fresh_id();
1579 cage.vertices.push(CageVertex {
1580 id,
1581 at: mesh.positions[i],
1582 });
1583 ids.push(id);
1584 }
1585 None => ids.push(name[1..].parse().expect("a vertex key")),
1586 }
1587 }
1588 for (f, name) in mesh.face_names.iter().enumerate() {
1589 if name.ends_with('m') {
1590 let id = cage.fresh_id();
1591 cage.faces.push(CageFace {
1592 id,
1593 vertices: mesh.faces[f].iter().map(|&v| ids[v]).collect(),
1594 });
1595 }
1596 }
1597 for &[a, b] in &mesh.sharp {
1598 cage.set_sharpness(&[[ids[a], ids[b]]], 1.0);
1599 }
1600 for (&[a, b], &s) in &mesh.soft {
1601 cage.set_sharpness(&[[ids[a], ids[b]]], s);
1602 }
1603 Ok(cage)
1604 }
1605}