Skip to main content

scena/assets/gltf/
scene_asset.rs

1use std::collections::BTreeSet;
2use std::sync::Arc;
3
4use crate::animation::{AnimationSourceChannel, AnimationSourceClip};
5use crate::geometry::Aabb;
6use crate::scene::view_math::{transform_aabb, union_aabb};
7use crate::scene::{Light, SourceCoordinateSystem, SourceUnits, Transform};
8use serde::{Deserialize, Serialize};
9
10use super::{
11    GltfExtensionDiagnostic, MaterialVariantBinding, SceneAssetAnchor, SceneAssetConnector,
12    SceneAssetSkin,
13};
14use crate::assets::{
15    AssetLoadWarning, AssetMaterialFallback, AssetPath, AssetProvenance, GeometryHandle,
16    MaterialHandle,
17};
18
19pub const ASSET_GEOMETRY_SUMMARY_SCHEMA_V1: &str = "scena.asset_geometry_summary.v1";
20
21#[derive(Debug, Clone)]
22pub struct SceneAsset {
23    pub(in crate::assets::gltf) inner: Arc<SceneAssetData>,
24}
25
26#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
27pub struct SceneAssetGeometrySummary {
28    pub schema: String,
29    pub node_count: usize,
30    pub mesh_count: usize,
31    pub primitive_count: usize,
32    pub bounds: Option<Aabb>,
33    pub provenance: AssetProvenance,
34    pub source_units: Vec<SourceUnits>,
35    pub source_coordinate_systems: Vec<SourceCoordinateSystem>,
36}
37
38#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
39pub struct SelectedGltfScene {
40    pub index: usize,
41    pub name: Option<String>,
42    pub selection: String,
43}
44
45#[derive(Debug, Clone, PartialEq)]
46pub(in crate::assets::gltf) struct SceneAssetData {
47    pub(in crate::assets::gltf) path: AssetPath,
48    pub(in crate::assets::gltf) node_count: usize,
49    pub(in crate::assets::gltf) mesh_count: usize,
50    pub(in crate::assets::gltf) nodes: Vec<SceneAssetNode>,
51    pub(in crate::assets::gltf) roots: Vec<usize>,
52    pub(in crate::assets::gltf) selected_gltf_scene: Option<SelectedGltfScene>,
53    pub(in crate::assets::gltf) skins: Vec<SceneAssetSkin>,
54    pub(in crate::assets::gltf) clips: Vec<SceneAssetClip>,
55    pub(in crate::assets::gltf) extensions_used: Vec<String>,
56    pub(in crate::assets::gltf) extensions_required: Vec<String>,
57    pub(in crate::assets::gltf) extension_diagnostics: Vec<GltfExtensionDiagnostic>,
58    pub(in crate::assets::gltf) material_variants: Vec<String>,
59    pub(in crate::assets::gltf) material_fallbacks: Vec<AssetMaterialFallback>,
60    pub(in crate::assets::gltf) load_warnings: Vec<AssetLoadWarning>,
61    pub(in crate::assets::gltf) provenance: AssetProvenance,
62    pub(in crate::assets::gltf) retained_source_bytes: Option<Arc<[u8]>>,
63}
64
65#[derive(Debug, Clone, PartialEq)]
66pub struct SceneAssetNode {
67    pub(in crate::assets::gltf) name: Option<String>,
68    pub(in crate::assets::gltf) children: Vec<usize>,
69    pub(in crate::assets::gltf) transform: Transform,
70    pub(in crate::assets::gltf) meshes: Vec<SceneAssetMesh>,
71    pub(in crate::assets::gltf) instance_transforms: Vec<Transform>,
72    pub(in crate::assets::gltf) skin: Option<usize>,
73    pub(in crate::assets::gltf) light: Option<SceneAssetLight>,
74    pub(in crate::assets::gltf) anchors: Vec<SceneAssetAnchor>,
75    pub(in crate::assets::gltf) connectors: Vec<SceneAssetConnector>,
76}
77
78#[derive(Debug, Clone, PartialEq)]
79pub struct SceneAssetMesh {
80    pub(in crate::assets::gltf) geometry: GeometryHandle,
81    pub(in crate::assets::gltf) material: MaterialHandle,
82    pub(in crate::assets::gltf) bounds: Aabb,
83    pub(in crate::assets::gltf) uses_vertex_colors: bool,
84    pub(in crate::assets::gltf) morph_weights: Vec<f32>,
85    pub(in crate::assets::gltf) material_variant_bindings: Vec<MaterialVariantBinding>,
86}
87
88#[derive(Debug, Clone, Copy, PartialEq)]
89pub struct SceneAssetLight {
90    pub(in crate::assets::gltf) light: Light,
91}
92
93#[derive(Debug, Clone, PartialEq)]
94pub struct SceneAssetClip {
95    pub(in crate::assets::gltf) clip: AnimationSourceClip,
96}
97
98impl SceneAsset {
99    pub fn empty() -> Self {
100        Self {
101            inner: Arc::new(SceneAssetData {
102                path: AssetPath::from("memory:empty"),
103                node_count: 0,
104                mesh_count: 0,
105                nodes: Vec::new(),
106                roots: Vec::new(),
107                selected_gltf_scene: None,
108                skins: Vec::new(),
109                clips: Vec::new(),
110                extensions_used: Vec::new(),
111                extensions_required: Vec::new(),
112                extension_diagnostics: Vec::new(),
113                material_variants: Vec::new(),
114                material_fallbacks: Vec::new(),
115                load_warnings: Vec::new(),
116                provenance: AssetProvenance::new("memory:empty"),
117                retained_source_bytes: None,
118            }),
119        }
120    }
121
122    pub fn path(&self) -> &AssetPath {
123        &self.inner.path
124    }
125
126    pub fn provenance(&self) -> &AssetProvenance {
127        &self.inner.provenance
128    }
129
130    pub fn node_count(&self) -> usize {
131        self.inner.node_count
132    }
133
134    pub fn mesh_count(&self) -> usize {
135        self.inner.mesh_count
136    }
137
138    pub fn primitive_count(&self) -> usize {
139        self.inner.nodes.iter().map(|node| node.meshes.len()).sum()
140    }
141
142    pub fn bounds(&self) -> Option<Aabb> {
143        let roots = self.root_indices();
144        roots
145            .into_iter()
146            .filter_map(|root| self.node_bounds_in_asset_space(root, Transform::IDENTITY))
147            .reduce(union_aabb)
148    }
149
150    pub fn geometry_summary(&self) -> SceneAssetGeometrySummary {
151        let source_units = self.source_units_summary();
152        let source_coordinate_systems = self.source_coordinate_system_summary();
153        SceneAssetGeometrySummary {
154            schema: ASSET_GEOMETRY_SUMMARY_SCHEMA_V1.to_owned(),
155            node_count: self.node_count(),
156            mesh_count: self.mesh_count(),
157            primitive_count: self.primitive_count(),
158            bounds: self.bounds(),
159            provenance: self.provenance().clone(),
160            source_units,
161            source_coordinate_systems,
162        }
163    }
164
165    pub fn nodes(&self) -> &[SceneAssetNode] {
166        &self.inner.nodes
167    }
168
169    pub fn selected_gltf_scene(&self) -> Option<&SelectedGltfScene> {
170        self.inner.selected_gltf_scene.as_ref()
171    }
172
173    pub fn skins(&self) -> &[SceneAssetSkin] {
174        &self.inner.skins
175    }
176
177    pub fn clips(&self) -> &[SceneAssetClip] {
178        &self.inner.clips
179    }
180
181    pub fn extensions_used(&self) -> &[String] {
182        &self.inner.extensions_used
183    }
184
185    pub fn extensions_required(&self) -> &[String] {
186        &self.inner.extensions_required
187    }
188
189    pub fn extension_diagnostics(&self) -> &[GltfExtensionDiagnostic] {
190        &self.inner.extension_diagnostics
191    }
192
193    /// Variant names declared by KHR_materials_variants in declaration
194    /// order; empty when the extension is absent (Phase 2B step 1).
195    pub fn material_variants(&self) -> &[String] {
196        &self.inner.material_variants
197    }
198
199    pub fn material_fallbacks(&self) -> &[AssetMaterialFallback] {
200        &self.inner.material_fallbacks
201    }
202
203    pub(crate) fn load_warnings(&self) -> &[AssetLoadWarning] {
204        &self.inner.load_warnings
205    }
206
207    pub fn retained_source_bytes_len(&self) -> Option<usize> {
208        self.inner
209            .retained_source_bytes
210            .as_ref()
211            .map(|bytes| bytes.len())
212    }
213
214    pub(in crate::assets) fn retained_source_bytes(&self) -> Option<&[u8]> {
215        self.inner.retained_source_bytes.as_deref()
216    }
217
218    pub(in crate::assets) fn with_retained_source_bytes(mut self, bytes: &[u8]) -> Self {
219        Arc::make_mut(&mut self.inner).retained_source_bytes =
220            Some(Arc::<[u8]>::from(bytes.to_vec()));
221        self
222    }
223
224    #[cfg(test)]
225    pub(crate) fn inject_invalid_child_for_transaction_test(
226        &mut self,
227        parent: usize,
228        child: usize,
229    ) {
230        Arc::make_mut(&mut self.inner).nodes[parent]
231            .children
232            .push(child);
233    }
234
235    #[cfg(test)]
236    pub(crate) fn inject_invalid_anchor_for_transaction_test(&mut self, node: usize, reason: &str) {
237        Arc::make_mut(&mut self.inner).nodes[node]
238            .anchors
239            .push(SceneAssetAnchor::invalid_for_transaction_test(reason));
240    }
241
242    #[cfg(test)]
243    pub(crate) fn inject_invalid_connector_for_transaction_test(
244        &mut self,
245        node: usize,
246        reason: &str,
247    ) {
248        Arc::make_mut(&mut self.inner).nodes[node]
249            .connectors
250            .push(SceneAssetConnector::invalid_for_transaction_test(reason));
251    }
252
253    #[cfg(test)]
254    pub(crate) fn inject_invalid_skin_for_transaction_test(&mut self, node: usize, skin: usize) {
255        Arc::make_mut(&mut self.inner).nodes[node].skin = Some(skin);
256    }
257
258    #[cfg(test)]
259    pub(crate) fn inject_additional_root_for_transaction_test(&mut self) {
260        let inner = Arc::make_mut(&mut self.inner);
261        let mut root = inner.nodes[0].clone();
262        root.name = Some("SecondRoot".to_owned());
263        root.children.clear();
264        inner.nodes.push(root);
265        inner.roots.push(inner.nodes.len() - 1);
266        inner.node_count = inner.nodes.len();
267    }
268
269    #[cfg(test)]
270    pub(crate) fn rename_root_for_transaction_test(&mut self, ordinal: usize, name: &str) {
271        let inner = Arc::make_mut(&mut self.inner);
272        let root = inner.roots[ordinal];
273        inner.nodes[root].name = Some(name.to_owned());
274    }
275
276    pub(crate) fn root_indices(&self) -> Vec<usize> {
277        self.inner.roots.clone()
278    }
279
280    fn node_bounds_in_asset_space(
281        &self,
282        node_index: usize,
283        asset_from_parent: Transform,
284    ) -> Option<Aabb> {
285        let node = self.inner.nodes.get(node_index)?;
286        let asset_from_node = Transform::compose(asset_from_parent, node.transform);
287        let mut bounds = node
288            .meshes
289            .iter()
290            .filter_map(|mesh| mesh_bounds_in_node_space(mesh, &node.instance_transforms))
291            .map(|bounds| transform_aabb(bounds, asset_from_node))
292            .reduce(union_aabb);
293
294        for child in &node.children {
295            if let Some(child_bounds) = self.node_bounds_in_asset_space(*child, asset_from_node) {
296                bounds =
297                    Some(bounds.map_or(child_bounds, |bounds| union_aabb(bounds, child_bounds)));
298            }
299        }
300        bounds
301    }
302
303    fn source_units_summary(&self) -> Vec<SourceUnits> {
304        let mut units = BTreeSet::new();
305        for node in &self.inner.nodes {
306            for anchor in &node.anchors {
307                if let Some(source_units) = anchor.source_units() {
308                    units.insert(source_units);
309                }
310            }
311        }
312        units.into_iter().collect()
313    }
314
315    fn source_coordinate_system_summary(&self) -> Vec<SourceCoordinateSystem> {
316        let mut coordinate_systems = BTreeSet::new();
317        coordinate_systems.insert(SourceCoordinateSystem::GltfYUpRightHanded);
318        coordinate_systems.into_iter().collect()
319    }
320}
321
322fn mesh_bounds_in_node_space(mesh: &SceneAssetMesh, instances: &[Transform]) -> Option<Aabb> {
323    if instances.is_empty() {
324        return Some(mesh.bounds);
325    }
326    instances
327        .iter()
328        .map(|instance| transform_aabb(mesh.bounds, *instance))
329        .reduce(union_aabb)
330}
331
332impl PartialEq for SceneAsset {
333    fn eq(&self, other: &Self) -> bool {
334        Arc::ptr_eq(&self.inner, &other.inner) || self.inner.path == other.inner.path
335    }
336}
337
338impl Eq for SceneAsset {}
339
340impl SceneAssetNode {
341    pub fn name(&self) -> Option<&str> {
342        self.name.as_deref()
343    }
344
345    pub fn children(&self) -> &[usize] {
346        &self.children
347    }
348
349    pub fn transform(&self) -> Transform {
350        self.transform
351    }
352
353    pub fn mesh(&self) -> Option<&SceneAssetMesh> {
354        self.meshes.first()
355    }
356
357    pub fn meshes(&self) -> &[SceneAssetMesh] {
358        &self.meshes
359    }
360
361    pub fn instance_transforms(&self) -> &[Transform] {
362        &self.instance_transforms
363    }
364
365    pub const fn skin(&self) -> Option<usize> {
366        self.skin
367    }
368
369    pub fn light(&self) -> Option<SceneAssetLight> {
370        self.light
371    }
372
373    pub fn anchors(&self) -> &[SceneAssetAnchor] {
374        &self.anchors
375    }
376
377    pub fn connectors(&self) -> &[SceneAssetConnector] {
378        &self.connectors
379    }
380}
381
382impl SceneAssetMesh {
383    pub const fn geometry(&self) -> GeometryHandle {
384        self.geometry
385    }
386
387    pub const fn material(&self) -> MaterialHandle {
388        self.material
389    }
390
391    pub const fn bounds(&self) -> Aabb {
392        self.bounds
393    }
394
395    pub const fn uses_vertex_colors(&self) -> bool {
396        self.uses_vertex_colors
397    }
398
399    pub fn morph_weights(&self) -> &[f32] {
400        &self.morph_weights
401    }
402
403    pub fn material_variant_bindings(&self) -> &[MaterialVariantBinding] {
404        &self.material_variant_bindings
405    }
406}
407
408impl SceneAssetLight {
409    pub const fn light(self) -> Light {
410        self.light
411    }
412}
413
414impl SceneAssetClip {
415    pub fn name(&self) -> Option<&str> {
416        self.clip.name()
417    }
418
419    pub fn channels(&self) -> &[AnimationSourceChannel] {
420        self.clip.channels()
421    }
422
423    pub const fn duration_seconds(&self) -> f32 {
424        self.clip.duration_seconds()
425    }
426
427    pub(crate) fn clip(&self) -> &AnimationSourceClip {
428        &self.clip
429    }
430}