Skip to main content

geop_ops_subd/
cage.rs

1//! [`Cage`]: the control mesh a subdivision surface is the limit of, as a
2//! step's arguments hold it — vertices, faces through them, and the edges
3//! creased sharp — and [`Mesh`], the same mesh indexed, mirrored if asked
4//! to, and checked to be one a limit surface can be built from.
5//!
6//! Every vertex and face has an id of its own, kept through every edit, so
7//! that what the limit surface is made of can be named after the cage
8//! element it comes from (see [`crate::limit`]): `v3` and `f7`, and, on the
9//! mirrored half, `v3m` and `f7m`.
10
11use std::collections::{BTreeMap, BTreeSet, HashMap};
12
13use geop_core_math::{
14    geop_error::{GeopError, GeopResult},
15    solvers::walk::{Take, flood, walk},
16};
17use serde::{Deserialize, Serialize};
18
19/// A vertex of the cage: where it is.
20#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
21pub struct CageVertex {
22    pub id: u32,
23    pub at: [f64; 3],
24}
25
26/// A face of the cage: its vertices, counter-clockwise seen from outside.
27#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
28pub struct CageFace {
29    pub id: u32,
30    pub vertices: Vec<u32>,
31}
32
33/// An edge creased: kept sharp through the limit surface at a `sharpness`
34/// of one, bent a little less than smooth below it.
35#[derive(Clone, Copy, Debug, PartialEq, Serialize, Deserialize)]
36pub struct Crease {
37    /// Its two vertices, the smaller id first.
38    pub edge: [u32; 2],
39    /// In `(0, 1]`: one is sharp — a corner in the limit surface — and
40    /// below it the crease is *soft*: the cage is pulled to the edge by
41    /// that fraction in the first subdivision step and smooth after, so
42    /// the surface rounds off tighter there without a corner.
43    pub sharpness: f64,
44}
45
46/// A control cage: a mesh of faces of any number of sides — quads mostly,
47/// triangles and n-gons allowed — with the edges named in `creases` kept
48/// sharp. Ids are unique across vertices and faces, and `next_id` is the
49/// next one no element has had.
50#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
51pub struct Cage {
52    pub vertices: Vec<CageVertex>,
53    pub faces: Vec<CageFace>,
54    /// The edges creased, in order of their vertices.
55    #[serde(default)]
56    pub creases: Vec<Crease>,
57    pub next_id: u32,
58}
59
60/// The plane the cage is mirrored in, if any: `X` is the plane `x = 0` of
61/// the world. The cage holds one half; the other is its mirror image,
62/// joined to it along the vertices that lie on the plane.
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
64#[serde(rename_all = "snake_case")]
65pub enum Mirror {
66    #[default]
67    None,
68    X,
69    Y,
70    Z,
71}
72
73impl Mirror {
74    /// The coordinate the plane zeroes.
75    pub fn axis(self) -> Option<usize> {
76        match self {
77            Mirror::None => None,
78            Mirror::X => Some(0),
79            Mirror::Y => Some(1),
80            Mirror::Z => Some(2),
81        }
82    }
83}
84
85/// The name of the vertex `id`: `v3`.
86pub fn vertex_key(id: u32) -> String {
87    format!("v{id}")
88}
89
90/// The name of the face `id`: `f7`.
91pub fn face_key(id: u32) -> String {
92    format!("f{id}")
93}
94
95/// The vertex after corner `k` of face `f`, the way the face runs.
96pub fn next(faces: &[Vec<usize>], (f, k): (usize, usize)) -> usize {
97    faces[f][(k + 1) % faces[f].len()]
98}
99
100/// The vertex before corner `k` of face `f`, the way the face runs.
101pub fn prev(faces: &[Vec<usize>], (f, k): (usize, usize)) -> usize {
102    let n = faces[f].len();
103    faces[f][(k + n - 1) % n]
104}
105
106/// The edge between `a` and `b`, either way round: the smaller first.
107pub fn undirected<T: Ord + Copy>(a: T, b: T) -> [T; 2] {
108    [a.min(b), a.max(b)]
109}
110
111/// The name of the edge between vertices `a` and `b`: `e3-5`, the smaller
112/// id first.
113pub fn edge_key(a: u32, b: u32) -> String {
114    let [a, b] = undirected(a, b);
115    format!("e{a}-{b}")
116}
117
118impl Cage {
119    /// The vertex `id`.
120    pub fn vertex(&self, id: u32) -> GeopResult<&CageVertex> {
121        self.vertices
122            .iter()
123            .find(|v| v.id == id)
124            .ok_or_else(|| GeopError::new(format!("the cage has no vertex {}", vertex_key(id))))
125    }
126
127    pub fn vertex_mut(&mut self, id: u32) -> GeopResult<&mut CageVertex> {
128        self.vertices
129            .iter_mut()
130            .find(|v| v.id == id)
131            .ok_or_else(|| GeopError::new(format!("the cage has no vertex {}", vertex_key(id))))
132    }
133
134    /// The face `id`.
135    pub fn face(&self, id: u32) -> GeopResult<&CageFace> {
136        self.faces
137            .iter()
138            .find(|f| f.id == id)
139            .ok_or_else(|| GeopError::new(format!("the cage has no face {}", face_key(id))))
140    }
141
142    /// An id no element has had yet.
143    pub fn fresh_id(&mut self) -> u32 {
144        let id = self.next_id;
145        self.next_id += 1;
146        id
147    }
148
149    /// Every edge, each by its two vertices, the smaller id first, in order.
150    pub fn edges(&self) -> BTreeSet<[u32; 2]> {
151        self.faces
152            .iter()
153            .flat_map(|f| {
154                let n = f.vertices.len();
155                (0..n).map(move |k| {
156                    let (a, b) = (f.vertices[k], f.vertices[(k + 1) % n]);
157                    undirected(a, b)
158                })
159            })
160            .collect()
161    }
162
163    /// How sharp the edge between `a` and `b` is creased: zero for a smooth
164    /// edge, one for a sharp one (see [`Crease`]).
165    pub fn sharpness(&self, a: u32, b: u32) -> f64 {
166        let edge = undirected(a, b);
167        (self.creases.iter())
168            .find(|c| c.edge == edge)
169            .map_or(0.0, |c| c.sharpness)
170    }
171
172    /// Whether the edge between `a` and `b` is creased at all.
173    pub fn is_crease(&self, a: u32, b: u32) -> bool {
174        self.sharpness(a, b) > 0.0
175    }
176
177    /// The face that runs along each directed edge `(a, b)`, by its index
178    /// and the corner of it at `a`. Two faces running the same way along
179    /// an edge are turned against each other: the later one is kept, and
180    /// [`Topology::new`] refuses the cage by name.
181    pub fn directed_edges(&self) -> HashMap<(u32, u32), (usize, usize)> {
182        let mut directed = HashMap::new();
183        for (f, face) in self.faces.iter().enumerate() {
184            let n = face.vertices.len();
185            for k in 0..n {
186                directed.insert((face.vertices[k], face.vertices[(k + 1) % n]), (f, k));
187            }
188        }
189        directed
190    }
191
192    /// The cage indexed and its topology checked, for an edit that walks
193    /// around vertices: vertex `i` of the mesh is `self.vertices[i]`, face
194    /// `f` is `self.faces[f]`.
195    pub fn indexed(&self) -> GeopResult<(Mesh, Topology)> {
196        let mesh = Mesh::new(self, Mirror::None)?;
197        let topology = Topology::new(
198            mesh.positions.len(),
199            &mesh.faces,
200            &mesh.vertex_names,
201            &mesh.face_names,
202        )?;
203        Ok((mesh, topology))
204    }
205}
206
207/// The cage as the limit surface is built from it: indexed, the mirror
208/// image added, every element named.
209#[derive(Clone, Debug)]
210pub struct Mesh {
211    /// Per vertex: its name (`v3`, `v3m`) and where it is.
212    pub vertex_names: Vec<String>,
213    pub positions: Vec<[f64; 3]>,
214    /// Per face: its name (`f7`, `f7m`) and its vertices, counter-clockwise
215    /// seen from outside.
216    pub face_names: Vec<String>,
217    pub faces: Vec<Vec<usize>>,
218    /// The edges kept sharp, the smaller index first.
219    pub sharp: BTreeSet<[usize; 2]>,
220    /// The edges creased soft, by their sharpness in `(0, 1)` (see
221    /// [`Crease`]).
222    pub soft: BTreeMap<[usize; 2], f64>,
223}
224
225impl Mesh {
226    /// `cage`, and, if `mirror` names a plane, its mirror image in it,
227    /// joined to it at the vertices on the plane. Fails, naming the
228    /// elements, for a cage no limit surface can be built from (see
229    /// [`Topology::new`]), and for a mirrored one with a face lying in the
230    /// plane, or reaching across it.
231    pub fn new(cage: &Cage, mirror: Mirror) -> GeopResult<Self> {
232        let mut index: HashMap<u32, usize> = HashMap::new();
233        let mut mesh = Mesh {
234            vertex_names: Vec::new(),
235            positions: Vec::new(),
236            face_names: Vec::new(),
237            faces: Vec::new(),
238            sharp: BTreeSet::new(),
239            soft: BTreeMap::new(),
240        };
241        for v in &cage.vertices {
242            if !v.at.iter().all(|c| c.is_finite()) {
243                return Err(GeopError::new(format!(
244                    "vertex {} is not at a finite point: {:?}",
245                    vertex_key(v.id),
246                    v.at
247                )));
248            }
249            if index.insert(v.id, mesh.positions.len()).is_some() {
250                return Err(GeopError::new(format!(
251                    "the cage has two vertices {}",
252                    vertex_key(v.id)
253                )));
254            }
255            mesh.vertex_names.push(vertex_key(v.id));
256            mesh.positions.push(v.at);
257        }
258        let mut face_ids = BTreeSet::new();
259        for f in &cage.faces {
260            if !face_ids.insert(f.id) {
261                return Err(GeopError::new(format!(
262                    "the cage has two faces {}",
263                    face_key(f.id)
264                )));
265            }
266            let vertices = f
267                .vertices
268                .iter()
269                .map(|id| {
270                    index.get(id).copied().ok_or_else(|| {
271                        GeopError::new(format!(
272                            "face {} runs through vertex {}, which the cage does not have",
273                            face_key(f.id),
274                            vertex_key(*id)
275                        ))
276                    })
277                })
278                .collect::<GeopResult<Vec<_>>>()?;
279            mesh.face_names.push(face_key(f.id));
280            mesh.faces.push(vertices);
281        }
282        for crease in &cage.creases {
283            let [a, b] = crease.edge;
284            if !(crease.sharpness > 0.0 && crease.sharpness <= 1.0) {
285                return Err(GeopError::new(format!(
286                    "crease {} has a sharpness of {}: it must be above zero and at most one",
287                    edge_key(a, b),
288                    crease.sharpness
289                )));
290            }
291            match (index.get(&a), index.get(&b)) {
292                (Some(&i), Some(&j)) if crease.sharpness == 1.0 => {
293                    mesh.sharp.insert(undirected(i, j));
294                }
295                (Some(&i), Some(&j)) => {
296                    mesh.soft.insert(undirected(i, j), crease.sharpness);
297                }
298                _ => {
299                    return Err(GeopError::new(format!(
300                        "crease {} runs between vertices the cage does not have",
301                        edge_key(a, b)
302                    )));
303                }
304            }
305        }
306        if let Some(axis) = mirror.axis() {
307            mesh.add_mirror_image(axis)?;
308        }
309        Ok(mesh)
310    }
311
312    /// Adds the mirror image in the plane where coordinate `axis` is zero,
313    /// sharing the vertices exactly on it.
314    fn add_mirror_image(&mut self, axis: usize) -> GeopResult<()> {
315        let plane = ["x", "y", "z"][axis];
316        let n = self.positions.len();
317        let mut image = vec![0; n];
318        for (i, mirrored) in image.iter_mut().enumerate() {
319            let mut at = self.positions[i];
320            if at[axis] == 0.0 {
321                *mirrored = i;
322                continue;
323            }
324            if at[axis] < 0.0 {
325                return Err(GeopError::new(format!(
326                    "vertex {} is on the far side of the mirror plane {plane} = 0: the cage holds the half where {plane} >= 0",
327                    self.vertex_names[i]
328                )));
329            }
330            at[axis] = -at[axis];
331            *mirrored = self.positions.len();
332            self.vertex_names.push(format!("{}m", self.vertex_names[i]));
333            self.positions.push(at);
334        }
335        let faces = self.faces.len();
336        for f in 0..faces {
337            if self.faces[f].iter().all(|&v| image[v] == v) {
338                return Err(GeopError::new(format!(
339                    "face {} lies in the mirror plane {plane} = 0",
340                    self.face_names[f]
341                )));
342            }
343            let mirrored: Vec<usize> = self.faces[f].iter().rev().map(|&v| image[v]).collect();
344            self.face_names.push(format!("{}m", self.face_names[f]));
345            self.faces.push(mirrored);
346        }
347        let sharp: Vec<[usize; 2]> = self.sharp.iter().copied().collect();
348        for [a, b] in sharp {
349            let (i, j) = (image[a], image[b]);
350            self.sharp.insert(undirected(i, j));
351        }
352        let soft: Vec<([usize; 2], f64)> = self.soft.iter().map(|(e, s)| (*e, *s)).collect();
353        for ([a, b], s) in soft {
354            let (i, j) = (image[a], image[b]);
355            self.soft.insert(undirected(i, j), s);
356        }
357        Ok(())
358    }
359
360    /// The name of the edge between vertices `a` and `b`, as a cage
361    /// element: `e3-5`, or, on the mirrored half, `e3m-5m`.
362    pub fn edge_name(&self, a: usize, b: usize) -> String {
363        let [a, b] = undirected(a, b);
364        format!(
365            "e{}-{}",
366            &self.vertex_names[a][1..],
367            &self.vertex_names[b][1..]
368        )
369    }
370}
371
372/// How the faces of a mesh fit together: which face runs along each
373/// directed edge, and the faces around each vertex in order.
374#[derive(Clone, Debug)]
375pub struct Topology {
376    /// `(a, b)`: the face that runs from `a` to `b`, and the corner of it
377    /// at `a`.
378    pub directed: HashMap<(usize, usize), (usize, usize)>,
379    /// Per vertex: the faces around it, each with its corner there, in
380    /// order — each next one across the edge to the previous one's
381    /// preceding corner — and whether they close up all round, rather than
382    /// meeting the mesh's boundary on both ends.
383    pub fans: Vec<(Vec<(usize, usize)>, bool)>,
384}
385
386impl Topology {
387    /// The topology of `faces` over `vertex_count` vertices. Fails, naming
388    /// the elements with `vertex_names` and `face_names`, for a mesh no
389    /// limit surface can be built from: a face of fewer than three
390    /// vertices, or through one twice; faces turned against each other
391    /// (an edge run the same way twice), or more than two meeting at an
392    /// edge; faces around a vertex that do not make one fan; a vertex of no
393    /// face; an inner vertex of fewer than three faces.
394    pub fn new(
395        vertex_count: usize,
396        faces: &[Vec<usize>],
397        vertex_names: &[String],
398        face_names: &[String],
399    ) -> GeopResult<Self> {
400        let mut directed = HashMap::new();
401        let mut corners: Vec<Vec<(usize, usize)>> = vec![Vec::new(); vertex_count];
402        for (f, face) in faces.iter().enumerate() {
403            let n = face.len();
404            if n < 3 {
405                return Err(GeopError::new(format!(
406                    "face {} has {n} vertices: a face needs at least three",
407                    face_names[f]
408                )));
409            }
410            for k in 0..n {
411                let (a, b) = (face[k], face[(k + 1) % n]);
412                if face[..k].contains(&a) {
413                    return Err(GeopError::new(format!(
414                        "face {} runs through vertex {} twice",
415                        face_names[f], vertex_names[a]
416                    )));
417                }
418                if let Some((g, _)) = directed.insert((a, b), (f, k)) {
419                    return Err(GeopError::new(format!(
420                        "faces {} and {} both run from {} to {}: they are turned against each other, or more than two faces meet at that edge",
421                        face_names[g], face_names[f], vertex_names[a], vertex_names[b]
422                    )));
423                }
424                corners[a].push((f, k));
425            }
426        }
427        let mut fans = Vec::with_capacity(vertex_count);
428        for (v, around) in corners.iter().enumerate() {
429            if around.is_empty() {
430                return Err(GeopError::new(format!(
431                    "vertex {} belongs to no face",
432                    vertex_names[v]
433                )));
434            }
435            // A fan that meets the boundary starts at the face with no
436            // neighbour across its edge to the next corner.
437            let start = around
438                .iter()
439                .find(|&&c| !directed.contains_key(&(next(faces, c), v)))
440                .copied();
441            let closed = start.is_none();
442            // Round the vertex from corner to corner across the edge
443            // between them, until back at the first, at the boundary, or at
444            // a corner already passed: every corner there is is one of
445            // `around`.
446            let fan = walk(start.unwrap_or(around[0]), |&last| {
447                Ok::<_, GeopError>(directed.get(&(v, prev(faces, last))).copied())
448            })?
449            .nodes;
450            if fan.len() != around.len() {
451                return Err(GeopError::new(format!(
452                    "the faces around vertex {} do not make one fan: the surface pinches there",
453                    vertex_names[v]
454                )));
455            }
456            if closed && fan.len() < 3 {
457                return Err(GeopError::new(format!(
458                    "inner vertex {} belongs to {} faces: it needs at least three",
459                    vertex_names[v],
460                    fan.len()
461                )));
462            }
463            fans.push((fan, closed));
464        }
465        Ok(Self { directed, fans })
466    }
467
468    /// Whether only one face runs along the edge between `a` and `b`.
469    pub fn is_boundary(&self, a: usize, b: usize) -> bool {
470        self.directed.contains_key(&(a, b)) != self.directed.contains_key(&(b, a))
471    }
472
473    /// Every edge, the smaller index first, with its faces.
474    pub fn edges(&self) -> BTreeMap<[usize; 2], Vec<usize>> {
475        let mut edges: BTreeMap<[usize; 2], Vec<usize>> = BTreeMap::new();
476        for (&(a, b), &(f, _)) in &self.directed {
477            edges.entry(undirected(a, b)).or_default().push(f);
478        }
479        for faces in edges.values_mut() {
480            faces.sort();
481        }
482        edges
483    }
484}
485
486impl Mesh {
487    /// The mesh's topology, checked (see [`Topology::new`]), and checked to
488    /// be in one piece.
489    pub fn topology(&self) -> GeopResult<Topology> {
490        if self.faces.is_empty() {
491            return Err(GeopError::new("the cage has no faces"));
492        }
493        let topology = Topology::new(
494            self.positions.len(),
495            &self.faces,
496            &self.vertex_names,
497            &self.face_names,
498        )?;
499        // From vertex 0 to every vertex of a face around one reached.
500        let mut seen = vec![false; self.positions.len()];
501        let reached = flood([0], Take::Newest, |&v| {
502            Ok::<_, GeopError>(
503                (topology.fans[v].0.iter())
504                    .flat_map(|&(f, _)| self.faces[f].iter().copied())
505                    .collect(),
506            )
507        })?;
508        for v in reached {
509            seen[v] = true;
510        }
511        if let Some(apart) = seen.iter().position(|s| !s) {
512            return Err(GeopError::new(format!(
513                "the cage falls apart: vertex {} is not connected to vertex {}",
514                self.vertex_names[apart], self.vertex_names[0]
515            )));
516        }
517        for &[a, b] in self.sharp.iter().chain(self.soft.keys()) {
518            if !topology.directed.contains_key(&(a, b)) && !topology.directed.contains_key(&(b, a))
519            {
520                return Err(GeopError::new(format!(
521                    "crease {} is no edge of the cage",
522                    self.edge_name(a, b)
523                )));
524            }
525        }
526        Ok(topology)
527    }
528}