1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
// SPDX-License-Identifier: Apache-2.0
//! Subdivision-surface control cages.
use crate::ids::SubdId;
use crate::math::{Point3, Vector3};
use crate::provenance::SourceObjectAssociation;
#[cfg(feature = "schema")]
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
/// A subdivision surface represented by its control cage.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdSurface {
/// Arena identity.
pub id: SubdId,
/// Subdivision scheme.
pub scheme: SubdScheme,
/// Control-cage vertices.
pub vertices: Vec<SubdVertex>,
/// Control-cage edges.
pub edges: Vec<SubdEdge>,
/// Control-cage faces.
pub faces: Vec<SubdFace>,
/// Native editor symmetry blocks projected into control-cage coordinates.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub symmetries: Vec<SubdSymmetry>,
/// Native source-object identity and effective display metadata.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub source_object: Option<SourceObjectAssociation>,
}
/// A symmetry plane frame carried by a T-spline editor block.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdPlaneFrame {
/// A point on the plane in document length units.
pub origin: Point3,
/// First unit in-plane axis.
pub first_axis: Vector3,
/// Second unit in-plane axis.
pub second_axis: Vector3,
}
/// Kind-specific controls for a T-spline symmetry block.
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(tag = "kind", rename_all = "snake_case")]
pub enum SubdSymmetryKind {
/// One-to-one correspondence across the symmetry plane.
Correspondence,
/// Radial editor symmetry with native segment and sweep controls.
Radial {
/// Number of radial segments.
segments: u32,
/// Native radial sweep value.
sweep: f64,
},
}
/// Selector of one native radial-symmetry map.
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SubdRadialMapSelector {
/// Native `ef` map.
Ef,
/// Native `er` map.
Er,
/// Native `ff` map.
Ff,
/// Native `fr` map.
Fr,
/// Native `vf` map.
Vf,
/// Native `vr` map.
Vr,
}
/// One selector-preserving native radial-symmetry map.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdRadialSymmetryMap {
/// Native map selector. Its element namespace is format-native.
pub selector: SubdRadialMapSelector,
/// Native source/target identifier pairs.
pub pairs: Vec<[u64; 2]>,
}
/// Typed editor symmetry state for one subdivision cage.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdSymmetry {
/// Symmetry mode and its radial controls, when present.
pub kind: SubdSymmetryKind,
/// Geometric symmetry-plane frame.
pub plane: SubdPlaneFrame,
/// Forward face correspondences for a topology-addressed symmetry block.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub face_pairs: Vec<[u32; 2]>,
/// Forward edge correspondences for a topology-addressed symmetry block.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub edge_pairs: Vec<[u32; 2]>,
/// Forward vertex correspondences for a topology-addressed symmetry block.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub vertex_pairs: Vec<[u32; 2]>,
/// Selector-preserving native maps for radial symmetry blocks.
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub radial_maps: Vec<SubdRadialSymmetryMap>,
}
/// Subdivision scheme used by a control cage.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SubdScheme {
/// Catmull-Clark subdivision.
CatmullClark,
}
/// A control-cage vertex and its subdivision tag.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdVertex {
/// Vertex position.
pub point: Point3,
/// Subdivision vertex tag.
pub tag: SubdVertexTag,
/// Optional secondary-grip topology owned by this vertex.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub secondary_grips: Option<SubdVertexGripLayout>,
}
/// Compass direction of the root edge in a control-cage grid frame.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SubdGripDirection {
/// Positive grid-y direction.
North,
/// Positive grid-x direction.
East,
/// Negative grid-y direction.
South,
/// Negative grid-x direction.
West,
}
/// Typed secondary-grip layout for one subdivision vertex.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdVertexGripLayout {
/// Direction of the native root edge; wedge zero is the north slot.
pub direction: SubdGripDirection,
/// Wedges in north-anchored order.
pub wedges: Vec<SubdGripWedge>,
}
/// One wedge in a secondary-grip layout.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdGripWedge {
/// IR edge for this fan slot, or `None` for a phantom boundary slot.
pub edge: Option<u32>,
/// Face in the sector following this slot, or `None` for a boundary gap.
pub sector_face: Option<u32>,
/// Whether this slot was inserted to complete a boundary gap.
pub phantom: bool,
/// Spoke grips ordered nearest-first from the owning vertex.
pub spokes: Vec<Option<SubdSecondaryGrip>>,
/// Sector-grid grips ordered by the spoke-k position, then the next-spoke
/// position, with `S[k] * S[k + 1]` slots.
pub sectors: Vec<Option<SubdSecondaryGrip>>,
}
/// A secondary grip point and its source grip-array identity.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdSecondaryGrip {
/// Index in the source cage's `0g` grip array.
pub source_index: u32,
/// Grip position in document units.
pub point: Point3,
/// Positive rational grip weight.
pub weight: f64,
}
/// A control-cage vertex tag.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SubdVertexTag {
/// Smooth vertex.
Smooth,
/// Crease vertex.
Crease,
/// Corner vertex.
Corner,
/// Dart vertex.
Dart,
}
/// A control-cage edge with endpoint sharpness and sector coefficients.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdEdge {
/// Indices of the two distinct endpoint vertices.
pub vertices: [u32; 2],
/// Sharpness at the start and end endpoints.
pub sharpness: [f64; 2],
/// Subdivision edge tag.
pub tag: SubdEdgeTag,
/// Parametric knot interval, when the source cage exposes one.
#[serde(default, skip_serializing_if = "Option::is_none")]
pub knot_interval: Option<f64>,
/// Sector coefficients at the two endpoints.
pub sector_coefficients: [f64; 2],
}
/// A control-cage edge tag.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
#[serde(rename_all = "snake_case")]
pub enum SubdEdgeTag {
/// Smooth edge.
Smooth,
/// Smooth-X edge with the source's distinct subdivision behavior.
SmoothX,
/// Crease edge.
Crease,
}
/// A subdivision face bounded by a directed edge ring.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdFace {
/// Ordered directed edge uses forming the face boundary.
pub edges: Vec<SubdEdgeUse>,
}
/// One directed use of a subdivision edge in a face ring.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "schema", derive(JsonSchema))]
pub struct SubdEdgeUse {
/// Index into the parent surface's edge array.
pub edge: u32,
/// Whether this use traverses the edge from its second endpoint.
pub reversed: bool,
}
#[cfg(test)]
mod tests;