Skip to main content

subdiv_kernels/
output.rs

1use crate::{
2    Adjacency, Interpolatable, InverseStencilChain, Mesh, Scheme, SchemeOptions, StencilTable,
3};
4
5/// Origin classification for refined vertices.
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub enum VertexOrigin {
9    /// Vertex descended from a coarse vertex index.
10    Vertex(u32),
11
12    /// Vertex descended from a coarse edge index.
13    Edge(u32),
14
15    /// Vertex descended from a coarse face index.
16    Face(u32),
17}
18
19/// Refinement lineage maps for adapter-side propagation.
20#[derive(Debug, Clone, PartialEq, Eq, Hash, Default)]
21#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
22pub struct LineageMaps {
23    /// Origin tag for each refined vertex index.
24    pub vertex_origin: Vec<VertexOrigin>,
25
26    /// Parent coarse face index for each refined face.
27    pub face_parent: Vec<u32>,
28
29    /// Parent coarse edge index for each refined edge.
30    pub edge_parent: Vec<u32>,
31}
32
33/// Output of [`Refiner::refine_uniform`](crate::Refiner::refine_uniform).
34///
35/// Contains the refined topology, per-level stencil tables, and lineage
36/// information. Use [`interpolate`](Self::interpolate) to apply
37/// subdivision weights to any data buffer, or
38/// [`compose_stencils`](Self::compose_stencils) to precompute a
39/// single stencil table for amortized re-evaluation (animation).
40///
41/// # Performance model
42///
43/// - **One-shot**: call [`interpolate`](Self::interpolate) — chains
44///   per-level stencil application. Same algorithmic cost as direct
45///   subdivision. No exponential stencil growth.
46/// - **Animation**: call [`compose_stencils`](Self::compose_stencils)
47///   once, then [`StencilTable::interpolate`] each frame. Stencil
48///   composition is O(output × entries²) but amortized over many frames.
49/// - **Multiple buffers**: [`interpolate`](Self::interpolate) can be
50///   called once per buffer (positions, UVs, colors, …) — all share
51///   the same topology computation.
52#[derive(Debug, Clone, PartialEq)]
53#[non_exhaustive]
54#[must_use]
55pub struct RefinementResult {
56    /// Refined topology (no positions).
57    pub topology: Mesh,
58
59    /// Per-level stencil tables. `level_stencils[i]` maps level-i
60    /// vertices to level-(i+1) vertices. Length equals the number of
61    /// refinement levels.
62    pub level_stencils: Vec<StencilTable>,
63
64    /// Ancestry tracking for adapter-side attribute propagation.
65    /// Relative to the previous level (level N-1 -> N); for direct
66    /// refined-face -> base-face ancestry use [`face_root`](Self::face_root).
67    pub lineage: LineageMaps,
68
69    /// Base-mesh (root) face index for each refined face -- the per-level
70    /// `face_parent` chain pre-folded across all refinement levels, so an
71    /// adapter can map any refined face straight to the input face it
72    /// descends from (picking, per-face attribute propagation). Indexed by
73    /// refined face; values index the faces of the mesh given to the
74    /// [`Refiner`](crate::Refiner).
75    pub face_root: Vec<u32>,
76
77    /// Refined face selection mask (present when input had selection).
78    pub selected_faces: Option<Vec<bool>>,
79
80    /// For each input edge, the refined vertices lying along it, in order.
81    ///
82    /// `Some` only when the `edge_polylines` refinement option was set.
83    /// Indices refer to [`topology`](Self::topology).
84    pub edge_polylines: Option<Vec<Vec<u32>>>,
85
86    /// Pre-built adjacency arrays for the refined topology.
87    ///
88    /// Allows adapter-side mesh construction without redundant edge
89    /// discovery or adjacency analysis.
90    pub adjacency: Adjacency,
91
92    /// Scheme that produced this result. Recorded at
93    /// [`refine_uniform`](crate::Refiner::refine_uniform) time so
94    /// scheme-dependent post-processing
95    /// ([`limit_stencils`](Self::limit_stencils)) needs no refiner
96    /// handle.
97    pub scheme: Scheme,
98
99    /// Scheme options in effect during refinement (boundary and
100    /// sharpness conventions for [`limit_stencils`](Self::limit_stencils)).
101    pub options: SchemeOptions,
102}
103
104impl RefinementResult {
105    /// Interpolate a data buffer through all refinement levels.
106    ///
107    /// Chains per-level stencil application: each level reads from the
108    /// previous level's output and writes the next. This avoids the
109    /// exponential stencil growth of [`compose_stencils`](Self::compose_stencils)
110    /// and matches the performance of direct subdivision.
111    ///
112    /// The input buffer must have one entry per vertex in the **original**
113    /// (pre-refinement) topology. The output has one entry per vertex in
114    /// [`topology`](Self::topology).
115    pub fn interpolate<T: Interpolatable>(&self, input: &[T]) -> Vec<T> {
116        self.level_stencils
117            .iter()
118            .fold(input.to_vec(), |data, stencil| stencil.interpolate(&data))
119    }
120
121    /// Compose all per-level stencil tables into a single table mapping
122    /// original vertices directly to final refined vertices.
123    ///
124    /// Use this when you need to re-evaluate the same topology with
125    /// different data many times (e.g. animation with static topology).
126    /// The composed table enables a single `StencilTable::interpolate`
127    /// call per frame instead of chaining N levels.
128    ///
129    /// For one-shot subdivision, prefer [`interpolate`](Self::interpolate)
130    /// which avoids the O(output × entries²) composition cost.
131    /// Compose all per-level stencil tables into a single table mapping
132    /// original vertices directly to final refined vertices.
133    ///
134    /// `input_vertex_count` must match the number of vertices in the
135    /// original (pre-refinement) topology.
136    pub fn compose_stencils(&self, input_vertex_count: usize) -> StencilTable {
137        self.level_stencils.iter().fold(
138            StencilTable::identity(input_vertex_count),
139            |composed, level| composed.compose(level),
140        )
141    }
142
143    /// Build the inverse stencil chain for this refinement -- the transpose of
144    /// every level -- used to map changed control points to the refined output
145    /// vertices they affect.
146    ///
147    /// The chain is topology-only, so build it once and reuse it across edits.
148    /// For a single edit, [`affected_outputs`](Self::affected_outputs) is a
149    /// convenience that builds and queries it in one call.
150    pub fn inverse_stencil_chain(&self) -> InverseStencilChain {
151        InverseStencilChain::from(self.level_stencils.as_slice())
152    }
153
154    /// Final refined output indices affected by changing the given original
155    /// (pre-refinement) control-point indices, sorted ascending and deduped.
156    ///
157    /// `changed_inputs` are indices into the input buffer -- the same order as
158    /// the vertices of the `Mesh` given to the `Refiner` and of
159    /// [`interpolate`](Self::interpolate)'s input -- *not* host-mesh vertex IDs.
160    /// A host that keys edits by a stable vertex ID must map those IDs to this
161    /// dense input order first.
162    ///
163    /// Outputs *not* in this set are bit-identical under a change confined to
164    /// `changed_inputs` -- this is the basis of sparse re-evaluation. This
165    /// rebuilds the inverse chain on each call; for repeated edits, cache
166    /// [`inverse_stencil_chain`](Self::inverse_stencil_chain) and call its
167    /// `affected_outputs` directly.
168    pub fn affected_outputs(&self, changed_inputs: &[u32]) -> Vec<u32> {
169        self.inverse_stencil_chain()
170            .affected_outputs(changed_inputs)
171    }
172}