Skip to main content

scena/scene/
import.rs

1use std::collections::BTreeSet;
2use std::sync::atomic::AtomicBool;
3use std::sync::{Arc, Mutex};
4
5use self::bounds::union_optional;
6use self::diagnostic_overlays::diagnostic_overlay;
7use self::handedness::reject_unproven_left_handed_mesh_import;
8use self::types::{ImportBuild, ImportedNode, PendingSkinBinding, mesh_node_kind};
9use self::units::convert_marker_units;
10pub(super) use self::variants::MeshVariantRecord;
11use super::transforms::compose_transform;
12use super::{
13    ConnectorMetadata, ConnectorPolarity, ConnectorRollPolicy, NodeKey, NodeKind, Scene,
14    SceneSkinBinding, Transform,
15};
16use crate::animation::{AnimationClip, AnimationClipKey};
17use crate::assets::SceneAsset;
18use crate::diagnostics::{ImportDiagnosticOverlay, ImportDiagnosticOverlayKind, InstantiateError};
19
20mod accessors;
21mod bounds;
22mod diagnostic_overlays;
23mod handedness;
24mod load;
25mod lookups;
26mod options;
27mod types;
28mod units;
29mod variants;
30
31#[derive(Debug, Clone)]
32pub struct SceneImport {
33    roots: Vec<NodeKey>,
34    records: Vec<ImportedNode>,
35    anchors: Vec<ImportAnchor>,
36    connectors: Vec<ImportConnector>,
37    clips: Vec<ImportClip>,
38    diagnostic_overlays: Vec<ImportDiagnosticOverlay>,
39    source_units: SourceUnits,
40    source_coordinate_system: SourceCoordinateSystem,
41    live: Arc<AtomicBool>,
42    // Phase 2B step 3: KHR_materials_variants runtime state.
43    pub(super) material_variants: Vec<String>,
44    pub(super) active_variant: Arc<Mutex<Option<u32>>>,
45    pub(super) variant_records: Vec<MeshVariantRecord>,
46}
47
48#[derive(Debug, Clone)]
49pub struct ImportAnchor {
50    name: String,
51    node: NodeKey,
52    placement_node: NodeKey,
53    transform: Transform,
54    placement_transform: Transform,
55    tags: BTreeSet<String>,
56    label: Option<String>,
57    source_units: SourceUnits,
58    source_coordinate_system: SourceCoordinateSystem,
59    live: Arc<AtomicBool>,
60}
61
62#[derive(Debug, Clone)]
63pub struct ImportConnector {
64    name: String,
65    kind: Option<String>,
66    allowed_mates: Vec<String>,
67    tags: BTreeSet<String>,
68    snap_tolerance: Option<f32>,
69    clearance_hint: Option<f32>,
70    roll_policy: ConnectorRollPolicy,
71    polarity: Option<ConnectorPolarity>,
72    metadata: Option<ConnectorMetadata>,
73    node: NodeKey,
74    placement_node: NodeKey,
75    transform: Transform,
76    placement_transform: Transform,
77    source_units: SourceUnits,
78    source_coordinate_system: SourceCoordinateSystem,
79    live: Arc<AtomicBool>,
80}
81
82#[derive(Debug, Clone, PartialEq)]
83pub struct ImportAnchorDebugMetadata {
84    name: String,
85    node: NodeKey,
86    transform: Transform,
87}
88
89#[derive(Debug, Clone, PartialEq)]
90pub struct ImportClip {
91    clip: AnimationClip,
92}
93
94#[derive(Debug, Clone, PartialEq)]
95pub struct ImportPivot {
96    name: Option<String>,
97    node: NodeKey,
98    transform: Transform,
99}
100
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
102pub struct ImportOptions {
103    source_units: SourceUnits,
104    source_coordinate_system: SourceCoordinateSystem,
105}
106
107#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
108pub enum SourceUnits {
109    #[default]
110    Meters,
111    Centimeters,
112    Millimeters,
113    Inches,
114    Feet,
115}
116
117#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
118pub enum SourceCoordinateSystem {
119    #[default]
120    GltfYUpRightHanded,
121    YUpLeftHanded,
122    ZUpRightHanded,
123    ZUpLeftHanded,
124}
125
126impl Scene {
127    pub fn instantiate(
128        &mut self,
129        scene_asset: &SceneAsset,
130    ) -> Result<SceneImport, InstantiateError> {
131        self.instantiate_with(scene_asset, ImportOptions::gltf_default())
132    }
133
134    pub fn instantiate_with(
135        &mut self,
136        scene_asset: &SceneAsset,
137        options: ImportOptions,
138    ) -> Result<SceneImport, InstantiateError> {
139        reject_unproven_left_handed_mesh_import(scene_asset, options)?;
140        let nodes = scene_asset.nodes();
141        let mut child_indices = BTreeSet::new();
142        for node in nodes {
143            child_indices.extend(node.children().iter().copied());
144        }
145
146        let roots = (0..nodes.len())
147            .filter(|index| !child_indices.contains(index))
148            .collect::<Vec<_>>();
149        let mut import = SceneImport {
150            roots: Vec::new(),
151            records: Vec::new(),
152            anchors: Vec::new(),
153            connectors: Vec::new(),
154            clips: Vec::new(),
155            diagnostic_overlays: Vec::new(),
156            source_units: options.source_units(),
157            source_coordinate_system: options.source_coordinate_system(),
158            live: Arc::new(AtomicBool::new(true)),
159            material_variants: scene_asset.material_variants().to_vec(),
160            active_variant: Arc::new(Mutex::new(None)),
161            variant_records: Vec::new(),
162        };
163        let mut pending_skin_bindings = Vec::new();
164        for source_index in roots {
165            let mut build = ImportBuild {
166                scene_asset,
167                options,
168                import_live: &import.live,
169                records: &mut import.records,
170                anchors: &mut import.anchors,
171                connectors: &mut import.connectors,
172                diagnostic_overlays: &mut import.diagnostic_overlays,
173                pending_skin_bindings: &mut pending_skin_bindings,
174                variant_records: &mut import.variant_records,
175            };
176            let node = self.instantiate_scene_asset_node(
177                source_index,
178                self.root,
179                None,
180                None,
181                Transform::IDENTITY,
182                &mut build,
183            )?;
184            import.roots.push(node);
185        }
186        self.resolve_import_skin_bindings(
187            scene_asset,
188            &import.records,
189            pending_skin_bindings.as_slice(),
190        )?;
191        import.clips = scene_asset
192            .clips()
193            .iter()
194            .map(|clip| {
195                let rebased = clip.clip().rebind(
196                    AnimationClipKey::fresh(),
197                    |source_index| {
198                        import
199                            .records
200                            .iter()
201                            .find(|record| record.source_index == source_index)
202                            .map(|record| record.node)
203                    },
204                    |target, value| options.convert_animation_vec3(target, value),
205                );
206                ImportClip { clip: rebased }
207            })
208            .collect();
209        Ok(import)
210    }
211
212    fn instantiate_scene_asset_node(
213        &mut self,
214        source_index: usize,
215        parent: NodeKey,
216        imported_parent: Option<NodeKey>,
217        import_root: Option<NodeKey>,
218        root_from_parent: Transform,
219        build: &mut ImportBuild<'_>,
220    ) -> Result<NodeKey, InstantiateError> {
221        let source_node = build.scene_asset.nodes().get(source_index).ok_or(
222            InstantiateError::InvalidChildIndex {
223                parent: source_index,
224                child: source_index,
225            },
226        )?;
227        let transform = build.options.convert_transform(source_node.transform());
228        let meshes = source_node.meshes();
229        let skin = source_node.skin();
230        let bounds = meshes.iter().fold(None, |bounds, mesh| {
231            Some(union_optional(bounds, mesh.bounds()))
232        });
233        let node = match (meshes, source_node.light()) {
234            ([mesh], _) => {
235                let node = self.insert_node(parent, mesh_node_kind(mesh), transform);
236                if let Ok(node) = node {
237                    self.set_initial_morph_weights(node, mesh.morph_weights());
238                    if let Some(skin) = skin {
239                        build.pending_skin_bindings.push(PendingSkinBinding {
240                            node,
241                            source_node: source_index,
242                            skin,
243                        });
244                    }
245                    if !mesh.material_variant_bindings().is_empty() {
246                        build.variant_records.push(MeshVariantRecord {
247                            node,
248                            default_material: mesh.material(),
249                            bindings: mesh.material_variant_bindings().to_vec(),
250                        });
251                    }
252                }
253                node
254            }
255            ([_, _, ..], _) => {
256                let node = self.insert_node(parent, NodeKind::Empty, transform);
257                if let Ok(parent) = node {
258                    for mesh in meshes {
259                        let child = self
260                            .insert_node(parent, mesh_node_kind(mesh), Transform::IDENTITY)
261                            .expect("multi-primitive parent was inserted by this scene");
262                        self.node_bounds.insert(child, mesh.bounds());
263                        self.set_initial_morph_weights(child, mesh.morph_weights());
264                        if let Some(skin) = skin {
265                            build.pending_skin_bindings.push(PendingSkinBinding {
266                                node: child,
267                                source_node: source_index,
268                                skin,
269                            });
270                        }
271                        if !mesh.material_variant_bindings().is_empty() {
272                            build.variant_records.push(MeshVariantRecord {
273                                node: child,
274                                default_material: mesh.material(),
275                                bindings: mesh.material_variant_bindings().to_vec(),
276                            });
277                        }
278                    }
279                }
280                node
281            }
282            ([], Some(light)) => match light.light() {
283                super::Light::Directional(light) => self
284                    .directional_light(light)
285                    .parent(parent)
286                    .transform(transform)
287                    .add(),
288                super::Light::Point(light) => self
289                    .point_light(light)
290                    .parent(parent)
291                    .transform(transform)
292                    .add(),
293                super::Light::Spot(light) => self
294                    .spot_light(light)
295                    .parent(parent)
296                    .transform(transform)
297                    .add(),
298            },
299            ([], None) => self.insert_node(parent, NodeKind::Empty, transform),
300        }
301        .expect("import parent was inserted by this scene");
302        build.records.push(ImportedNode {
303            source_index,
304            node,
305            parent: imported_parent,
306            name: source_node.name().map(str::to_string),
307            bounds,
308        });
309        let placement_node = import_root.unwrap_or(node);
310        let root_from_node = match import_root {
311            Some(_) => compose_transform(root_from_parent, transform),
312            None => Transform::IDENTITY,
313        };
314        let label = source_node.name().map(str::to_string);
315        let overlay_options = build.options;
316        build.diagnostic_overlays.push(diagnostic_overlay(
317            overlay_options,
318            ImportDiagnosticOverlayKind::Origin,
319            node,
320            transform,
321            None,
322            label.clone(),
323        ));
324        build.diagnostic_overlays.push(diagnostic_overlay(
325            overlay_options,
326            ImportDiagnosticOverlayKind::Axes,
327            node,
328            transform,
329            None,
330            label.clone(),
331        ));
332        if let Some(bounds) = bounds {
333            self.node_bounds.insert(node, bounds);
334            build.diagnostic_overlays.push(diagnostic_overlay(
335                overlay_options,
336                ImportDiagnosticOverlayKind::Bounds,
337                node,
338                Transform::IDENTITY,
339                Some(bounds),
340                label.clone(),
341            ));
342        }
343        let mut anchor_names = BTreeSet::new();
344        for anchor in source_node.anchors() {
345            if let Some(reason) = anchor.invalid_reason() {
346                return Err(InstantiateError::InvalidAnchorExtras {
347                    node: source_node.name().unwrap_or("<unnamed>").to_string(),
348                    reason: reason.to_string(),
349                });
350            }
351            if !anchor_names.insert(anchor.name()) {
352                return Err(InstantiateError::InvalidAnchorExtras {
353                    node: source_node.name().unwrap_or("<unnamed>").to_string(),
354                    reason: format!("duplicate anchor '{}'", anchor.name()),
355                });
356            }
357            let anchor_units = anchor
358                .source_units()
359                .unwrap_or(build.options.source_units());
360            let anchor_transform = convert_marker_units(
361                anchor.transform(),
362                anchor_units,
363                build.options.source_units(),
364            );
365            let anchor_connection_transform = build
366                .options
367                .source_coordinate_system()
368                .convert_connector_transform(anchor_transform);
369            build.anchors.push(ImportAnchor {
370                name: anchor.name().to_string(),
371                node,
372                placement_node,
373                transform: anchor_transform,
374                placement_transform: compose_transform(root_from_node, anchor_connection_transform),
375                tags: anchor.tags().clone(),
376                label: anchor.label().map(str::to_string),
377                source_units: anchor_units,
378                source_coordinate_system: build.options.source_coordinate_system(),
379                live: Arc::clone(build.import_live),
380            });
381            build.diagnostic_overlays.push(diagnostic_overlay(
382                overlay_options,
383                ImportDiagnosticOverlayKind::Anchor,
384                node,
385                anchor_transform,
386                None,
387                Some(anchor.name().to_string()),
388            ));
389            if anchor.name() == "pivot" {
390                build.diagnostic_overlays.push(diagnostic_overlay(
391                    overlay_options,
392                    ImportDiagnosticOverlayKind::Pivot,
393                    node,
394                    anchor_transform,
395                    None,
396                    Some(anchor.name().to_string()),
397                ));
398            }
399        }
400        let mut connector_names = BTreeSet::new();
401        for connector in source_node.connectors() {
402            if let Some(reason) = connector.invalid_reason() {
403                return Err(InstantiateError::InvalidAnchorExtras {
404                    node: source_node.name().unwrap_or("<unnamed>").to_string(),
405                    reason: reason.to_string(),
406                });
407            }
408            if !connector_names.insert(connector.name()) {
409                return Err(InstantiateError::InvalidAnchorExtras {
410                    node: source_node.name().unwrap_or("<unnamed>").to_string(),
411                    reason: format!("duplicate connector '{}'", connector.name()),
412                });
413            }
414            let connector_transform = connector.transform();
415            let connector_connection_transform = build
416                .options
417                .source_coordinate_system()
418                .convert_connector_transform(connector_transform);
419            build.connectors.push(ImportConnector {
420                name: connector.name().to_string(),
421                kind: connector.kind().map(str::to_string),
422                allowed_mates: connector
423                    .allowed_mates()
424                    .into_iter()
425                    .map(str::to_string)
426                    .collect(),
427                tags: connector.tags().clone(),
428                snap_tolerance: connector.snap_tolerance(),
429                clearance_hint: connector.clearance_hint(),
430                roll_policy: connector.roll_policy(),
431                polarity: connector.polarity(),
432                metadata: connector.metadata().cloned(),
433                node,
434                placement_node,
435                transform: connector_transform,
436                placement_transform: compose_transform(
437                    root_from_node,
438                    connector_connection_transform,
439                ),
440                source_units: build.options.source_units(),
441                source_coordinate_system: build.options.source_coordinate_system(),
442                live: Arc::clone(build.import_live),
443            });
444            build.diagnostic_overlays.push(diagnostic_overlay(
445                overlay_options,
446                ImportDiagnosticOverlayKind::Connector,
447                node,
448                connector_transform,
449                None,
450                Some(connector.name().to_string()),
451            ));
452        }
453        for child in source_node.children() {
454            if build.scene_asset.nodes().get(*child).is_none() {
455                return Err(InstantiateError::InvalidChildIndex {
456                    parent: source_index,
457                    child: *child,
458                });
459            }
460            self.instantiate_scene_asset_node(
461                *child,
462                node,
463                Some(node),
464                Some(placement_node),
465                root_from_node,
466                build,
467            )?;
468        }
469        Ok(node)
470    }
471
472    fn resolve_import_skin_bindings(
473        &mut self,
474        scene_asset: &SceneAsset,
475        records: &[ImportedNode],
476        pending: &[PendingSkinBinding],
477    ) -> Result<(), InstantiateError> {
478        for pending in pending {
479            let skin = scene_asset.skins().get(pending.skin).ok_or(
480                InstantiateError::InvalidSkinIndex {
481                    node: pending.source_node,
482                    skin: pending.skin,
483                },
484            )?;
485            let joints = skin
486                .joints()
487                .iter()
488                .map(|source_joint| {
489                    records
490                        .iter()
491                        .find(|record| record.source_index == *source_joint)
492                        .map(|record| record.node)
493                        .ok_or(InstantiateError::InvalidSkinJointIndex {
494                            skin: pending.skin,
495                            joint: *source_joint,
496                        })
497                })
498                .collect::<Result<Vec<_>, _>>()?;
499            self.set_initial_skin_binding(
500                pending.node,
501                SceneSkinBinding::new(joints, skin.inverse_bind_matrices().to_vec()),
502            );
503        }
504        Ok(())
505    }
506}