Skip to main content

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