subdiv_kernels/topology.rs
1//! Mesh topology types: the control cage [`Mesh`], refined [`Adjacency`], and
2//! face-varying channels.
3
4use crate::KernelError;
5
6/// The control mesh: faces, edges, and optional crease/corner sharpness — but
7/// **no positions**.
8///
9/// This is the input to [`Refiner`](crate::Refiner). Positions and any other
10/// per-vertex data are carried separately and applied with a
11/// [`StencilTable`](crate::StencilTable), so one refinement serves every
12/// attribute.
13#[derive(Debug, Clone, PartialEq)]
14#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
15pub struct Mesh {
16 /// Number of vertices (needed since there is no positions array).
17 pub vertex_count: u32,
18
19 /// Number of corners for each face.
20 pub face_vertex_counts: Vec<u32>,
21
22 /// Flat face-vertex index list.
23 pub face_vertex_indices: Vec<u32>,
24
25 /// Canonical undirected edge endpoints.
26 pub edge_vertices: Vec<[u32; 2]>,
27
28 /// Per-edge crease values aligned with `edge_vertices`.
29 pub edge_creases: Vec<f32>,
30
31 /// Per-vertex corner values aligned by vertex index.
32 pub vertex_corners: Vec<f32>,
33}
34
35impl Mesh {
36 /// Validate basic array consistency.
37 pub fn validate(&self) -> Result<(), KernelError> {
38 let corner_count: usize = self.face_vertex_counts.iter().map(|v| *v as usize).sum();
39
40 (corner_count == self.face_vertex_indices.len())
41 .then_some(())
42 .ok_or(KernelError::InvalidTopology(
43 "face corner count does not match index buffer length",
44 ))?;
45
46 (self.edge_creases.len() == self.edge_vertices.len())
47 .then_some(())
48 .ok_or(KernelError::InvalidTopology(
49 "edge crease count does not match edge buffer length",
50 ))?;
51
52 (self.vertex_corners.len() == self.vertex_count as usize)
53 .then_some(())
54 .ok_or(KernelError::InvalidTopology(
55 "vertex corner count does not match vertex count",
56 ))?;
57
58 self.face_vertex_indices
59 .iter()
60 .all(|&idx| idx < self.vertex_count)
61 .then_some(())
62 .ok_or(KernelError::InvalidTopology("face index out of bounds"))?;
63
64 self.edge_vertices
65 .iter()
66 .flat_map(|e| e.iter())
67 .all(|&idx| idx < self.vertex_count)
68 .then_some(())
69 .ok_or(KernelError::InvalidTopology("edge endpoint out of bounds"))
70 }
71}
72
73/// Pre-built adjacency arrays in CSR format.
74///
75/// Produced by refinement and consumed by adapter-side mesh construction
76/// to avoid redundant topology analysis.
77#[derive(Debug, Clone, PartialEq)]
78#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
79#[non_exhaustive]
80pub struct Adjacency {
81 /// Per face-corner → edge index (same layout as `face_vertex_indices`).
82 pub face_edges: Vec<u32>,
83 /// Two incident faces per edge. `u32::MAX` = boundary.
84 pub edge_faces: Vec<[u32; 2]>,
85 /// CSR vertex → edge adjacency: per-vertex start offsets into `vertex_edges`.
86 pub vertex_edge_offsets: Vec<u32>,
87 /// Flattened incident-edge indices (sliced by `vertex_edge_offsets`).
88 pub vertex_edges: Vec<u32>,
89 /// CSR vertex → face adjacency: per-vertex start offsets into `vertex_faces`.
90 pub vertex_face_offsets: Vec<u32>,
91 /// Flattened incident-face indices (sliced by `vertex_face_offsets`).
92 pub vertex_faces: Vec<u32>,
93 /// Per-edge boundary flag.
94 pub edge_is_boundary: Vec<bool>,
95 /// Per-vertex boundary flag.
96 pub vertex_is_boundary: Vec<bool>,
97}
98
99/// Per-channel face-varying value-index topology.
100///
101/// At UV seams, face-corners sharing a geometric vertex can reference
102/// different FVar values. This struct holds the per-face-corner indices
103/// into the channel's own value array.
104#[derive(Debug, Clone, PartialEq)]
105#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
106pub struct FaceVaryingChannel {
107 /// Per face-corner index into the channel's value array.
108 /// Same length as `Mesh::face_vertex_indices`.
109 pub indices: Vec<u32>,
110
111 /// Number of distinct values in this channel.
112 pub value_count: u32,
113}