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}