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}