subdiv_kernels/lib.rs
1#![cfg_attr(docsrs, feature(doc_cfg))]
2#![warn(missing_docs)]
3//! Subdivision surface kernels — Catmull–Clark, Loop, √3, and Doo–Sabin.
4//!
5//! Subdivision refines a coarse polygon **control mesh** into a finer, smoother
6//! one: each step splits the faces and places the new points as weighted
7//! averages of their neighbors, converging to a smooth *limit surface*.
8//!
9//! This crate computes that refinement's connectivity and weights and returns
10//! [`StencilTable`]s — sparse maps where each output point is a weighted sum of
11//! a few input points. Supply a [`topology::Mesh`] (the control cage) and apply
12//! the stencils to your own per-vertex data (positions, UVs, colors, …); the
13//! crate holds no geometry and needs no host mesh type.
14//!
15//! # Example
16//!
17//! Refine a tetrahedron and exercise the main pieces of the API — one-shot
18//! interpolation, composed-table re-evaluation, sparse-edit queries,
19//! face-varying (UV) channels, the cached refinement handle, and limit
20//! stencils.
21//!
22//! ```
23//! use std::num::NonZeroU8;
24//! use subdiv_kernels::{
25//! topology::{FaceVaryingChannel, Mesh},
26//! FaceVaryingInterpolation, Refiner, Scheme, SchemeOptions, UniformRefine,
27//! };
28//!
29//! // A tetrahedron: 4 vertices, 4 triangular faces, 6 edges (closed surface).
30//! let face_vertex_indices = vec![0, 1, 2, /**/ 0, 2, 3, /**/ 0, 3, 1, /**/ 1, 3, 2];
31//! let mesh = Mesh {
32//! vertex_count: 4,
33//! face_vertex_counts: vec![3; 4],
34//! face_vertex_indices: face_vertex_indices.clone(),
35//! edge_vertices: vec![[0, 1], [0, 2], [0, 3], [1, 2], [1, 3], [2, 3]],
36//! edge_creases: vec![0.0; 6],
37//! vertex_corners: vec![0.0; 4],
38//! };
39//! let positions: Vec<[f32; 3]> =
40//! vec![[0., 0., 0.], [1., 0., 0.], [0., 1., 0.], [0., 0., 1.]];
41//!
42//! let refiner = Refiner::new(mesh, Scheme::CatmullClark, SchemeOptions::default())?;
43//! let req = UniformRefine::from(NonZeroU8::new(2).unwrap());
44//!
45//! // One-shot: interpolate any per-vertex data through all levels.
46//! let result = refiner.refine_uniform(&req)?;
47//! let refined = result.interpolate(&positions);
48//! assert_eq!(refined.len(), result.topology.vertex_count as usize);
49//!
50//! // Animation: compose the per-level stencils once, re-evaluate each frame.
51//! // Same surface as the chained path (up to f32 rounding).
52//! let composed = result.compose_stencils(positions.len());
53//! let composed_positions = composed.interpolate(&positions);
54//! assert!(composed_positions.iter().zip(&refined).all(|(a, b)| {
55//! a.iter().zip(b).all(|(x, y)| (x - y).abs() < 1e-4)
56//! }));
57//!
58//! // Sparse edits: which refined outputs move when control point 0 moves?
59//! assert!(!result.affected_outputs(&[0]).is_empty());
60//!
61//! // Face-varying UVs, smooth interior with linear island boundaries.
62//! let uvs: Vec<[f32; 2]> = (0..4).map(|i| [i as f32, 0.0]).collect();
63//! let uv_channel = FaceVaryingChannel { indices: face_vertex_indices, value_count: 4 };
64//! let fvar_tables = refiner.face_varying_stencils(
65//! &req,
66//! &uv_channel,
67//! FaceVaryingInterpolation::SmoothWithLinearBoundaries,
68//! )?;
69//! let refined_uvs = fvar_tables.iter().fold(uvs, |d, t| t.interpolate(&d));
70//! assert_eq!(refined_uvs.len(), result.topology.face_vertex_indices.len());
71//!
72//! // Cached handle: query per level without recomputing topology, then take
73//! // the owned final mesh + adjacency.
74//! let refinement = refiner.refine_topology(&req)?;
75//! let parts = refinement.into_final_parts();
76//! assert_eq!(parts.topology.vertex_count, result.topology.vertex_count);
77//!
78//! // Limit surface: stencils for limit positions and tangents/normals.
79//! let _limit = result.limit_stencils()?;
80//!
81//! // Write the refined surface as a Wavefront OBJ (vertices, then faces).
82//! let mut obj = String::new();
83//! for [x, y, z] in &refined {
84//! obj += &format!("v {x} {y} {z}\n");
85//! }
86//! let mut corner = 0;
87//! for &n in &result.topology.face_vertex_counts {
88//! obj += "f";
89//! for k in 0..n as usize {
90//! // OBJ indices are 1-based.
91//! obj += &format!(" {}", result.topology.face_vertex_indices[corner + k] + 1);
92//! }
93//! obj += "\n";
94//! corner += n as usize;
95//! }
96//! // std::fs::write("surface.obj", &obj)?; // ← persist to disk
97//! assert_eq!(obj.lines().filter(|l| l.starts_with("v ")).count(), refined.len());
98//! # Ok::<(), subdiv_kernels::KernelError>(())
99//! ```
100//!
101//! # Performance
102//!
103//! [`RefinementResult::interpolate`] chains the per-level stencils — the same
104//! algorithmic cost as direct subdivision, best for a one-shot refine. For
105//! animation (static topology, changing data),
106//! [`RefinementResult::compose_stencils`] precomputes a single table mapping
107//! control points straight to the final level, so each frame is one
108//! [`StencilTable::interpolate`] call. Either path applies to any number of
109//! data buffers (positions, UVs, …) that share the topology.
110//!
111//! # Implementing [`Interpolatable`]
112//!
113//! Any type with a weighted add can be subdivided. The crate ships impls for
114//! `f32`, `f64`, and `[f32; N]` / `[f64; N]`; for your own types:
115//!
116//! ```
117//! use subdiv_kernels::Interpolatable;
118//!
119//! #[derive(Default, Clone)]
120//! struct Color { r: f32, g: f32, b: f32, a: f32 }
121//!
122//! impl Interpolatable for Color {
123//! fn add_with_weight(&mut self, src: &Self, weight: f32) {
124//! self.r += src.r * weight;
125//! self.g += src.g * weight;
126//! self.b += src.b * weight;
127//! self.a += src.a * weight;
128//! }
129//! }
130//! ```
131
132mod catmull_clark;
133mod closest_point;
134pub(crate) mod csr;
135mod doo_sabin;
136mod error;
137mod face_varying;
138mod interpolate;
139mod inverse;
140mod limit;
141mod limit_eval;
142mod loop_subdivision;
143mod options;
144mod output;
145mod patch;
146mod refiner;
147pub(crate) mod sharpness;
148mod sqrt3;
149mod stencil;
150#[cfg(test)]
151mod test_support;
152pub mod topology;
153#[cfg(feature = "wgpu")]
154mod wgpu;
155
156pub use closest_point::ClosestPoint;
157pub use error::KernelError;
158pub use interpolate::Interpolatable;
159pub use inverse::{AffectedScratch, InverseStencilChain, InverseStencilMap};
160pub use limit::{LimitStencils, SectoredLimitStencils};
161pub use limit_eval::{LimitEvaluator, LimitSample, MAX_ISOLATION_DEPTH};
162pub use options::{
163 BoundaryInterpolation, CornerRule, CreaseComputationMethod, FaceVaryingInterpolation, Scheme,
164 SchemeOptions, TriangleSubdivisionRule, UniformRefine,
165};
166pub use output::{LineageMaps, RefinementResult, VertexOrigin};
167pub use patch::{PatchTable, QuadClass};
168pub use refiner::{RefinedFinalParts, Refinement, Refiner};
169pub use stencil::StencilTable;
170// Canonical home is the `topology` module (`topology::Mesh`); these are also
171// re-exported at the crate root for terse `use subdiv_kernels::Mesh` sites.
172pub use topology::{Adjacency, FaceVaryingChannel, Mesh};
173#[cfg(feature = "wgpu")]
174#[cfg_attr(docsrs, doc(cfg(feature = "wgpu")))]
175pub use wgpu::{
176 BufferDescriptor, GpuContext, MAX_COMPONENTS, STENCIL_EVAL_WGSL, StencilEvalPipeline,
177 StencilTableGpu, evaluate_stencils,
178};
179
180/// Common imports for typical use: `use subdiv_kernels::prelude::*;`.
181pub mod prelude {
182 pub use crate::{
183 BoundaryInterpolation, CornerRule, CreaseComputationMethod, FaceVaryingChannel,
184 FaceVaryingInterpolation, Interpolatable, KernelError, Mesh, RefinementResult, Refiner,
185 Scheme, SchemeOptions, StencilTable, TriangleSubdivisionRule, UniformRefine,
186 };
187}