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