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 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}