Skip to main content

fyrox_impl/resource/fbx/
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//! Contains all methods to load and convert FBX model format.
22//!
23//! FBX is most flexible format to store and distribute 3D models, it has lots of useful features
24//! such as skeletal animation, keyframe animation, support tangents, binormals, materials, etc.
25//!
26//! Normally you should never use methods from this module directly, use resource manager to load
27//! models and create their instances.
28
29mod document;
30pub mod error;
31mod scene;
32
33use crate::material::MaterialTextureBinding;
34use crate::{
35    asset::manager::ResourceManager,
36    core::{
37        algebra::{Matrix4, Point3, UnitQuaternion, Vector2, Vector3, Vector4},
38        instant::Instant,
39        log::{Log, MessageKind},
40        math::curve::{CurveKey, CurveKeyKind},
41        math::{self, triangulator::triangulate, RotationOrder},
42        pool::Handle,
43    },
44    graph::SceneGraph,
45    material,
46    material::MaterialResourceBinding,
47    resource::{
48        fbx::{
49            document::FbxDocument,
50            error::FbxError,
51            scene::{
52                animation::{FbxAnimationCurveNode, FbxAnimationCurveNodeType},
53                geometry::FbxMeshGeometry,
54                model::FbxModel,
55                FbxComponent, FbxMapping, FbxScene,
56            },
57        },
58        model::{MaterialSearchOptions, ModelImportOptions},
59        texture::{Texture, TextureImportOptions, TextureResource, TextureResourceExtension},
60    },
61    scene::{
62        animation::{Animation, AnimationContainer, AnimationPlayerBuilder, Track},
63        base::BaseBuilder,
64        graph::Graph,
65        mesh::{
66            buffer::{VertexAttributeUsage, VertexBuffer, VertexWriteTrait},
67            surface::{
68                BlendShape, BlendShapesContainer, InputBlendShapeData, Surface, SurfaceData,
69                SurfaceResource, VertexWeightSet,
70            },
71            vertex::{AnimatedVertex, StaticVertex},
72            Mesh, MeshBuilder,
73        },
74        node::Node,
75        pivot::PivotBuilder,
76        transform::TransformBuilder,
77        Scene,
78    },
79    utils::{self, raw_mesh::RawMeshBuilder},
80};
81use fxhash::{FxHashMap, FxHashSet};
82use fyrox_animation::track::TrackBinding;
83use fyrox_core::{err, Uuid};
84use fyrox_material::shader::{ShaderResource, ShaderResourceExtension};
85use fyrox_material::MaterialResource;
86use fyrox_resource::io::ResourceIo;
87use fyrox_resource::untyped::ResourceKind;
88use std::{cmp::Ordering, path::Path};
89
90/// Input angles in degrees
91fn quat_from_euler(euler: Vector3<f32>) -> UnitQuaternion<f32> {
92    math::quat_from_euler(
93        Vector3::new(
94            euler.x.to_radians(),
95            euler.y.to_radians(),
96            euler.z.to_radians(),
97        ),
98        RotationOrder::XYZ,
99    )
100}
101
102/// Fixes index that is used as indicator of end of a polygon
103/// FBX stores array of indices like so 0,1,-3,... where -3
104/// is actually index 2 but it xor'ed using -1.
105fn fix_index(index: i32) -> usize {
106    if index < 0 {
107        (index ^ -1) as usize
108    } else {
109        index as usize
110    }
111}
112
113/// Triangulates polygon face if needed.
114/// Returns number of processed indices.
115fn prepare_next_face(
116    vertices: &[Vector3<f32>],
117    indices: &[i32],
118    temp_vertices: &mut Vec<Vector3<f32>>,
119    out_triangles: &mut Vec<[usize; 3]>,
120    out_face_triangles: &mut Vec<[usize; 3]>,
121) -> usize {
122    out_triangles.clear();
123    out_face_triangles.clear();
124
125    // Find out how much vertices do we have per face.
126    let mut vertex_per_face = 0;
127    for &index in indices {
128        vertex_per_face += 1;
129        if index < 0 {
130            break;
131        }
132    }
133
134    match vertex_per_face.cmp(&3) {
135        Ordering::Less => {
136            // Silently ignore invalid faces.
137        }
138        Ordering::Equal => {
139            let a = fix_index(indices[0]);
140            let b = fix_index(indices[1]);
141            let c = fix_index(indices[2]);
142
143            // Ensure that we have valid indices here. Some exporters may fuck up indices
144            // and they'll blow up loader.
145            if a < vertices.len() && b < vertices.len() && c < vertices.len() {
146                // We have a triangle
147                out_triangles.push([a, b, c]);
148                out_face_triangles.push([0, 1, 2]);
149            }
150        }
151        Ordering::Greater => {
152            // Found arbitrary polygon, triangulate it.
153            temp_vertices.clear();
154            for i in 0..vertex_per_face {
155                temp_vertices.push(vertices[fix_index(indices[i])]);
156            }
157            triangulate(temp_vertices, out_face_triangles);
158            for triangle in out_face_triangles.iter() {
159                out_triangles.push([
160                    fix_index(indices[triangle[0]]),
161                    fix_index(indices[triangle[1]]),
162                    fix_index(indices[triangle[2]]),
163                ]);
164            }
165        }
166    }
167
168    vertex_per_face
169}
170
171#[derive(Clone)]
172struct UnpackedVertex {
173    // Index of surface this vertex belongs to.
174    surface_index: usize,
175    position: Vector3<f32>,
176    normal: Vector3<f32>,
177    tangent: Vector3<f32>,
178    uv: Vector2<f32>,
179    // Set of weights for skinning.
180    weights: Option<VertexWeightSet>,
181}
182
183impl Into<AnimatedVertex> for UnpackedVertex {
184    fn into(self) -> AnimatedVertex {
185        AnimatedVertex {
186            position: self.position,
187            tex_coord: self.uv,
188            normal: self.normal,
189            tangent: Vector4::new(self.tangent.x, self.tangent.y, self.tangent.z, 1.0),
190            // Correct values will be assigned in second pass of conversion
191            // when all nodes will be converted.
192            bone_weights: Default::default(),
193            bone_indices: Default::default(),
194        }
195    }
196}
197
198impl Into<StaticVertex> for UnpackedVertex {
199    fn into(self) -> StaticVertex {
200        StaticVertex {
201            position: self.position,
202            tex_coord: self.uv,
203            normal: self.normal,
204            tangent: Vector4::new(self.tangent.x, self.tangent.y, self.tangent.z, 1.0),
205        }
206    }
207}
208
209fn convert_vertex(
210    geom: &FbxMeshGeometry,
211    geometric_transform: &Matrix4<f32>,
212    material_index: usize,
213    index: usize,
214    index_in_polygon: usize,
215    skin_data: &[VertexWeightSet],
216) -> Result<UnpackedVertex, FbxError> {
217    let position = *geom.vertices.get(index).ok_or(FbxError::IndexOutOfBounds)?;
218
219    let normal = match geom.normals.as_ref() {
220        Some(normals) => *normals.get(index, index_in_polygon)?,
221        None => Vector3::y(),
222    };
223
224    let tangent = match geom.tangents.as_ref() {
225        Some(tangents) => *tangents.get(index, index_in_polygon)?,
226        None => Vector3::y(),
227    };
228
229    let uv = match geom.uvs.as_ref() {
230        Some(uvs) => *uvs.get(index, index_in_polygon)?,
231        None => Vector2::default(),
232    };
233
234    let material = match geom.materials.as_ref() {
235        Some(materials) => *materials.get(material_index, index_in_polygon)?,
236        None => 0,
237    };
238
239    Ok(UnpackedVertex {
240        position: geometric_transform
241            .transform_point(&Point3::from(position))
242            .coords,
243        normal: geometric_transform.transform_vector(&normal),
244        tangent: geometric_transform.transform_vector(&tangent),
245        uv: Vector2::new(uv.x, 1.0 - uv.y), // Invert Y because OpenGL has origin at left *bottom* corner.
246        surface_index: material as usize,
247        weights: if geom.deformers.is_empty() {
248            None
249        } else {
250            Some(*skin_data.get(index).ok_or(FbxError::IndexOutOfBounds)?)
251        },
252    })
253}
254
255#[derive(Clone)]
256enum FbxMeshBuilder {
257    Static(RawMeshBuilder<StaticVertex>),
258    Animated(RawMeshBuilder<AnimatedVertex>),
259}
260
261impl FbxMeshBuilder {
262    fn build(self) -> SurfaceData {
263        match self {
264            FbxMeshBuilder::Static(builder) => SurfaceData::from_raw_mesh(builder.build()),
265            FbxMeshBuilder::Animated(builder) => SurfaceData::from_raw_mesh(builder.build()),
266        }
267    }
268}
269
270#[derive(Clone)]
271struct FbxSurfaceData {
272    base_mesh_builder: FbxMeshBuilder,
273    blend_shapes: Vec<InputBlendShapeData>,
274    skin_data: Vec<VertexWeightSet>,
275}
276
277fn make_blend_shapes_container(
278    base_shape: &VertexBuffer,
279    blend_shapes: Vec<InputBlendShapeData>,
280) -> Option<BlendShapesContainer> {
281    if blend_shapes.is_empty() {
282        None
283    } else {
284        Some(BlendShapesContainer::from_lists(base_shape, &blend_shapes))
285    }
286}
287
288type EngineMaterial = material::Material;
289type MaterialMap = FxHashMap<Handle<FbxComponent>, MaterialResource>;
290
291async fn create_materials(
292    fbx_scene: &FbxScene,
293    resource_manager: &ResourceManager,
294    model_import_options: &ModelImportOptions,
295    model_path: &Path,
296) -> Result<MaterialMap, FbxError> {
297    let mut map = MaterialMap::default();
298    for (component_handle, component) in fbx_scene.pair_iter() {
299        if let FbxComponent::Material(fbx_material) = component {
300            let mut material = EngineMaterial::from_shader(ShaderResource::standard());
301
302            material.set_property("diffuseColor", fbx_material.diffuse_color);
303
304            let io = resource_manager.resource_io();
305
306            for (name, texture_handle) in fbx_material.textures.iter() {
307                let texture = fbx_scene.get(*texture_handle).as_texture()?;
308                let path = texture.get_root_file_path(&fbx_scene.components);
309
310                if let Some(filename) = path.file_name() {
311                    let texture_path = if texture.content.is_empty() {
312                        match model_import_options.material_search_options {
313                            MaterialSearchOptions::MaterialsDirectory(ref directory) => {
314                                Some(directory.join(filename))
315                            }
316                            MaterialSearchOptions::RecursiveUp => {
317                                let mut texture_path = None;
318                                let mut path = model_path.to_owned();
319                                while let Some(parent) = path.parent() {
320                                    let candidate = parent.join(filename);
321                                    if io.exists(&candidate).await {
322                                        texture_path = Some(candidate);
323                                        break;
324                                    }
325                                    path.pop();
326                                }
327                                texture_path
328                            }
329                            MaterialSearchOptions::WorkingDirectory => {
330                                let mut texture_path = None;
331
332                                let path = Path::new(".");
333
334                                if let Ok(iter) = io.walk_directory(path, usize::MAX).await {
335                                    for dir in iter {
336                                        if io.is_dir(&dir).await {
337                                            let candidate = dir.join(filename);
338                                            if candidate.exists() {
339                                                texture_path = Some(candidate);
340                                                break;
341                                            }
342                                        }
343                                    }
344                                }
345
346                                texture_path
347                            }
348                            MaterialSearchOptions::UsePathDirectly => Some(path.clone()),
349                        }
350                    } else {
351                        Some(path.clone())
352                    };
353
354                    if let Some(texture_path) = texture_path {
355                        let texture = if texture.content.is_empty() {
356                            resource_manager.request::<Texture>(texture_path.as_path())
357                        } else {
358                            TextureResource::load_from_memory(
359                                Uuid::new_v4(),
360                                ResourceKind::External,
361                                &texture.content,
362                                TextureImportOptions::default(),
363                            )
364                            .unwrap()
365                        };
366
367                        // Make up your mind, Autodesk and Blender.
368                        // Handle all possible combinations of links to auto-import materials.
369                        let name = if name.contains("AmbientColor")
370                            || name.contains("ambient_color")
371                        {
372                            Some("aoTexture")
373                        } else if name.contains("DiffuseColor")
374                            || name.contains("diffuse_color")
375                            || name.contains("base_color_map")
376                            || name.contains("texmap_diffuse")
377                        {
378                            Some("diffuseTexture")
379                        } else if name.contains("MetalnessMap")
380                            || name.contains("metalness_map")
381                            || name.contains("ReflectionFactor")
382                            || name.contains("texmap_reflection")
383                            || name.contains("texmap_metalness")
384                        {
385                            Some("metallicTexture")
386                        } else if name.contains("RoughnessMap")
387                            || name.contains("roughness_map")
388                            || name.contains("Shininess")
389                            || name.contains("ShininessExponent")
390                            || name.contains("texmap_roughness")
391                        {
392                            Some("roughnessTexture")
393                        } else if name.contains("Bump")
394                            || name.contains("bump_map")
395                            || name.contains("NormalMap")
396                            || name.contains("normal_map")
397                            || name.contains("texmap_bump")
398                        {
399                            Some("normalTexture")
400                        } else if name.contains("DisplacementColor")
401                            || name.contains("displacement_map")
402                        {
403                            Some("heightTexture")
404                        } else if name.contains("EmissiveColor") || name.contains("emit_color_map")
405                        {
406                            Some("emissionTexture")
407                        } else {
408                            None
409                        };
410
411                        if let Some(property_name) = name {
412                            material.bind(
413                                property_name,
414                                MaterialResourceBinding::Texture(MaterialTextureBinding {
415                                    value: Some(texture),
416                                }),
417                            );
418                        }
419                    } else {
420                        Log::writeln(
421                            MessageKind::Warning,
422                            format!(
423                                "Unable to find a texture {filename:?} for 3D model {model_path:?} using {model_import_options:?} option!"
424                            ),
425                        );
426                    }
427                }
428            }
429
430            let old_material =
431                map.insert(component_handle, MaterialResource::new_embedded(material));
432            assert!(old_material.is_none());
433        }
434    }
435    Ok(map)
436}
437
438fn create_surfaces(
439    data_set: Vec<FbxSurfaceData>,
440    model: &FbxModel,
441    materials: &MaterialMap,
442) -> Result<Vec<Surface>, FbxError> {
443    let mut surfaces = Vec::new();
444
445    // Create surfaces per material
446    if model.materials.is_empty() {
447        assert_eq!(data_set.len(), 1);
448        let data = data_set.into_iter().next().unwrap();
449        let mut surface_data = data.base_mesh_builder.build();
450        surface_data.blend_shapes_container =
451            make_blend_shapes_container(&surface_data.vertex_buffer, data.blend_shapes);
452        let mut surface = Surface::new(SurfaceResource::new_ok(
453            Uuid::new_v4(),
454            ResourceKind::External,
455            surface_data,
456        ));
457        surface.vertex_weights = data.skin_data;
458        surfaces.push(surface);
459    } else {
460        assert_eq!(data_set.len(), model.materials.len());
461        for (&material_handle, data) in model.materials.iter().zip(data_set.into_iter()) {
462            let mut surface_data = data.base_mesh_builder.build();
463            surface_data.blend_shapes_container =
464                make_blend_shapes_container(&surface_data.vertex_buffer, data.blend_shapes);
465            let mut surface = Surface::new(SurfaceResource::new_ok(
466                Uuid::new_v4(),
467                ResourceKind::External,
468                surface_data,
469            ));
470            surface.vertex_weights = data.skin_data;
471
472            if let Some(material) = materials.get(&material_handle) {
473                surface.set_material(material.clone());
474            } else {
475                err!("No respective material");
476            }
477
478            surfaces.push(surface);
479        }
480    }
481
482    Ok(surfaces)
483}
484
485fn convert_mesh(
486    base: BaseBuilder,
487    fbx_scene: &FbxScene,
488    model: &FbxModel,
489    graph: &mut Graph,
490    materials: &MaterialMap,
491) -> Result<Handle<Mesh>, FbxError> {
492    let geometric_transform = Matrix4::new_translation(&model.geometric_translation)
493        * quat_from_euler(model.geometric_rotation).to_homogeneous()
494        * Matrix4::new_nonuniform_scaling(&model.geometric_scale);
495
496    let mut temp_vertices = Vec::new();
497    let mut triangles = Vec::new();
498
499    // Array for triangulation needs, it will contain triangle definitions for
500    // triangulated polygon.
501    let mut face_triangles = Vec::new();
502
503    let mut mesh_surfaces = Vec::new();
504    let mut mesh_blend_shapes = Vec::new();
505
506    for &geom_handle in &model.geoms {
507        let geom = fbx_scene.get(geom_handle).as_mesh_geometry()?;
508        let skin_data = geom.get_skin_data(fbx_scene)?;
509        let blend_shapes = geom.collect_blend_shapes_refs(fbx_scene)?;
510
511        if !mesh_blend_shapes.is_empty() {
512            Log::warn("More than two geoms with blend shapes?");
513        }
514        mesh_blend_shapes = blend_shapes
515            .iter()
516            .map(|bs| BlendShape {
517                weight: bs.deform_percent,
518                name: bs.name.clone(),
519            })
520            .collect();
521
522        let mut data_set = vec![
523            FbxSurfaceData {
524                base_mesh_builder: if geom.deformers.is_empty() {
525                    FbxMeshBuilder::Static(RawMeshBuilder::new(1024, 1024))
526                } else {
527                    FbxMeshBuilder::Animated(RawMeshBuilder::new(1024, 1024))
528                },
529                blend_shapes: blend_shapes
530                    .iter()
531                    .map(|bs_channel| {
532                        InputBlendShapeData {
533                            name: bs_channel.name.clone(),
534                            default_weight: bs_channel.deform_percent,
535                            positions: Default::default(),
536                            normals: Default::default(),
537                            tangents: Default::default(),
538                        }
539                    })
540                    .collect(),
541                skin_data: Default::default(),
542            };
543            model.materials.len().max(1)
544        ];
545
546        let mut material_index = 0;
547        let mut n = 0;
548        while n < geom.indices.len() {
549            let origin = n;
550            n += prepare_next_face(
551                &geom.vertices,
552                &geom.indices[origin..],
553                &mut temp_vertices,
554                &mut triangles,
555                &mut face_triangles,
556            );
557            for (triangle, face_triangle) in triangles.iter().zip(face_triangles.iter()) {
558                for (&index, &face_vertex_index) in triangle.iter().zip(face_triangle.iter()) {
559                    let polygon_vertex_index = origin + face_vertex_index;
560                    let vertex = convert_vertex(
561                        geom,
562                        &geometric_transform,
563                        material_index,
564                        index,
565                        polygon_vertex_index,
566                        &skin_data,
567                    )?;
568                    let data = data_set.get_mut(vertex.surface_index).unwrap();
569                    let weights = vertex.weights;
570                    let final_index;
571                    let is_unique_vertex = match data.base_mesh_builder {
572                        FbxMeshBuilder::Static(ref mut builder) => {
573                            final_index = builder.vertex_count();
574                            builder.insert(vertex.clone().into())
575                        }
576                        FbxMeshBuilder::Animated(ref mut builder) => {
577                            final_index = builder.vertex_count();
578                            builder.insert(vertex.clone().into())
579                        }
580                    };
581                    if is_unique_vertex {
582                        if let Some(skin_data) = weights {
583                            data.skin_data.push(skin_data);
584                        }
585                    }
586
587                    // Fill each blend shape, but modify the vertex first using the "offsets" from blend shapes.
588                    assert_eq!(blend_shapes.len(), data.blend_shapes.len());
589                    for (fbx_blend_shape, blend_shape) in
590                        blend_shapes.iter().zip(data.blend_shapes.iter_mut())
591                    {
592                        let blend_shape_geometry = fbx_scene
593                            .get(fbx_blend_shape.geometry)
594                            .as_shape_geometry()?;
595
596                        // Only certain vertices are affected by a blend shape, because FBX stores only changed
597                        // parts ("diff").
598                        if let Some(relative_index) =
599                            blend_shape_geometry.indices.get(&(index as i32))
600                        {
601                            blend_shape.positions.insert(
602                                final_index as u32,
603                                utils::vec3_f16_from_f32(
604                                    blend_shape_geometry.vertices[*relative_index as usize],
605                                ),
606                            );
607                            if let Some(normals) = blend_shape_geometry.normals.as_ref() {
608                                blend_shape.normals.insert(
609                                    final_index as u32,
610                                    utils::vec3_f16_from_f32(normals[*relative_index as usize]),
611                                );
612                            }
613                            if let Some(tangents) = blend_shape_geometry.tangents.as_ref() {
614                                blend_shape.normals.insert(
615                                    final_index as u32,
616                                    utils::vec3_f16_from_f32(tangents[*relative_index as usize]),
617                                );
618                            }
619                        }
620                    }
621                }
622            }
623            if let Some(materials) = geom.materials.as_ref() {
624                if materials.mapping == FbxMapping::ByPolygon {
625                    material_index += 1;
626                }
627            }
628        }
629
630        let mut surfaces = create_surfaces(data_set, model, materials)?;
631
632        if geom.tangents.is_none() {
633            for surface in surfaces.iter_mut() {
634                surface.data().data_ref().calculate_tangents().unwrap();
635            }
636        }
637
638        for surface in surfaces {
639            mesh_surfaces.push(surface);
640        }
641    }
642
643    Ok(MeshBuilder::new(base)
644        .with_blend_shapes(mesh_blend_shapes)
645        .with_surfaces(mesh_surfaces)
646        .build(graph))
647}
648
649fn convert_model_to_base(model: &FbxModel) -> BaseBuilder {
650    BaseBuilder::new()
651        .with_inv_bind_pose_transform(model.inv_bind_transform)
652        .with_name(model.name.as_str())
653        .with_local_transform(
654            TransformBuilder::new()
655                .with_local_rotation(quat_from_euler(model.rotation))
656                .with_local_scale(model.scale)
657                .with_local_position(model.translation)
658                .with_post_rotation(quat_from_euler(model.post_rotation))
659                .with_pre_rotation(quat_from_euler(model.pre_rotation))
660                .with_rotation_offset(model.rotation_offset)
661                .with_rotation_pivot(model.rotation_pivot)
662                .with_scaling_offset(model.scaling_offset)
663                .with_scaling_pivot(model.scaling_pivot)
664                .build(),
665        )
666}
667
668fn convert_model(
669    fbx_scene: &FbxScene,
670    model: &FbxModel,
671    graph: &mut Graph,
672    animation: &mut Animation,
673    materials: &MaterialMap,
674) -> Result<Handle<Node>, FbxError> {
675    let base = convert_model_to_base(model);
676
677    // Create node with the correct kind.
678    let node_handle = if !model.geoms.is_empty() {
679        convert_mesh(base, fbx_scene, model, graph, materials)?.to_base()
680    } else if model.light.is_some() {
681        fbx_scene.get(model.light).as_light()?.convert(base, graph)
682    } else {
683        PivotBuilder::new(base).build(graph).to_base()
684    };
685
686    // Convert animations
687    if !model.animation_curve_nodes.is_empty() {
688        // Find supported curve nodes (translation, rotation, scale)
689        let mut lcl_translation = None;
690        let mut lcl_rotation = None;
691        let mut lcl_scale = None;
692        for &anim_curve_node_handle in model.animation_curve_nodes.iter() {
693            let component = fbx_scene.get(anim_curve_node_handle);
694            if let FbxComponent::AnimationCurveNode(curve_node) = component {
695                if curve_node.actual_type == FbxAnimationCurveNodeType::Rotation {
696                    lcl_rotation = Some(curve_node);
697                } else if curve_node.actual_type == FbxAnimationCurveNodeType::Translation {
698                    lcl_translation = Some(curve_node);
699                } else if curve_node.actual_type == FbxAnimationCurveNodeType::Scale {
700                    lcl_scale = Some(curve_node);
701                }
702            }
703        }
704
705        fn fill_track<F: Fn(f32) -> f32>(
706            track: &mut Track,
707            fbx_scene: &FbxScene,
708            fbx_track: &FbxAnimationCurveNode,
709            default: Vector3<f32>,
710            transform_value: F,
711        ) {
712            let curves = track.data_container_mut().curves_mut();
713
714            if !fbx_track.curves.contains_key("d|X") {
715                curves[0].add_key(CurveKey::new(0.0, default.x, CurveKeyKind::Constant));
716            }
717            if !fbx_track.curves.contains_key("d|Y") {
718                curves[1].add_key(CurveKey::new(0.0, default.y, CurveKeyKind::Constant));
719            }
720            if !fbx_track.curves.contains_key("d|Z") {
721                curves[2].add_key(CurveKey::new(0.0, default.z, CurveKeyKind::Constant));
722            }
723
724            for (id, curve_handle) in fbx_track.curves.iter() {
725                let index = match id.as_str() {
726                    "d|X" => Some(0),
727                    "d|Y" => Some(1),
728                    "d|Z" => Some(2),
729                    _ => None,
730                };
731
732                if let Some(index) = index {
733                    if let FbxComponent::AnimationCurve(fbx_curve) = fbx_scene.get(*curve_handle) {
734                        if fbx_curve.keys.is_empty() {
735                            curves[index].add_key(CurveKey::new(
736                                0.0,
737                                default[index],
738                                CurveKeyKind::Constant,
739                            ));
740                        } else {
741                            for pair in fbx_curve.keys.iter() {
742                                curves[index].add_key(CurveKey::new(
743                                    pair.time,
744                                    transform_value(pair.value),
745                                    CurveKeyKind::Linear,
746                                ))
747                            }
748                        }
749                    }
750                }
751            }
752        }
753
754        fn add_vec3_key(track: &mut Track, value: Vector3<f32>) {
755            let curves = track.data_container_mut().curves_mut();
756            curves[0].add_key(CurveKey::new(0.0, value.x, CurveKeyKind::Constant));
757            curves[1].add_key(CurveKey::new(0.0, value.y, CurveKeyKind::Constant));
758            curves[2].add_key(CurveKey::new(0.0, value.z, CurveKeyKind::Constant));
759        }
760
761        // Convert to engine format
762        let mut translation_track = Track::new_position();
763        if let Some(lcl_translation) = lcl_translation {
764            fill_track(
765                &mut translation_track,
766                fbx_scene,
767                lcl_translation,
768                model.translation,
769                |v| v,
770            );
771        } else {
772            add_vec3_key(&mut translation_track, model.translation);
773        }
774
775        let mut rotation_track = Track::new_rotation();
776        if let Some(lcl_rotation) = lcl_rotation {
777            fill_track(
778                &mut rotation_track,
779                fbx_scene,
780                lcl_rotation,
781                model.rotation,
782                |v| v.to_radians(),
783            );
784        } else {
785            add_vec3_key(&mut rotation_track, model.rotation);
786        }
787
788        let mut scale_track = Track::new_scale();
789        if let Some(lcl_scale) = lcl_scale {
790            fill_track(&mut scale_track, fbx_scene, lcl_scale, model.scale, |v| v);
791        } else {
792            add_vec3_key(&mut scale_track, model.scale);
793        }
794
795        animation.add_track_with_binding(TrackBinding::new(node_handle), translation_track);
796        animation.add_track_with_binding(TrackBinding::new(node_handle), rotation_track);
797        animation.add_track_with_binding(TrackBinding::new(node_handle), scale_track);
798    }
799
800    animation.fit_length_to_content();
801
802    Ok(node_handle)
803}
804
805///
806/// Converts FBX DOM to native engine representation.
807///
808async fn convert(
809    fbx_scene: &FbxScene,
810    resource_manager: ResourceManager,
811    scene: &mut Scene,
812    model_path: &Path,
813    model_import_options: &ModelImportOptions,
814) -> Result<(), FbxError> {
815    let root = scene.graph.get_root();
816
817    let mut animation = Animation::default();
818    animation.set_name("Animation");
819
820    let materials = create_materials(
821        fbx_scene,
822        &resource_manager,
823        model_import_options,
824        model_path,
825    )
826    .await?;
827
828    let mut fbx_model_to_node_map = FxHashMap::default();
829    for (component_handle, component) in fbx_scene.pair_iter() {
830        if let FbxComponent::Model(model) = component {
831            let node = convert_model(
832                fbx_scene,
833                model,
834                &mut scene.graph,
835                &mut animation,
836                &materials,
837            )?;
838            scene.graph.link_nodes(node, root);
839            fbx_model_to_node_map.insert(component_handle, node);
840        }
841    }
842
843    // Do not create the animation player if there's no animation content.
844    if !animation.tracks_data().data_ref().tracks().is_empty() {
845        let mut animations_container = AnimationContainer::new();
846        animations_container.add(animation);
847        AnimationPlayerBuilder::new(BaseBuilder::new().with_name("AnimationPlayer"))
848            .with_animations(animations_container)
849            .build(&mut scene.graph);
850    }
851
852    // Link according to hierarchy
853    for (&fbx_model_handle, node_handle) in fbx_model_to_node_map.iter() {
854        if let FbxComponent::Model(fbx_model) = fbx_scene.get(fbx_model_handle) {
855            for fbx_child_handle in fbx_model.children.iter() {
856                if let Some(child_handle) = fbx_model_to_node_map.get(fbx_child_handle) {
857                    scene.graph.link_nodes(*child_handle, *node_handle);
858                }
859            }
860        }
861    }
862    scene.graph.update_hierarchical_data();
863
864    // Remap handles from fbx model to handles of instantiated nodes
865    // on each surface of each mesh.
866    for &handle in fbx_model_to_node_map.values() {
867        if let Some(mesh) = scene.graph[handle].cast_mut::<Mesh>() {
868            let mut surface_bones = FxHashSet::default();
869            for surface in mesh.surfaces_mut() {
870                for weight_set in surface.vertex_weights.iter_mut() {
871                    for weight in weight_set.iter_mut() {
872                        let fbx_model: Handle<FbxComponent> = weight.effector.into();
873                        let bone_handle = fbx_model_to_node_map
874                            .get(&fbx_model)
875                            .ok_or(FbxError::UnableToRemapModelToNode)?;
876                        surface_bones.insert(*bone_handle);
877                        weight.effector = (*bone_handle).into();
878                    }
879                }
880                surface
881                    .bones
882                    .set_value_silent(surface_bones.iter().copied().collect());
883
884                let data_rc = surface.data();
885                let mut data = data_rc.data_ref();
886                if data.vertex_buffer.vertex_count() as usize == surface.vertex_weights.len() {
887                    let mut vertex_buffer_mut = data.vertex_buffer.modify();
888                    for (mut view, weight_set) in vertex_buffer_mut
889                        .iter_mut()
890                        .zip(surface.vertex_weights.iter())
891                    {
892                        let mut indices = Vector4::default();
893                        let mut weights = Vector4::default();
894                        for (k, weight) in weight_set.iter().enumerate() {
895                            indices[k] = surface
896                                .bones
897                                .iter()
898                                .position(|bone_handle| {
899                                    *bone_handle == Handle::<Node>::from(weight.effector)
900                                })
901                                .ok_or(FbxError::UnableToFindBone)?
902                                as u8;
903                            weights[k] = weight.value;
904                        }
905
906                        view.write_4_f32(VertexAttributeUsage::BoneWeight, weights)
907                            .unwrap();
908                        view.write_4_u8(VertexAttributeUsage::BoneIndices, indices)
909                            .unwrap();
910                    }
911                }
912            }
913        }
914    }
915
916    Ok(())
917}
918
919/// Tries to load and convert FBX from given path.
920///
921/// Normally you should never use this method, use resource manager to load models.
922pub async fn load_to_scene<P: AsRef<Path>>(
923    scene: &mut Scene,
924    resource_manager: ResourceManager,
925    io: &dyn ResourceIo,
926    path: P,
927    model_import_options: &ModelImportOptions,
928) -> Result<(), FbxError> {
929    let start_time = Instant::now();
930
931    Log::writeln(
932        MessageKind::Information,
933        format!("Trying to load {:?}", path.as_ref()),
934    );
935
936    let now = Instant::now();
937    let fbx = FbxDocument::new(path.as_ref(), io).await?;
938    let parsing_time = now.elapsed().as_millis();
939
940    let now = Instant::now();
941    let fbx_scene = FbxScene::new(&fbx)?;
942    let dom_prepare_time = now.elapsed().as_millis();
943
944    let now = Instant::now();
945    convert(
946        &fbx_scene,
947        resource_manager,
948        scene,
949        path.as_ref(),
950        model_import_options,
951    )
952    .await?;
953    let conversion_time = now.elapsed().as_millis();
954
955    Log::writeln(MessageKind::Information,
956                 format!("FBX {:?} loaded in {} ms\n\t- Parsing - {} ms\n\t- DOM Prepare - {} ms\n\t- Conversion - {} ms",
957                         path.as_ref(), start_time.elapsed().as_millis(), parsing_time, dom_prepare_time, conversion_time));
958
959    // Check for multiple nodes with same name and throw a warning if any.
960    // It seems that FBX was designed using ass, not brains. It has no unique **persistent**
961    // IDs for entities, so the only way to find an entity is to use its name, but FBX also
962    // allows to have multiple entities with the same name. facepalm.jpg
963    let mut hash_set = FxHashSet::<String>::default();
964    for node in scene.graph.linear_iter() {
965        if hash_set.contains(node.name()) {
966            Log::writeln(
967                MessageKind::Error,
968                format!(
969                    "A node with existing name {} was found during the load of {} resource! \
970                    Do **NOT IGNORE** this message, please fix names in your model, otherwise \
971                    engine won't be able to correctly restore data from your resource!",
972                    node.name(),
973                    path.as_ref().display()
974                ),
975            );
976        } else {
977            hash_set.insert(node.name_owned());
978        }
979    }
980
981    Ok(())
982}