Skip to main content

axiolid_refine/
subdivide.rs

1//! Subdivision surfaces: Loop on triangle meshes, Catmull-Clark on
2//! polygon meshes.
3//!
4//! Unlike [`crate::refine`], which splits triangles and leaves the surface
5//! where it was, a subdivision *scheme* also moves every vertex by a fixed
6//! stencil (a mask) of its neighbours. Repeating it converges to a smooth
7//! limit surface: C2 except at extraordinary vertices, where it is C1.
8//! Each level is a new [`HalfedgeMesh`]; the input is not modified.
9//!
10//! # Masks
11//!
12//! **Loop** (Loop 1987), triangles only, 1-to-4 split:
13//!
14//! - new edge vertex: `3/8 (a + b) + 1/8 (c + d)`, `c`, `d` the corners
15//!   opposite the edge;
16//! - old vertex of valence `n`: `(1 - n beta) v + beta sum(neighbours)` with
17//!   Loop's `beta = (5/8 - (3/8 + 1/4 cos(2 pi / n))^2) / n`.
18//!
19//! **Catmull-Clark** (Catmull and Clark 1978), any polygons, each `k`-gon
20//! split into `k` quads:
21//!
22//! - face vertex: the centroid of the corners;
23//! - new edge vertex: `(a + b + f1 + f2) / 4` with the two face vertices;
24//! - old vertex of valence `n`: `(Q + 2 R + (n - 3) v) / n`, `Q` the mean of
25//!   the adjacent face vertices and `R` the mean of the incident edges'
26//!   midpoints.
27//!
28//! **Boundaries** (both schemes) follow the cubic B-spline curve, so a
29//! boundary converges to a curve that depends on boundary vertices only and
30//! two meshes sharing a boundary stay joined: a boundary edge gets its
31//! midpoint and a boundary vertex `3/4 v + 1/8 (b1 + b2)` from its two
32//! boundary neighbours. Isolated vertices are carried unchanged.
33//!
34//! # Numbering
35//!
36//! Each level's vertices are, in order: the input's live vertices (so a
37//! compact input keeps its vertex ids), one per live edge in edge-id order,
38//! and for Catmull-Clark one per live face in face-id order. Faces follow
39//! the input faces in face-id order. The output is deterministic.
40//!
41//! [`limit_positions`] evaluates where each vertex converges, from the
42//! eigen-analysis of the masks above (Halstead, Kass and DeRose 1993).
43
44use core::f64::consts::PI;
45
46use axiolid_core::{Point3, Scalar, Vec3};
47use axiolid_mesh::halfedge::{FaceId, HalfedgeBuildError, HalfedgeId, HalfedgeMesh, VertexId};
48
49/// A subdivision scheme.
50#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51#[non_exhaustive]
52pub enum SubdivisionScheme {
53    /// Loop: triangle meshes, 1-to-4 split, C2 away from extraordinary
54    /// vertices.
55    Loop,
56    /// Catmull-Clark: polygon meshes, all quads after the first level, C2
57    /// away from extraordinary vertices.
58    CatmullClark,
59}
60
61/// Options of [`subdivide`].
62#[derive(Debug, Clone, Copy, PartialEq, Eq)]
63pub struct SubdivisionOptions {
64    /// The scheme.
65    pub scheme: SubdivisionScheme,
66    /// Number of levels; zero returns a copy.
67    pub levels: u32,
68    /// Largest face count any level may produce.
69    pub max_faces: usize,
70}
71
72impl SubdivisionOptions {
73    /// `levels` levels of `scheme` with the default face budget.
74    #[must_use]
75    pub const fn new(scheme: SubdivisionScheme, levels: u32) -> Self {
76        Self {
77            scheme,
78            levels,
79            max_faces: 50_000_000,
80        }
81    }
82}
83
84/// Why subdivision refused.
85#[derive(Debug, Clone, PartialEq, thiserror::Error)]
86#[non_exhaustive]
87pub enum SubdivisionError {
88    /// Loop subdivision needs triangles.
89    #[error("face {face} has {degree} sides; Loop subdivision needs triangles")]
90    NotATriangle {
91        /// The face.
92        face: FaceId,
93        /// Its number of sides.
94        degree: usize,
95    },
96    /// Catmull-Clark limit positions need quads (subdivide once first).
97    #[error("face {face} has {degree} sides; Catmull-Clark limit positions need quads")]
98    NotAQuad {
99        /// The face.
100        face: FaceId,
101        /// Its number of sides.
102        degree: usize,
103    },
104    /// A level would produce more faces than allowed.
105    #[error("subdivision would produce {faces} faces, over the {limit} budget")]
106    BudgetExceeded {
107        /// Faces the level would have.
108        faces: usize,
109        /// The budget in force.
110        limit: usize,
111    },
112    /// Building a level's mesh failed. Not expected: every level of a valid
113    /// mesh is valid. Reported rather than unwrapped.
114    #[error("subdivided mesh could not be built: {0}")]
115    Build(#[from] HalfedgeBuildError),
116}
117
118/// Subdivide `mesh` `options.levels` times.
119///
120/// # Errors
121///
122/// [`SubdivisionError::NotATriangle`] for Loop on a mesh with another face
123/// and [`SubdivisionError::BudgetExceeded`] before building a level over
124/// the face budget.
125pub fn subdivide(
126    mesh: &HalfedgeMesh,
127    options: &SubdivisionOptions,
128) -> Result<HalfedgeMesh, SubdivisionError> {
129    let mut current = mesh.clone();
130    for _ in 0..options.levels {
131        let faces = match options.scheme {
132            SubdivisionScheme::Loop => {
133                require_triangles(&current)?;
134                current.face_count() * 4
135            }
136            SubdivisionScheme::CatmullClark => current
137                .faces()
138                .map(|f| current.face_degree(f))
139                .sum::<usize>(),
140        };
141        if faces > options.max_faces {
142            return Err(SubdivisionError::BudgetExceeded {
143                faces,
144                limit: options.max_faces,
145            });
146        }
147        current = match options.scheme {
148            SubdivisionScheme::Loop => loop_level(&current)?,
149            SubdivisionScheme::CatmullClark => catmull_clark_level(&current)?,
150        };
151    }
152    Ok(current)
153}
154
155/// The limit position of every vertex under repeated subdivision by
156/// `scheme`, indexed by vertex id (a removed vertex keeps its stored
157/// position).
158///
159/// # Errors
160///
161/// [`SubdivisionError::NotATriangle`] for Loop and
162/// [`SubdivisionError::NotAQuad`] for Catmull-Clark on a face of another
163/// degree.
164pub fn limit_positions(
165    mesh: &HalfedgeMesh,
166    scheme: SubdivisionScheme,
167) -> Result<Vec<Point3>, SubdivisionError> {
168    match scheme {
169        SubdivisionScheme::Loop => require_triangles(mesh)?,
170        SubdivisionScheme::CatmullClark => {
171            for f in mesh.faces() {
172                let degree = mesh.face_degree(f);
173                if degree != 4 {
174                    return Err(SubdivisionError::NotAQuad { face: f, degree });
175                }
176            }
177        }
178    }
179    let mut out = mesh.positions().to_vec();
180    for v in mesh.vertices() {
181        let p = mesh.position(v);
182        out[v.index()] = if mesh.is_isolated(v) {
183            p
184        } else if let Some((b1, b2)) = boundary_neighbours(mesh, v) {
185            // Limit of the cubic B-spline mask (1, 6, 1) / 8.
186            (4.0 * p + b1 + b2) / 6.0
187        } else {
188            match scheme {
189                SubdivisionScheme::Loop => {
190                    let n = mesh.degree(v) as Scalar;
191                    let gamma = 1.0 / (3.0 / (8.0 * loop_beta(n)) + n);
192                    let sum = mesh
193                        .vertex_vertices(v)
194                        .fold(Vec3::ZERO, |s, u| s + mesh.position(u));
195                    (1.0 - n * gamma) * p + gamma * sum
196                }
197                SubdivisionScheme::CatmullClark => {
198                    let n = mesh.degree(v) as Scalar;
199                    let mut edges = Vec3::ZERO;
200                    let mut diagonals = Vec3::ZERO;
201                    for h in mesh.outgoing_halfedges(v) {
202                        edges += mesh.position(mesh.target(h));
203                        diagonals += mesh.position(mesh.target(mesh.next(h)));
204                    }
205                    (n * n * p + 4.0 * edges + diagonals) / (n * (n + 5.0))
206                }
207            }
208        };
209    }
210    Ok(out)
211}
212
213fn require_triangles(mesh: &HalfedgeMesh) -> Result<(), SubdivisionError> {
214    for f in mesh.faces() {
215        let degree = mesh.face_degree(f);
216        if degree != 3 {
217            return Err(SubdivisionError::NotATriangle { face: f, degree });
218        }
219    }
220    Ok(())
221}
222
223/// Loop's `beta` for valence `n`.
224fn loop_beta(n: Scalar) -> Scalar {
225    let c = 3.0 / 8.0 + 0.25 * (2.0 * PI / n).cos();
226    (5.0 / 8.0 - c * c) / n
227}
228
229/// The two boundary neighbours of a boundary vertex (next along its
230/// boundary halfedge, previous along the incoming one); `None` for an
231/// interior vertex.
232fn boundary_neighbours(mesh: &HalfedgeMesh, v: VertexId) -> Option<(Point3, Point3)> {
233    let h = mesh.vertex_halfedge(v)?;
234    if !mesh.is_boundary_halfedge(h) {
235        return None;
236    }
237    let next = mesh.target(h);
238    let previous = mesh.source(mesh.prev(h));
239    Some((mesh.position(next), mesh.position(previous)))
240}
241
242/// Cubic B-spline vertex mask, shared by both schemes on a boundary.
243fn boundary_vertex(p: Point3, b1: Point3, b2: Point3) -> Point3 {
244    0.75 * p + 0.125 * (b1 + b2)
245}
246
247/// Dense numbering of the live input vertices.
248fn vertex_numbering(mesh: &HalfedgeMesh) -> (Vec<u32>, Vec<VertexId>) {
249    let mut number = vec![u32::MAX; mesh.positions().len()];
250    let live: Vec<VertexId> = mesh.vertices().collect();
251    for (k, v) in live.iter().enumerate() {
252        number[v.index()] = k as u32;
253    }
254    (number, live)
255}
256
257/// Dense numbering of the live edges, offset by `base`.
258fn edge_numbering(mesh: &HalfedgeMesh, base: usize) -> Vec<u32> {
259    let slots = mesh.edges().last().map_or(0, |e| e.index() + 1);
260    let mut number = vec![u32::MAX; slots];
261    for (k, e) in mesh.edges().enumerate() {
262        number[e.index()] = (base + k) as u32;
263    }
264    number
265}
266
267fn loop_level(mesh: &HalfedgeMesh) -> Result<HalfedgeMesh, SubdivisionError> {
268    let (vertex_number, live) = vertex_numbering(mesh);
269    let edge_number = edge_numbering(mesh, live.len());
270    let mut positions = Vec::with_capacity(live.len() + mesh.edge_count());
271    for &v in &live {
272        let p = mesh.position(v);
273        positions.push(if mesh.is_isolated(v) {
274            p
275        } else if let Some((b1, b2)) = boundary_neighbours(mesh, v) {
276            boundary_vertex(p, b1, b2)
277        } else {
278            let n = mesh.degree(v) as Scalar;
279            let beta = loop_beta(n);
280            let sum = mesh
281                .vertex_vertices(v)
282                .fold(Vec3::ZERO, |s, u| s + mesh.position(u));
283            (1.0 - n * beta) * p + beta * sum
284        });
285    }
286    for e in mesh.edges() {
287        let h = mesh.edge_halfedge(e, 0);
288        let g = mesh.opposite(h);
289        let a = mesh.position(mesh.source(h));
290        let b = mesh.position(mesh.target(h));
291        positions.push(if mesh.is_boundary_edge(e) {
292            0.5 * (a + b)
293        } else {
294            let c = mesh.position(mesh.target(mesh.next(h)));
295            let d = mesh.position(mesh.target(mesh.next(g)));
296            0.375 * (a + b) + 0.125 * (c + d)
297        });
298    }
299    let mut faces: Vec<[u32; 3]> = Vec::with_capacity(mesh.face_count() * 4);
300    for f in mesh.faces() {
301        let hs: Vec<HalfedgeId> = mesh.face_halfedges(f).collect();
302        let corner = |i: usize| vertex_number[mesh.source(hs[i]).index()];
303        let mid = |i: usize| edge_number[mesh.edge(hs[i]).index()];
304        for i in 0..3 {
305            faces.push([corner(i), mid(i), mid((i + 2) % 3)]);
306        }
307        faces.push([mid(0), mid(1), mid(2)]);
308    }
309    Ok(HalfedgeMesh::from_faces(positions, &faces)?)
310}
311
312fn catmull_clark_level(mesh: &HalfedgeMesh) -> Result<HalfedgeMesh, SubdivisionError> {
313    let (vertex_number, live) = vertex_numbering(mesh);
314    let edge_number = edge_numbering(mesh, live.len());
315    let face_base = live.len() + mesh.edge_count();
316    let mut face_number = vec![u32::MAX; mesh.faces().last().map_or(0, |f| f.index() + 1)];
317    let mut face_point = vec![Vec3::ZERO; face_number.len()];
318    for (k, f) in mesh.faces().enumerate() {
319        face_number[f.index()] = (face_base + k) as u32;
320        let (sum, count) = mesh
321            .face_vertices(f)
322            .fold((Vec3::ZERO, 0usize), |(s, c), v| {
323                (s + mesh.position(v), c + 1)
324            });
325        face_point[f.index()] = sum / count as Scalar;
326    }
327    let mut positions = Vec::with_capacity(face_base + mesh.face_count());
328    for &v in &live {
329        let p = mesh.position(v);
330        positions.push(if mesh.is_isolated(v) {
331            p
332        } else if let Some((b1, b2)) = boundary_neighbours(mesh, v) {
333            boundary_vertex(p, b1, b2)
334        } else {
335            let mut q = Vec3::ZERO;
336            let mut r = Vec3::ZERO;
337            let mut n = 0usize;
338            for h in mesh.outgoing_halfedges(v) {
339                let f = mesh
340                    .face(h)
341                    .expect("an interior vertex has faces all round");
342                q += face_point[f.index()];
343                r += 0.5 * (p + mesh.position(mesh.target(h)));
344                n += 1;
345            }
346            let n = n as Scalar;
347            (q / n + 2.0 * r / n + (n - 3.0) * p) / n
348        });
349    }
350    for e in mesh.edges() {
351        let h = mesh.edge_halfedge(e, 0);
352        let a = mesh.position(mesh.source(h));
353        let b = mesh.position(mesh.target(h));
354        positions.push(match (mesh.face(h), mesh.face(mesh.opposite(h))) {
355            (Some(f1), Some(f2)) => {
356                0.25 * (a + b + face_point[f1.index()] + face_point[f2.index()])
357            }
358            _ => 0.5 * (a + b),
359        });
360    }
361    for f in mesh.faces() {
362        positions.push(face_point[f.index()]);
363    }
364    let mut faces: Vec<[u32; 4]> =
365        Vec::with_capacity(mesh.faces().map(|f| mesh.face_degree(f)).sum::<usize>());
366    for f in mesh.faces() {
367        let hs: Vec<HalfedgeId> = mesh.face_halfedges(f).collect();
368        let k = hs.len();
369        let centre = face_number[f.index()];
370        for i in 0..k {
371            let previous = hs[(i + k - 1) % k];
372            faces.push([
373                vertex_number[mesh.source(hs[i]).index()],
374                edge_number[mesh.edge(hs[i]).index()],
375                centre,
376                edge_number[mesh.edge(previous).index()],
377            ]);
378        }
379    }
380    Ok(HalfedgeMesh::from_faces(positions, &faces)?)
381}