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