fyrox_impl/resource/model/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21#![warn(missing_docs)]
22
23//! Contains all data structures and method to work with model resources.
24//!
25//! Model is an isolated scene that is used to create copies of its data - this
26//! process is known as `instantiation`. Isolation in this context means that
27//! such scene cannot be modified, rendered, etc. It just a data source.
28//!
29//! All instances will have references to resource they were created from - this
30//! will help to get correct vertex and indices buffers when loading a save file,
31//! loader will just take all needed data from resource so we don't need to store
32//! such data in save file. Also this mechanism works perfectly when you changing
33//! resource in external editor (3Ds max, Maya, Blender, etc.) engine will assign
34//! correct visual data when loading a saved game.
35//!
36//! # Supported formats
37//!
38//! Currently only FBX (common format in game industry for storing complex 3d models),
39//! RGS (native Fyroxed format), GLTF formats are supported.
40
41use crate::{
42    asset::{
43        io::ResourceIo, manager::ResourceManager, options::ImportOptions, untyped::ResourceKind,
44        Resource, ResourceData, MODEL_RESOURCE_UUID,
45    },
46    core::{
47        algebra::{UnitQuaternion, Vector3},
48        log::{Log, MessageKind},
49        pool::Handle,
50        reflect::prelude::*,
51        uuid::Uuid,
52        uuid_provider,
53        variable::InheritableVariable,
54        visitor::{Visit, VisitResult, Visitor},
55        NameProvider, TypeUuidProvider,
56    },
57    engine::SerializationContext,
58    generic_animation::AnimationContainer,
59    graph::{BaseSceneGraph, NodeHandleMap, NodeMapping, PrefabData, SceneGraph, SceneGraphNode},
60    resource::fbx::{self, error::FbxError},
61    scene::{
62        animation::Animation, base::SceneNodeId, graph::Graph, node::Node, transform::Transform,
63        Scene, SceneLoader,
64    },
65};
66use fxhash::FxHashMap;
67use fyrox_core::algebra::Point3;
68use fyrox_core::math;
69use fyrox_core::visitor::error::VisitError;
70use fyrox_ui::{UiNode, UserInterface};
71use serde::{Deserialize, Serialize};
72use std::{
73    error::Error,
74    fmt::{Display, Formatter},
75    path::{Path, PathBuf},
76    sync::Arc,
77};
78use strum_macros::{AsRefStr, EnumString, VariantNames};
79
80pub mod loader;
81
82/// See module docs.
83#[derive(Debug, Clone, Visit, Reflect)]
84pub struct Model {
85    #[visit(skip)]
86    pub(crate) mapping: NodeMapping,
87    #[visit(skip)]
88    pub(crate) scene: Scene,
89}
90
91impl PrefabData for Model {
92    type Graph = Graph;
93
94    #[inline]
95    fn graph(&self) -> &Self::Graph {
96        &self.scene.graph
97    }
98
99    #[inline]
100    fn mapping(&self) -> NodeMapping {
101        self.mapping
102    }
103}
104
105impl TypeUuidProvider for Model {
106    fn type_uuid() -> Uuid {
107        MODEL_RESOURCE_UUID
108    }
109}
110
111/// Instantiation context holds additional data that could be useful for a prefab instantiation.
112pub struct InstantiationContext<'a> {
113    model: &'a ModelResource,
114    dest_scene: &'a mut Scene,
115    local_transform: Option<Transform>,
116    ids: Option<&'a FxHashMap<Handle<Node>, SceneNodeId>>,
117}
118
119impl<'a> InstantiationContext<'a> {
120    /// Sets the desired local rotation for the instance.
121    pub fn with_rotation(mut self, rotation: UnitQuaternion<f32>) -> Self {
122        self.local_transform
123            .get_or_insert_with(Default::default)
124            .set_rotation(rotation);
125        self
126    }
127
128    /// Sets the desired local position for the instance.
129    pub fn with_position(mut self, position: Vector3<f32>) -> Self {
130        self.local_transform
131            .get_or_insert_with(Default::default)
132            .set_position(position);
133        self
134    }
135
136    /// Sets the desired local scaling for the instance.
137    pub fn with_scale(mut self, scale: Vector3<f32>) -> Self {
138        self.local_transform
139            .get_or_insert_with(Default::default)
140            .set_scale(scale);
141        self
142    }
143
144    /// Sets the desired local transform for the instance.
145    pub fn with_transform(mut self, transform: Transform) -> Self {
146        self.local_transform = Some(transform);
147        self
148    }
149
150    /// Instantiates a prefab and assigns the given set of ids to the respective instances. The given
151    /// hash map should contain pairs `(OriginalHandle, SceneNodeId)`. Original handle is a handle
152    /// of a node from the prefab itself, not the instance handle! Use this method in pair with
153    /// [`ModelResourceExtension::generate_ids`].
154    ///
155    /// This method should be used only if you need to instantiate an object on multiple clients in
156    /// a multiplayer game with client-server model. This method ensures that the instances will
157    /// have the same ids across all clients.
158    pub fn with_ids(mut self, ids: &'a FxHashMap<Handle<Node>, SceneNodeId>) -> Self {
159        self.ids = Some(ids);
160        self
161    }
162
163    /// Finishes instantiation.
164    pub fn finish(self) -> Handle<Node> {
165        let model = self.model.clone();
166        let data = model.data_ref();
167
168        let resource_root = data.scene.graph.get_root();
169        let (root, _) = ModelResource::instantiate_from(
170            model.clone(),
171            &data,
172            resource_root,
173            &mut self.dest_scene.graph,
174            &mut |original_handle, node| {
175                if original_handle == resource_root {
176                    if let Some(transform) = self.local_transform.clone() {
177                        *node.local_transform_mut() = transform;
178                    }
179                }
180
181                if let Some(ids) = self.ids.as_ref() {
182                    if let Some(id) = ids.get(&original_handle) {
183                        node.instance_id = *id;
184                    } else {
185                        Log::warn(format!(
186                            "No id specified for node {original_handle}! Random id will be used."
187                        ))
188                    }
189                }
190            },
191        );
192
193        // Explicitly mark as root node.
194        self.dest_scene.graph[root].is_resource_instance_root = true;
195
196        root
197    }
198}
199
200/// Common trait that has animation retargetting methods.
201pub trait AnimationSource {
202    /// Prefab type.
203    type Prefab: PrefabData<Graph = Self::SceneGraph>;
204    /// Scene graph type.
205    type SceneGraph: SceneGraph<Node = Self::Node, Prefab = Self::Prefab>;
206    /// Scene node type.
207    type Node: SceneGraphNode<SceneGraph = Self::SceneGraph, ResourceData = Self::Prefab>;
208
209    /// Returns a reference to an inner graph.
210    fn inner_graph(&self) -> &Self::SceneGraph;
211
212    /// Tries to retarget animations from given model resource to a node hierarchy starting
213    /// from `root` on a given scene.
214    ///
215    /// Animation retargeting allows you to "transfer" animation from a model to a model
216    /// instance on a scene. Imagine you have a character that should have multiple animations
217    /// like idle, run, shoot, walk, etc. and you want to store each animation in a separate
218    /// file. Then when you creating a character on a level you want to have all possible
219    /// animations assigned to a character, this is where this function comes into play:
220    /// you just load a model of your character with skeleton, but without any animations,
221    /// then you load several "models" which have only skeleton with some animation (such
222    /// "models" can be considered as "animation" resources). After this you need to
223    /// instantiate model on your level and retarget all animations you need to that instance
224    /// from other "models". All you have after this is a handle to a model and bunch of
225    /// handles to specific animations. After this animations can be blended in any combinations
226    /// you need to. For example idle animation can be blended with walk animation when your
227    /// character starts walking.
228    ///
229    /// # Notes
230    ///
231    /// Most of the 3d model formats can contain only one animation, so in most cases
232    /// this function will return vector with only one animation.
233    fn retarget_animations_directly(
234        &self,
235        root: Handle<Self::Node>,
236        graph: &Self::SceneGraph,
237        self_kind: ResourceKind,
238    ) -> Vec<fyrox_animation::Animation<Handle<Self::Node>>> {
239        let mut retargetted_animations = Vec::new();
240
241        let model_graph = self.inner_graph();
242        for src_node_ref in model_graph.linear_iter() {
243            if let Some(src_animations) = src_node_ref
244                .component_ref::<InheritableVariable<AnimationContainer<Handle<Self::Node>>>>()
245            {
246                for src_anim in src_animations.iter() {
247                    let mut anim_copy = src_anim.clone();
248
249                    // Remap animation track nodes from resource to instance. This is required
250                    // because we've made a plain copy and it has tracks with node handles mapped
251                    // to nodes of internal scene.
252                    let src_anim_track_data = src_anim.tracks_data().state();
253                    let Some(src_anim_track_data) = src_anim_track_data.data_ref() else {
254                        continue;
255                    };
256
257                    for ref_track in src_anim_track_data.tracks.iter() {
258                        let Some(ref_track_binding) =
259                            src_anim.track_bindings().get(&ref_track.id())
260                        else {
261                            continue;
262                        };
263
264                        let ref_node = &model_graph.node(ref_track_binding.target);
265                        let track_binding = anim_copy
266                            .track_bindings_mut()
267                            .get_mut(&ref_track.id())
268                            .unwrap();
269                        // Find instantiated node that corresponds to node in resource
270                        match graph.find_by_name(root, ref_node.name()) {
271                            Some((instance_node, _)) => {
272                                track_binding.set_target(instance_node);
273                            }
274                            None => {
275                                track_binding.set_target(Default::default());
276                                Log::writeln(
277                                    MessageKind::Error,
278                                    format!(
279                                        "Failed to retarget animation {:?} for node {}",
280                                        self_kind,
281                                        ref_node.name()
282                                    ),
283                                );
284                            }
285                        }
286                    }
287
288                    retargetted_animations.push(anim_copy);
289                }
290            }
291        }
292
293        retargetted_animations
294    }
295
296    /// Tries to retarget animations from given model resource to a node hierarchy starting
297    /// from `root` on a given scene. Unlike [`Self::retarget_animations_directly`], it automatically
298    /// adds retargetted animations to the specified animation player in the hierarchy of given `root`.
299    ///
300    /// # Panic
301    ///
302    /// Panics if `dest_animation_player` is invalid handle, or the node does not have [`AnimationContainer`]
303    /// component.
304    fn retarget_animations_to_player(
305        &self,
306        root: Handle<Self::Node>,
307        dest_animation_player: Handle<Self::Node>,
308        graph: &mut Self::SceneGraph,
309        self_kind: ResourceKind,
310    ) -> Vec<Handle<fyrox_animation::Animation<Handle<Self::Node>>>> {
311        let mut animation_handles = Vec::new();
312
313        let animations = self.retarget_animations_directly(root, graph, self_kind);
314
315        let dest_animations = graph
316            .node_mut(dest_animation_player)
317            .component_mut::<InheritableVariable<AnimationContainer<Handle<Self::Node>>>>()
318            .unwrap();
319
320        for animation in animations {
321            animation_handles.push(dest_animations.add(animation));
322        }
323
324        animation_handles
325    }
326
327    /// Tries to retarget animations from given model resource to a node hierarchy starting
328    /// from `root` on a given scene. Unlike [`Self::retarget_animations_directly`], it automatically
329    /// adds retargetted animations to a first animation player in the hierarchy of given `root`.
330    ///
331    /// # Panic
332    ///
333    /// Panics if there's no animation player in the given hierarchy (descendant nodes of `root`).
334    fn retarget_animations(
335        &self,
336        root: Handle<Self::Node>,
337        graph: &mut Self::SceneGraph,
338        self_kind: ResourceKind,
339    ) -> Vec<Handle<fyrox_animation::Animation<Handle<Self::Node>>>> {
340        if let Some((animation_player, _)) = graph.find(root, &mut |n| {
341            n.component_ref::<InheritableVariable<AnimationContainer<Handle<Self::Node>>>>()
342                .is_some()
343        }) {
344            self.retarget_animations_to_player(root, animation_player, graph, self_kind)
345        } else {
346            Default::default()
347        }
348    }
349}
350
351/// Type alias for model resources.
352pub type ModelResource = Resource<Model>;
353
354/// Extension trait for model resources.
355pub trait ModelResourceExtension: Sized {
356    /// Tries to instantiate model from given resource. This is for internal use only and does not
357    /// set `is_resource_instance_root`.
358    fn instantiate_from<Pre>(
359        model: ModelResource,
360        model_data: &Model,
361        handle: Handle<Node>,
362        dest_graph: &mut Graph,
363        pre_processing_callback: &mut Pre,
364    ) -> (Handle<Node>, NodeHandleMap<Node>)
365    where
366        Pre: FnMut(Handle<Node>, &mut Node);
367
368    /// Begins instantiation of the model.
369    fn begin_instantiation<'a>(&'a self, dest_scene: &'a mut Scene) -> InstantiationContext<'a>;
370
371    /// Tries to instantiate model from given resource.
372    fn instantiate(&self, dest_scene: &mut Scene) -> Handle<Node>;
373
374    /// Instantiates a prefab and places it at specified position and orientation in global coordinates.
375    fn instantiate_at(
376        &self,
377        scene: &mut Scene,
378        position: Vector3<f32>,
379        orientation: UnitQuaternion<f32>,
380    ) -> Handle<Node>;
381
382    /// Instantiates a prefab and places it at specified position, orientation, scale in global
383    /// coordinates and attaches it to the specified parent. This method automatically calculates
384    /// required local position, rotation, scaling for the instance so it will remain at the same
385    /// place in world space after attachment. This method could be useful if you need to instantiate
386    /// an object at some other object.
387    fn instantiate_and_attach(
388        &self,
389        scene: &mut Scene,
390        parent: Handle<Node>,
391        position: Vector3<f32>,
392        face_towards: Vector3<f32>,
393        scale: Vector3<f32>,
394    ) -> Handle<Node>;
395
396    /// Tries to retarget animations from given model resource to a node hierarchy starting
397    /// from `root` on a given scene.
398    ///
399    /// Animation retargeting allows you to "transfer" animation from a model to a model
400    /// instance on a scene. Imagine you have a character that should have multiple animations
401    /// like idle, run, shoot, walk, etc. and you want to store each animation in a separate
402    /// file. Then when you creating a character on a level you want to have all possible
403    /// animations assigned to a character, this is where this function comes into play:
404    /// you just load a model of your character with skeleton, but without any animations,
405    /// then you load several "models" which have only skeleton with some animation (such
406    /// "models" can be considered as "animation" resources). After this you need to
407    /// instantiate model on your level and retarget all animations you need to that instance
408    /// from other "models". All you have after this is a handle to a model and bunch of
409    /// handles to specific animations. After this animations can be blended in any combinations
410    /// you need to. For example idle animation can be blended with walk animation when your
411    /// character starts walking.
412    ///
413    /// # Notes
414    ///
415    /// Most of the 3d model formats can contain only one animation, so in most cases
416    /// this function will return vector with only one animation.
417    fn retarget_animations_directly(&self, root: Handle<Node>, graph: &Graph) -> Vec<Animation>;
418
419    /// Tries to retarget animations from given model resource to a node hierarchy starting
420    /// from `root` on a given scene. Unlike [`Self::retarget_animations_directly`], it automatically
421    /// adds retargetted animations to the specified animation player in the hierarchy of given `root`.
422    ///
423    /// # Panic
424    ///
425    /// Panics if `dest_animation_player` is invalid handle, or the node does not have [`AnimationContainer`]
426    /// component.
427    fn retarget_animations_to_player(
428        &self,
429        root: Handle<Node>,
430        dest_animation_player: Handle<Node>,
431        graph: &mut Graph,
432    ) -> Vec<Handle<Animation>>;
433
434    /// Tries to retarget animations from given model resource to a node hierarchy starting
435    /// from `root` on a given scene. Unlike [`Self::retarget_animations_directly`], it automatically
436    /// adds retargetted animations to a first animation player in the hierarchy of given `root`.
437    ///
438    /// # Panic
439    ///
440    /// Panics if there's no animation player in the given hierarchy (descendant nodes of `root`).
441    fn retarget_animations(&self, root: Handle<Node>, graph: &mut Graph) -> Vec<Handle<Animation>>;
442
443    /// Generates a set of unique IDs for every node in the model. Use this method in pair with
444    /// [`ModelResource::begin_instantiation`].
445    fn generate_ids(&self) -> FxHashMap<Handle<Node>, SceneNodeId>;
446}
447
448impl AnimationSource for Model {
449    type Prefab = Model;
450    type SceneGraph = Graph;
451    type Node = Node;
452
453    fn inner_graph(&self) -> &Self::SceneGraph {
454        &self.scene.graph
455    }
456}
457
458impl AnimationSource for UserInterface {
459    type Prefab = UserInterface;
460    type SceneGraph = UserInterface;
461    type Node = UiNode;
462
463    fn inner_graph(&self) -> &Self::SceneGraph {
464        self
465    }
466}
467
468impl ModelResourceExtension for ModelResource {
469    /// Copy the given model into the given graph.
470    /// * `model`: The resource handle of the model, which put into the created nodes as their
471    ///   [`Base::resource`](crate::scene::base::Base::resource) to indicate which model
472    ///   the node was instantiated from.
473    /// * `model_data`: A borrow of the data contained in `model`.
474    /// * `handle`: The handle of the node within `model` that should be instantiated, typically
475    ///   the root of the model.
476    /// * `dest_graph`: A mutable borrow of the node graph that will contain the newly created copy.
477    /// * `pre_processing_callback`: A function that takes a node handle and a mutable node.
478    ///   The handle belongs to the original node in the model. The node is a copy of the original
479    ///   node, except that parent handle and child handles have been removed. This is a node
480    ///   that will be inserted into `dest_graph`.
481    fn instantiate_from<Pre>(
482        model: ModelResource,
483        model_data: &Model,
484        handle: Handle<Node>,
485        dest_graph: &mut Graph,
486        pre_processing_callback: &mut Pre,
487    ) -> (Handle<Node>, NodeHandleMap<Node>)
488    where
489        Pre: FnMut(Handle<Node>, &mut Node),
490    {
491        let (root, old_to_new) = model_data.scene.graph.copy_node(
492            handle,
493            dest_graph,
494            &mut |_, _| true,
495            pre_processing_callback,
496            &mut |_, original_handle, node| {
497                node.set_inheritance_data(original_handle, model.clone());
498            },
499        );
500
501        dest_graph.update_hierarchical_data_for_descendants(root);
502
503        (root, old_to_new)
504    }
505
506    fn begin_instantiation<'a>(&'a self, dest_scene: &'a mut Scene) -> InstantiationContext<'a> {
507        if !self.is_ok() {
508            Log::err(format!(
509                "Instantiating a model from a resource that is not loaded: {self:?}"
510            ));
511        }
512        InstantiationContext {
513            model: self,
514            dest_scene,
515            local_transform: None,
516            ids: None,
517        }
518    }
519
520    fn instantiate(&self, dest_scene: &mut Scene) -> Handle<Node> {
521        self.begin_instantiation(dest_scene).finish()
522    }
523
524    fn instantiate_at(
525        &self,
526        scene: &mut Scene,
527        position: Vector3<f32>,
528        orientation: UnitQuaternion<f32>,
529    ) -> Handle<Node> {
530        self.begin_instantiation(scene)
531            .with_rotation(orientation)
532            .with_position(position)
533            .finish()
534    }
535
536    fn instantiate_and_attach(
537        &self,
538        scene: &mut Scene,
539        parent: Handle<Node>,
540        position: Vector3<f32>,
541        face_towards: Vector3<f32>,
542        scale: Vector3<f32>,
543    ) -> Handle<Node> {
544        let parent_scale = scene.graph.global_scale(parent);
545
546        let parent_inv_transform = scene.graph[parent]
547            .global_transform()
548            .try_inverse()
549            .unwrap_or_default();
550
551        let local_position = parent_inv_transform
552            .transform_point(&Point3::from(position))
553            .coords;
554
555        let local_rotation =
556            math::vector_to_quat(parent_inv_transform.transform_vector(&face_towards));
557
558        // Discard parent's scale.
559        let local_scale = scale.component_div(&parent_scale);
560
561        let instance = self
562            .begin_instantiation(scene)
563            .with_position(local_position)
564            .with_rotation(local_rotation)
565            .with_scale(local_scale)
566            .finish();
567
568        scene.graph.link_nodes(instance, parent);
569
570        instance
571    }
572
573    fn retarget_animations_directly(&self, root: Handle<Node>, graph: &Graph) -> Vec<Animation> {
574        let mut header = self.state();
575        let self_kind = header.kind();
576        if let Some(model) = header.data() {
577            model.retarget_animations_directly(root, graph, self_kind)
578        } else {
579            Default::default()
580        }
581    }
582
583    fn retarget_animations_to_player(
584        &self,
585        root: Handle<Node>,
586        dest_animation_player: Handle<Node>,
587        graph: &mut Graph,
588    ) -> Vec<Handle<Animation>> {
589        let mut header = self.state();
590        let self_kind = header.kind();
591        if let Some(model) = header.data() {
592            model.retarget_animations_to_player(root, dest_animation_player, graph, self_kind)
593        } else {
594            Default::default()
595        }
596    }
597
598    fn retarget_animations(&self, root: Handle<Node>, graph: &mut Graph) -> Vec<Handle<Animation>> {
599        let mut header = self.state();
600        let self_kind = header.kind();
601        if let Some(model) = header.data() {
602            model.retarget_animations(root, graph, self_kind)
603        } else {
604            Default::default()
605        }
606    }
607
608    fn generate_ids(&self) -> FxHashMap<Handle<Node>, SceneNodeId> {
609        let data = self.data_ref();
610        data.scene
611            .graph
612            .pair_iter()
613            .map(|(h, _)| (h, SceneNodeId(Uuid::new_v4())))
614            .collect()
615    }
616}
617
618impl ResourceData for Model {
619    fn type_uuid(&self) -> Uuid {
620        <Self as TypeUuidProvider>::type_uuid()
621    }
622
623    fn save(&mut self, path: &Path) -> Result<(), Box<dyn Error>> {
624        let mut visitor = Visitor::new();
625        self.scene.save("Scene", &mut visitor)?;
626        visitor.save_ascii_to_file(path)?;
627        Ok(())
628    }
629
630    fn can_be_saved(&self) -> bool {
631        true
632    }
633
634    fn try_clone_box(&self) -> Option<Box<dyn ResourceData>> {
635        Some(Box::new(self.clone()))
636    }
637}
638
639impl Default for Model {
640    fn default() -> Self {
641        Self {
642            mapping: NodeMapping::UseNames,
643            scene: Scene::new(),
644        }
645    }
646}
647
648/// Defines a way of searching materials when loading a model resource from foreign file format such as FBX.
649///
650/// # Motivation
651///
652/// Most 3d model file formats store paths to external resources (textures and other things) as absolute paths,
653/// which makes it impossible to use with "location-independent" application like games. To fix that issue, the
654/// engine provides few ways of resolving paths to external resources. The engine starts resolving by stripping
655/// everything but file name from an external resource's path, then it uses one of the following methods to find
656/// a texture with the file name. It could look up on folders hierarchy by using [`MaterialSearchOptions::RecursiveUp`]
657/// method, or even use global search starting from the working directory of your game
658/// ([`MaterialSearchOptions::WorkingDirectory`])
659#[derive(
660    Clone,
661    Debug,
662    Visit,
663    PartialEq,
664    Eq,
665    Deserialize,
666    Serialize,
667    Reflect,
668    AsRefStr,
669    EnumString,
670    VariantNames,
671)]
672pub enum MaterialSearchOptions {
673    /// Search in specified materials directory. It is suitable for cases when
674    /// your model resource use shared textures.
675    ///
676    /// # Platform specific
677    ///
678    /// Works on every platform.
679    MaterialsDirectory(PathBuf),
680
681    /// Recursive-up search. It is suitable for cases when textures are placed
682    /// near your model resource. This is **default** option.
683    ///
684    /// # Platform specific
685    ///
686    /// Works on every platform.
687    RecursiveUp,
688
689    /// Global search starting from working directory. Slowest option with a lot of ambiguities -
690    /// it may load unexpected file in cases when there are two or more files with same name but
691    /// lying in different directories.
692    ///
693    /// # Platform specific
694    ///
695    /// WebAssembly - **not supported** due to lack of file system.
696    WorkingDirectory,
697
698    /// Try to use paths stored in the model resource directly. This options has limited usage,
699    /// it is suitable to load animations, or any other model which does not have any materials.
700    ///
701    /// # Important notes
702    ///
703    /// RGS (native engine scenes) files should be loaded with this option by default, otherwise
704    /// the engine won't be able to correctly find materials.
705    UsePathDirectly,
706}
707
708uuid_provider!(MaterialSearchOptions = "11634aa0-cf8f-4532-a8cd-c0fa6ef804f1");
709
710impl Default for MaterialSearchOptions {
711    fn default() -> Self {
712        Self::RecursiveUp
713    }
714}
715
716impl MaterialSearchOptions {
717    /// A helper to create MaterialsDirectory variant.
718    pub fn materials_directory<P: AsRef<Path>>(path: P) -> Self {
719        Self::MaterialsDirectory(path.as_ref().to_path_buf())
720    }
721}
722
723/// A set of options that will be applied to a model resource when loading it from external source.
724///
725/// # Details
726///
727/// The engine has a convenient way of storing import options in a `.options` files. For example you may
728/// have a `foo.fbx` 3d model, to change import options create a new file with additional `.options`
729/// extension: `foo.fbx.options`. The content of an options file could be something like this:
730///
731/// ```text
732/// (
733///     material_search_options: RecursiveUp
734/// )
735/// ```
736///
737/// Check documentation of the field of the structure for more info about each parameter.
738#[derive(Clone, Debug, PartialEq, Serialize, Deserialize, Default, Reflect, Eq)]
739pub struct ModelImportOptions {
740    /// See [`MaterialSearchOptions`] docs for more info.
741    #[serde(default)]
742    pub material_search_options: MaterialSearchOptions,
743}
744
745impl ImportOptions for ModelImportOptions {}
746
747/// All possible errors that may occur while trying to load model from some
748/// data source.
749#[derive(Debug)]
750pub enum ModelLoadError {
751    /// An error occurred while reading a data source.
752    Visit(VisitError),
753    /// Format is not supported.
754    NotSupported(String),
755    /// An error occurred while loading FBX file.
756    Fbx(FbxError),
757}
758
759impl Display for ModelLoadError {
760    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
761        match self {
762            ModelLoadError::Visit(v) => {
763                write!(f, "An error occurred while reading a data source: {v}")
764            }
765            ModelLoadError::NotSupported(v) => {
766                write!(f, "Model format is not supported: {v}")
767            }
768            ModelLoadError::Fbx(v) => v.fmt(f),
769        }
770    }
771}
772
773impl From<FbxError> for ModelLoadError {
774    fn from(fbx: FbxError) -> Self {
775        ModelLoadError::Fbx(fbx)
776    }
777}
778
779impl From<VisitError> for ModelLoadError {
780    fn from(e: VisitError) -> Self {
781        ModelLoadError::Visit(e)
782    }
783}
784
785impl Model {
786    /// Creates a new Model instance using the given node mapping and the given scene. It could be
787    /// used to create your own Model resources.
788    pub fn new(mapping: NodeMapping, scene: Scene) -> Self {
789        Self { mapping, scene }
790    }
791
792    pub(crate) async fn load<P: AsRef<Path>>(
793        path: P,
794        io: &dyn ResourceIo,
795        serialization_context: Arc<SerializationContext>,
796        resource_manager: ResourceManager,
797        model_import_options: ModelImportOptions,
798    ) -> Result<Self, ModelLoadError> {
799        let extension = path
800            .as_ref()
801            .extension()
802            .unwrap_or_default()
803            .to_string_lossy()
804            .as_ref()
805            .to_lowercase();
806        let (scene, mapping) = match extension.as_ref() {
807            "fbx" => {
808                let mut scene = Scene::new();
809                if let Some(filename) = path.as_ref().file_name() {
810                    let root = scene.graph.get_root();
811                    scene.graph[root].set_name(filename.to_string_lossy());
812                }
813                fbx::load_to_scene(
814                    &mut scene,
815                    resource_manager,
816                    io,
817                    path.as_ref(),
818                    &model_import_options,
819                )
820                .await?;
821                // Set NodeMapping::UseNames as mapping here because FBX does not have
822                // any persistent unique ids, and we have to use names.
823                (scene, NodeMapping::UseNames)
824            }
825            // Scene can be used directly as model resource. Such scenes can be created in
826            // Fyroxed.
827            "rgs" => (
828                SceneLoader::from_file(
829                    path.as_ref(),
830                    io,
831                    serialization_context,
832                    resource_manager.clone(),
833                )
834                .await?
835                .0
836                .finish()
837                .await,
838                NodeMapping::UseHandles,
839            ),
840            // TODO: Add more formats.
841            _ => {
842                return Err(ModelLoadError::NotSupported(format!(
843                    "Unsupported model resource format: {extension}"
844                )))
845            }
846        };
847
848        Ok(Self { scene, mapping })
849    }
850
851    /// Returns shared reference to internal scene, there is no way to obtain
852    /// mutable reference to inner scene because resource is immutable source
853    /// of data.
854    pub fn get_scene(&self) -> &Scene {
855        &self.scene
856    }
857
858    /// Searches for a node in the model, starting from specified node using the specified closure. Returns a tuple with a
859    /// handle and a reference to the found node. If nothing is found, it returns [`None`].
860    pub fn find_node_by_name(&self, name: &str) -> Option<(Handle<Node>, &Node)> {
861        self.scene.graph.find_by_name_from_root(name)
862    }
863
864    pub(crate) fn get_scene_mut(&mut self) -> &mut Scene {
865        &mut self.scene
866    }
867}