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}