Skip to main content

geop_ops_subd/
edit.rs

1//! What a cage starts as — a box, a cylinder, a sphere, a plane — and the
2//! edits that shape it: moving, turning and scaling its elements,
3//! extruding and insetting faces, bevelling edges, cutting and sliding
4//! edge loops, merging and dissolving, bridging two faces, filling a hole,
5//! creasing edges, deleting faces, and cutting it in half at a mirror
6//! plane or making the mirrored half its own.
7//!
8//! An edit keeps the id of every element it does not take away, and gives
9//! what it adds ids no element has had, so that names built from them stay
10//! put (see [`crate::cage`]). Every edit is a function of the cage and its
11//! arguments alone, its order fixed by the ids, so that the same edit of
12//! the same cage makes the same cage.
13
14use 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/// An element of a cage, as a selection names it (see
27/// [`crate::cage::vertex_key`], [`crate::cage::edge_key`],
28/// [`crate::cage::face_key`]).
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum Element {
31    Vertex(u32),
32    /// By its two vertices, the smaller id first.
33    Edge(u32, u32),
34    Face(u32),
35}
36
37impl Element {
38    /// The element `key` names: `v3`, `e3-5`, `f7`.
39    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    /// The key that names the element.
54    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
63/// A cage built from `points` and faces through them by index, ids given
64/// in that order: vertices first.
65fn 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
87/// Edges each directed the way a face runs along it.
88type DirectedEdges = BTreeSet<(u32, u32)>;
89
90/// The vertices of `face` as a cycle starting at `at`, which it runs
91/// through.
92fn 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
97/// `cycle` with each run of equal neighbours — around the end too — kept
98/// once.
99fn 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
112/// The vertex `cycle` runs through twice, if any.
113fn 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    /// The same cage with `next_id` past every id in it.
120    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    /// A box around the origin, `size` long along each axis: eight
131    /// vertices, six quads.
132    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    /// A cylinder around the `z` axis, centred on the origin: `segments`
156    /// quads around, two rows high, closed by an n-gon at each end.
157    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                // On an axis, exactly: a vertex a rounding off the plane
164                // `y = 0` is not on it for a mirror, and a sliver away from
165                // the one that is.
166                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    /// A sphere-like cage around the origin: a cube of 2 x 2 quads a side,
190    /// its vertices pushed out onto the sphere of `radius`.
191    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                // `u x v` points out of the cube face.
198                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    /// A flat square in the `z = 0` plane around the origin, `size` wide,
228    /// of `n` x `n` quads facing `+z`: an open cage, whose limit is a
229    /// sheet.
230    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    /// Whether the cage has the element `element`.
249    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    /// The vertices of the elements `keys` name, by id.
258    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    /// The centre of the vertices `vertices`: their average.
281    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    /// Whether vertex `v` lies on the plane of `mirror`, where the mirror
290    /// image joins the cage.
291    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    /// The vertices `vertices` moved to where `f` takes them — those on the
297    /// plane of `mirror` kept on it, where the mirror image joins them.
298    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    /// Makes the edit `edit` of the cage whole or not at all: a refusal
316    /// part way through leaves the cage as it was.
317    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    /// Creases the edges `edges` at `sharpness` (see [`Crease`]): one is
325    /// sharp, zero or less smooths them again, and more than one is sharp.
326    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    /// Renames the vertices of every crease by `to`, dropping a crease
341    /// whose two vertices became one.
342    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    /// Forgets vertices no face runs through any more, and creases of
355    /// edges no face has any more.
356    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    /// Deletes the faces `faces`, and every vertex and crease that leaves
368    /// without a face.
369    pub fn delete_faces(&mut self, faces: &BTreeSet<u32>) {
370        self.faces.retain(|f| !faces.contains(&f.id));
371        self.tidy();
372    }
373
374    /// The faces any of the edges `edges` is on, by id.
375    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    /// The faces any of the vertices `vertices` is a corner of, by id.
385    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    /// The orientation of the faces `faces`, for moving them along it: `z`
393    /// along their normals together, `x` along the first one's first edge,
394    /// both unit and square to each other. `None` for no faces, or faces
395    /// whose normals cancel out.
396    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    /// The outward normal of face `face`, by Newell's method: as long as
412    /// its area.
413    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    /// The unit direction from the edge `(a, b)` of face `face` into the
428    /// face, square to the edge in the face's plane: to the left of the
429    /// edge the way the face runs. `None` for a face of no area.
430    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    /// The edges of the faces `faces` with a face of the region on one side
441    /// only, directed the way the region's face runs; and every directed
442    /// edge of the cage.
443    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    /// Moves the region of faces `faces` by `offset` per vertex, keeping
465    /// the faces' ids, and joins each edge on the region's rim to its moved
466    /// copy by a new quad. A vertex inside the region just moves; one on
467    /// its rim is left behind for the faces around, and a new one moves.
468    /// With a `mirror`, what is on its plane stays on it, and an open edge
469    /// along the plane gets no quad, which would lie in it. A crease whose
470    /// edge only the region had moves with it; one on its rim stays. The
471    /// name is for the error messages.
472    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 &region {
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    /// Extrudes the faces `faces` `distance` along their normals (see
550    /// [`Cage::offset_region`]).
551    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    /// Insets the faces `faces`: their rim moves `thickness` inwards, in
580    /// the faces' own planes, and a new ring of quads fills the gap to where
581    /// the rim was (see [`Cage::offset_region`]). A vertex inside the region
582    /// stays.
583    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                    // The mitre: `thickness` from both rim edges.
612                    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    /// Refuses, by name, an edit `name` by `amount` that folds one of
650    /// `faces` over itself — the face is too small for it: a corner moved
651    /// out across one of the face's other sides, or a side turned round,
652    /// its two corners moved past each other. `corner_at` says where
653    /// corner `k` of a face goes, `None` for one that stays. A corner may
654    /// slide along its own two sides, and one on the plane of `mirror`
655    /// along a side in that plane. The check is exact for a convex face.
656    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                    // A corner may slide along its own two sides, and along
687                    // the mirror plane.
688                    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    /// Cuts a new edge loop across the edge between `a` and `b`: through
709    /// every edge of its ring (see [`Cage::edge_ring`]) at the fraction `t`
710    /// of the way along it from `a`'s side, splitting each quad of the
711    /// ring in two. The halves of a creased edge keep its crease.
712    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    /// [`Cage::insert_loop`], in place.
717    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    /// Slides the vertices of the chain of edges `edges` along the edges
786    /// leaving the chain, by the fraction `factor` of their length: towards
787    /// the side the faces running along the chain, oriented from its first
788    /// edge, lie on for a positive factor, the other side for a negative
789    /// one. Each vertex needs one edge to slide along on that side, which
790    /// the quads of the chain's faces give it: a vertex where the chain
791    /// branches, or where its faces disagree, is refused by name.
792    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        // The selected edges at each vertex.
809        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        // Each chain oriented from its lowest edge, as directed edges.
815        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                        // Onwards from `to` if we came in through `from`.
836                        pending.push(if from == x { (to, z) } else { (z, to) });
837                    }
838                }
839            }
840        }
841        // Where each vertex slides to: the vertex across from it in the
842        // face on the chosen side of each of its chain edges.
843        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            // The face runs `side.0 -> side.1`: across from `side.1` is the
855            // vertex after it, across from `side.0` the one before.
856            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    /// Bevels the edges `edges`: each becomes a strip of one quad, `width`
884    /// wide, between the two faces along it, and each vertex where three or
885    /// more bevelled edges meet a face of its own. A vertex is split into
886    /// one vertex per *sector* — the faces around it between two bevelled
887    /// edges — placed `width` from both edges in the sector's first and
888    /// last face. At a vertex with one bevelled edge the strip ends on the
889    /// faces beside it: its end vertices lie `width` along the two other
890    /// edges of the edge's faces there, which split there, and a triangle
891    /// caps the strip between them and the vertex — which goes if that
892    /// leaves it between two faces, as at a box's corner. Edges on the
893    /// boundary cannot be bevelled.
894    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    /// [`Cage::bevel`], in place.
899    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        // The unit direction from the edge between corner `(f, k)` and `w`
938        // into face `f`, square to the edge in its plane (see
939        // [`Cage::inward`]).
940        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        // Per touched vertex, per corner `(f, k)` of it: the new vertex of
953        // the sector that corner is in.
954        let mut sector_of: HashMap<(usize, usize), u32> = HashMap::new();
955        // Per touched vertex: its sector vertices in fan order, and whether
956        // the fan closes.
957        let mut sectors: BTreeMap<usize, (Vec<u32>, bool)> = BTreeMap::new();
958        // Per corner `(f, k)` of a vertex with one bevelled edge: the cap
959        // vertex that takes the corner's place in that face, on the face's
960        // other edge there, which it splits (`splits`, by the edge's
961        // vertices).
962        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            // Between `fan[i]` and `fan[i + 1]` lies the edge to `fan[i]`'s
971            // previous corner; an open fan is cut at both ends too.
972            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                // One bevelled edge all round: the strip is capped at the
1003                // vertex. The run starts with the face running from `v`
1004                // along the edge, and ends with the one running back; the
1005                // cap vertices lie on those faces' other edges at `v`.
1006                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                // The bevelled edge before the run — the one to the first
1035                // corner's next vertex — and after it.
1036                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        // A width a face is too small for folds it at a sector vertex.
1067        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        // The faces shrink to their sector vertices; a capped corner gives
1078        // way to its cap, which every face along the split edge takes in.
1079        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        // A strip per bevelled edge, a face per vertex of three sectors or
1096        // more, and the caps.
1097        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            // The corner of `y` in the face running `x -> y`.
1108            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        // A crease of an edge not bevelled follows its corners; one of a
1143        // bevelled edge is gone with it.
1144        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        // A capped vertex of three edges is left between the cap and one
1172        // face: it goes, and the cap with it.
1173        self.drop_two_edged(&capped, mirror)
1174    }
1175
1176    /// Merges the vertices `vertices` into one at their centre — on the
1177    /// mirror plane if any of them was — keeping the lowest id. A face left
1178    /// with fewer than three corners goes; one that would run through the
1179    /// merged vertex twice is refused by name.
1180    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    /// [`Cage::merge_vertices`], in place.
1185    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        // A corner the merge left between two faces alone goes.
1237        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    /// Takes the vertices among `candidates` that are left between two
1245    /// faces only, with two edges, out of those faces: no corner any more,
1246    /// such a vertex lies along an edge, where the limit surface has no
1247    /// use for it. On the plane of `mirror`, a vertex of one face whose two
1248    /// edges both run along the plane is that: the face and its image are
1249    /// its two faces.
1250    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            // Its neighbours, whichever way the faces run the edges.
1255            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    /// Dissolves the edges `edges`: the two faces along each become one,
1280    /// keeping the lower id, and an end left between those two faces alone
1281    /// goes with it. An edge with one face, or two faces that meet along
1282    /// more than that edge, is refused by name.
1283    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    /// [`Cage::dissolve_edges`], in place.
1288    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    /// Dissolves the vertices `vertices`: the faces around each become one
1331    /// face, keeping the lowest id, and the vertex goes — as does any
1332    /// neighbour of it left between two faces alone. Faces around it that
1333    /// meet elsewhere than at it are refused by name.
1334    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    /// [`Cage::dissolve_vertices`], in place.
1343    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                // Dissolving a neighbour took it with it.
1355                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                // From the corner after `v` up to the one before it, that
1363                // one left to the next face of the fan.
1364                for j in 1..n - 1 {
1365                    merged.push(self.vertices[face[(k + j) % n]].id);
1366                }
1367            }
1368            // An open fan ends at the boundary: its last face's last
1369            // corner closes the run.
1370            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    /// Bridges the faces `a` and `b`: both go, and a ring of quads joins
1408    /// their rims, each corner of `a` to the nearest corner of `b` going
1409    /// the other way round — a tunnel through the body, or a handle between
1410    /// two of its faces. The faces need the same number of corners. With a
1411    /// `mirror`, a quad that would lie in its plane is the seam to the
1412    /// mirror image, and is left out.
1413    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        // `b` seen from `a`'s side runs the other way round; of its
1433        // rotations, the one whose corners lie nearest `a`'s.
1434        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    /// Fills the hole the open edge between `a` and `b` is on with one face
1468    /// through all the vertices around it (see [`Cage::boundary_run`]).
1469    /// With a `mirror`, a hole whose rim lies in the plane is the seam to
1470    /// the mirror image, and is refused.
1471    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    /// Cuts the cage at the plane where coordinate `axis` is zero and keeps
1492    /// the half where it is positive, as a mirrored cage holds it. Every
1493    /// edge reaching across the plane is split where it crosses, the new
1494    /// vertex exactly on the plane. A face crossing the plane more than
1495    /// twice is refused, by name.
1496    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            // Mixed: keep the one run of vertices on the positive side,
1539            // from one vertex on the plane to the next.
1540            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            // `ring[first]` is on the negative side: the run ends by it.
1545            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    /// The whole cage the mirrored half stands for, as a cage of its own:
1567    /// the half keeps its ids, the mirror image gets new ones.
1568    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}