Skip to main content

geop_ops_subd/
limit.rs

1//! The limit surface of a once-subdivided cage (see [`crate::subdivide`]),
2//! as one bicubic Bézier patch per quad.
3//!
4//! Every quad's patch is put together from three kinds of control points,
5//! each computed once and shared by every patch it belongs to:
6//!
7//! - a *corner* per vertex: its limit position;
8//! - two *edge* points per edge, one near each end, which with the corners
9//!   make up the cubic every patch along that edge has as its boundary —
10//!   computed per edge, not per patch, so neighbouring patches meet along
11//!   exactly the same curve, and the B-rep built from them is closed;
12//! - an *interior* point per corner of every quad.
13//!
14//! This is Loop and Schaefer's approximation of Catmull–Clark surfaces by
15//! bicubic patches: where all four corners of a quad and its neighbours are
16//! regular — smooth, four faces each — it gives exactly the uniform bicubic
17//! B-spline that is the limit surface there. Around an extraordinary vertex
18//! it is an approximation: the patches meet along shared curves, with a
19//! continuous normal at every vertex — every edge point next to a smooth
20//! vertex lies in its limit tangent plane — but across the edges out of an
21//! extraordinary vertex the normal turns by a few degrees.
22//!
23//! Creased and boundary edges are sharp: their curve is the cubic B-spline
24//! of their vertices, which the patches on either side share, and the
25//! interior points next to them are computed per *sector*, the faces around
26//! a vertex between two sharp edges, as if that sector were mirrored into a
27//! regular vertex's: weight `n` is the vertex's number of faces where it is
28//! smooth, twice the sector's along a crease, four times at a corner.
29
30use std::collections::HashMap;
31
32use geop_core_math::{geop_error::GeopResult, scalars::Scalar, vector::Vector3};
33
34use crate::{
35    cage::{next, prev},
36    subdivide::{Subdivided, VertexKind, divide, is_sharp, vertex_kind, weighted},
37};
38
39/// A bicubic Bézier patch, `b[i][j]` with `i` along its first side.
40pub type Patch = [[Vector3; 4]; 4];
41
42/// The shared control points of every patch of a subdivided mesh.
43#[derive(Clone, Debug)]
44pub struct ControlNet {
45    /// Per vertex: its limit position.
46    pub corner: Vec<Vector3>,
47    /// Per directed edge `(v, w)`: the edge point near `v`.
48    pub edge: HashMap<(usize, usize), Vector3>,
49    /// Per quad, per corner: its interior point.
50    pub interior: Vec<[Vector3; 4]>,
51}
52
53impl ControlNet {
54    /// The patch of quad `f`, its first side running from its first corner
55    /// to the next one.
56    pub fn patch(&self, mesh: &Subdivided, f: usize) -> Patch {
57        let q = |i: usize| mesh.faces[f][i];
58        let l = |i: usize| self.corner[q(i)];
59        let e = |a: usize, b: usize| self.edge[&(q(a), q(b))];
60        let n = |i: usize| self.interior[f][i];
61        [
62            [l(0), e(0, 3), e(3, 0), l(3)],
63            [e(0, 1), n(0), n(3), e(3, 2)],
64            [e(1, 0), n(1), n(2), e(2, 3)],
65            [l(1), e(1, 2), e(2, 1), l(2)],
66        ]
67    }
68}
69
70/// For every corner of `v`'s fan, how many faces the sector it is in has:
71/// the run of faces around `v` between two sharp edges.
72fn sector_sizes(mesh: &Subdivided, v: usize) -> Vec<usize> {
73    let (fan, closed) = &mesh.topology.fans[v];
74    let m = fan.len();
75    // Between fan[i] and fan[i + 1] lies the edge to fan[i]'s previous
76    // corner.
77    let cut = |i: usize| {
78        let w = prev(&mesh.faces, fan[i]);
79        is_sharp(&mesh.sharp, &mesh.topology, v, w)
80    };
81    let start = if *closed {
82        // Begin just after a sharp edge; a smooth vertex has none, and is
83        // one sector all round.
84        match (0..m).find(|&i| cut(i)) {
85            Some(i) => (i + 1) % m,
86            None => return vec![m; m],
87        }
88    } else {
89        0
90    };
91    let mut sizes = vec![0; m];
92    let mut run: Vec<usize> = Vec::new();
93    for step in 0..m {
94        let i = (start + step) % m;
95        run.push(i);
96        if step + 1 == m || cut(i) {
97            for &j in &run {
98                sizes[j] = run.len();
99            }
100            run.clear();
101        }
102    }
103    sizes
104}
105
106/// The normal of `v`'s limit tangent plane, for a smooth vertex of `n`
107/// faces: the cross product of its two limit tangents, from the
108/// eigenvector masks of Catmull–Clark subdivision. A free choice of how
109/// long, so it is sharpened.
110fn tangent_normal(mesh: &Subdivided, v: usize) -> Vector3 {
111    let fan = &mesh.topology.fans[v].0;
112    let n = fan.len();
113    let p = &mesh.positions;
114    let angle = |i: usize| 2.0 * std::f64::consts::PI * i as f64 / n as f64;
115    let c = (std::f64::consts::PI / n as f64).cos();
116    let a = 1.0 + angle(1).cos() + c * (2.0 * (9.0 + angle(1).cos())).sqrt();
117    let tangent = |trig: fn(f64) -> f64| {
118        fan.iter()
119            .enumerate()
120            .fold(Vector3::zero(), |sum, (i, &corner)| {
121                let e = p[next(&mesh.faces, corner)].sub(&p[v]);
122                let d = p[mesh.faces[corner.0][(corner.1 + 2) % 4]].sub(&p[v]);
123                let we = Scalar::from_f64(a * trig(angle(i)));
124                let wd = Scalar::from_f64(trig(angle(i)) + trig(angle(i + 1)));
125                sum.add(&e.prod_scalar(we)).add(&d.prod_scalar(wd))
126            })
127    };
128    tangent(f64::cos).prod_cross(&tangent(f64::sin)).sharpen()
129}
130
131/// The control net of `mesh`'s limit surface (see the module docs).
132pub fn control_net(mesh: &Subdivided) -> GeopResult<ControlNet> {
133    let p = &mesh.positions;
134    let faces = &mesh.faces;
135    let topology = &mesh.topology;
136    let kinds: Vec<VertexKind> = (0..p.len())
137        .map(|v| vertex_kind(faces, &mesh.sharp, topology, v))
138        .collect();
139
140    let mut weight = vec![[0usize; 4]; faces.len()];
141    for (v, kind) in kinds.iter().enumerate() {
142        let fan = &topology.fans[v].0;
143        let sizes = sector_sizes(mesh, v);
144        for (i, &(f, k)) in fan.iter().enumerate() {
145            weight[f][k] = match kind {
146                VertexKind::Smooth => fan.len(),
147                VertexKind::Crease(..) => 2 * sizes[i],
148                VertexKind::Corner => 4 * sizes[i],
149            };
150        }
151    }
152    let mut interior = Vec::with_capacity(faces.len());
153    for (f, face) in faces.iter().enumerate() {
154        let mut points = [Vector3::zero(); 4];
155        for k in 0..4 {
156            let n = weight[f][k] as i64;
157            let terms = [
158                (n, p[face[k]]),
159                (2, p[face[(k + 1) % 4]]),
160                (2, p[face[(k + 3) % 4]]),
161                (1, p[face[(k + 2) % 4]]),
162            ];
163            points[k] = divide(weighted(&terms), n + 5)?;
164        }
165        interior.push(points);
166    }
167
168    let mut corner = Vec::with_capacity(p.len());
169    for v in 0..p.len() {
170        corner.push(match kinds[v] {
171            VertexKind::Corner => p[v],
172            VertexKind::Crease(a, b) => divide(weighted(&[(1, p[a]), (4, p[v]), (1, p[b])]), 6)?,
173            VertexKind::Smooth => {
174                let fan = &topology.fans[v].0;
175                let n = fan.len() as i64;
176                let mut terms = vec![(n * n, p[v])];
177                for &(f, k) in fan {
178                    terms.push((4, p[next(faces, (f, k))]));
179                    terms.push((1, p[faces[f][(k + 2) % 4]]));
180                }
181                divide(weighted(&terms), n * (n + 5))?
182            }
183        });
184    }
185
186    let mut normals: HashMap<usize, Vector3> = HashMap::new();
187    let mut edge = HashMap::new();
188    for (&(v, w), &(f, k)) in &topology.directed {
189        for (from, to) in [(v, w), (w, v)] {
190            if edge.contains_key(&(from, to)) {
191                continue;
192            }
193            let sharp = is_sharp(&mesh.sharp, topology, from, to);
194            let point = if sharp && kinds[from] != VertexKind::Smooth {
195                divide(weighted(&[(2, p[from]), (1, p[to])]), 3)?
196            } else {
197                // Not on the boundary, so two faces run along it: `f` from
198                // `v` to `w`, and its neighbour back.
199                let &(g, j) = &topology.directed[&(w, v)];
200                let at = |face: usize, corner_of: usize, from: usize| {
201                    if faces[face][corner_of] == from {
202                        interior[face][corner_of]
203                    } else {
204                        interior[face][(corner_of + 1) % 4]
205                    }
206                };
207                let sum = at(f, k, from).add(&at(g, j, from));
208                let average = divide(sum, 2)?;
209                let n = topology.fans[from].0.len();
210                if kinds[from] == VertexKind::Smooth && n != 4 {
211                    let normal = *normals
212                        .entry(from)
213                        .or_insert_with(|| tangent_normal(mesh, from));
214                    let off = average.sub(&corner[from]);
215                    let along = off.prod_dot(&normal).div(normal.norm_sq())?;
216                    average.sub(&normal.prod_scalar(along))
217                } else {
218                    average
219                }
220            };
221            edge.insert((from, to), point);
222        }
223    }
224    Ok(ControlNet {
225        corner,
226        edge,
227        interior,
228    })
229}