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