From de61cfe7c59e36e514c35c23941886ab584a71e0 Mon Sep 17 00:00:00 2001 From: Gordon MacPherson Date: Wed, 2 Dec 2020 15:38:11 +0000 Subject: [PATCH] =?UTF-8?q?[fbx]=20bugfixes=20skinning,=20materials=20and?= =?UTF-8?q?=20debugging=20info,=20merry=20xmas=20=F0=9F=8E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Better materials: - default values will no longer trigger things like emission, clear coat being enabled etc incorrectly, mostly importers misunderstand this value, a value of 1 is not actually enabled emission, it must have a non zero setting other than emission value for the emission flags to actually be enabled correctly in our engine #42386 - lambert materials are warned against significantly, do not use Lambert in Godot, use a PBR material like Ai Standard Surface (it's like going from low quality to high definition in terms of output in scenes and on assets) - roughness values are calculated correctly in this version Fixes for normal's array - some normal's from some files were dropped and generated, this is now fixed. - FBX indexing for items with (-1) index, for normal data is supported, this is super helpful at increasing our range of compatibility of FBX meshes. Fix bone rest data **no longer requiring go to bind pose in Maya and various applications** - Partial fix for #43688 Validation tools added for validating large projects Provide package name, vendor and FBX vendor in the log. Implemented metadata properties so we can read extra document info required for bug reporting **FBX 2011 (version 7200)** is unsupported now, you must re-export your file in Maya or Max to upgrade the file format. Fixes skin bind poses being generated based on node xforms in the scene Fixes duplicate bones being added to skin and prevents add_bones from registering skeleton bones it now registers skin bind poses, but this should really be documented in the engine correctly right now it doesn't tell you this is the case. --- modules/fbx/data/fbx_bone.cpp | 54 +---- modules/fbx/data/fbx_bone.h | 49 ++--- modules/fbx/data/fbx_material.cpp | 63 ++++-- modules/fbx/data/fbx_material.h | 13 +- modules/fbx/data/fbx_mesh_data.cpp | 80 +++++-- modules/fbx/data/fbx_mesh_data.h | 20 +- modules/fbx/data/fbx_skeleton.cpp | 5 +- modules/fbx/data/fbx_skeleton.h | 2 +- modules/fbx/data/import_state.h | 4 +- modules/fbx/data/pivot_transform.cpp | 37 ++++ modules/fbx/data/pivot_transform.h | 4 + modules/fbx/editor_scene_importer_fbx.cpp | 247 +++++++++++++--------- modules/fbx/editor_scene_importer_fbx.h | 3 + modules/fbx/fbx_parser/FBXDocument.cpp | 24 ++- modules/fbx/fbx_parser/FBXDocument.h | 5 + modules/fbx/fbx_parser/FBXMeshGeometry.h | 2 +- modules/fbx/fbx_parser/FBXUtil.cpp | 4 +- modules/fbx/fbx_parser/FBXUtil.h | 8 - modules/fbx/tools/validation_tools.cpp | 48 +++++ modules/fbx/tools/validation_tools.h | 93 ++++++++ 20 files changed, 513 insertions(+), 252 deletions(-) create mode 100644 modules/fbx/tools/validation_tools.cpp create mode 100644 modules/fbx/tools/validation_tools.h diff --git a/modules/fbx/data/fbx_bone.cpp b/modules/fbx/data/fbx_bone.cpp index 6f3fc520f6f..7ccb0b37173 100644 --- a/modules/fbx/data/fbx_bone.cpp +++ b/modules/fbx/data/fbx_bone.cpp @@ -33,8 +33,8 @@ #include "fbx_node.h" #include "import_state.h" -Ref FBXBone::get_link(const ImportState &state) const { - print_verbose("bone name: " + bone_name); +Ref FBXSkinDeformer::get_link(const ImportState &state) const { + print_verbose("bone name: " + bone->bone_name); // safe for when deformers must be polyfilled when skin has different count of binds to bones in the scene ;) if (!cluster) { @@ -54,53 +54,3 @@ Ref FBXBone::get_link(const ImportState &state) const { // the node in space this is for, like if it's FOR a target. return link_node; } - -/* right now we just get single skin working and we can patch in the multiple tomorrow - per skin not per bone. */ -// this will work for multiple meshes :) awesomeness. -// okay so these formula's are complex and need proper understanding of -// shear, pivots, geometric pivots, pre rotation and post rotation -// additionally DO NOT EDIT THIS if your blender file isn't working. -// Contact RevoluPowered Gordon MacPherson if you are contemplating making edits to this. -Transform FBXBone::get_vertex_skin_xform(const ImportState &state, Transform mesh_global_position, bool &r_valid_pose) { - r_valid_pose = false; - print_verbose("get_vertex_skin_xform: " + bone_name); - - // safe to do, this means we have 'remove unused deformer' checked. - if (!cluster) { - print_verbose("bone [" + itos(bone_id) + "] " + bone_name + ": has no skin offset poly-filling the skin to make rasterizer happy with unused deformers not being skinned"); - r_valid_pose = true; - return Transform(); - } - - ERR_FAIL_COND_V_MSG(cluster == nullptr, Transform(), "[serious] unable to resolve the fbx cluster for this bone " + bone_name); - // these methods will ONLY work for Maya. - if (cluster->TransformAssociateModelValid()) { - //print_error("additive skinning in use"); - Transform associate_global_init_position = cluster->TransformAssociateModel(); - Transform associate_global_current_position = Transform(); - Transform reference_global_init_position = cluster->GetTransform(); - Transform cluster_global_init_position = cluster->TransformLink(); - Ref link_node = get_link(state); - ERR_FAIL_COND_V_MSG(link_node.is_null(), Transform(), "invalid link corrupt file detected"); - r_valid_pose = true; - Transform cluster_global_current_position = link_node.is_valid() && link_node->pivot_transform.is_valid() ? link_node->pivot_transform->GlobalTransform : Transform(); - - vertex_transform_matrix = reference_global_init_position.affine_inverse() * associate_global_init_position * associate_global_current_position.affine_inverse() * - cluster_global_current_position * cluster_global_init_position.affine_inverse() * reference_global_init_position; - } else { - //print_error("non additive skinning is in use"); - Transform reference_global_position = cluster->GetTransform(); - Transform reference_global_current_position = mesh_global_position; - //Transform geometric_pivot = Transform(); // we do not use this - 3ds max only - Transform global_init_position = cluster->TransformLink(); - if (global_init_position.basis.determinant() == 0) { - global_init_position = Transform(Basis(), global_init_position.origin); - } - Transform cluster_relative_init_position = global_init_position.affine_inverse() * reference_global_position; - Transform cluster_relative_position_inverse = reference_global_current_position.affine_inverse() * global_init_position; - vertex_transform_matrix = cluster_relative_position_inverse * cluster_relative_init_position; - r_valid_pose = true; - } - - return vertex_transform_matrix; -} diff --git a/modules/fbx/data/fbx_bone.h b/modules/fbx/data/fbx_bone.h index 2d6997d4ff3..5d797da24fb 100644 --- a/modules/fbx/data/fbx_bone.h +++ b/modules/fbx/data/fbx_bone.h @@ -49,9 +49,6 @@ struct FBXBone : public Reference { return !valid_parent; } - uint64_t target_node_id; // the node target id for the skeleton element - bool valid_target = false; // only applies to bones with a mesh / in the skin. - // Godot specific data int godot_bone_id = -2; // godot internal bone id assigned after import @@ -60,36 +57,34 @@ struct FBXBone : public Reference { bool valid_armature_id = false; uint64_t armature_id = 0; - // Vertex Weight information - Transform transform_link; // todo remove - Transform transform_matrix; // todo remove - - /* get associate model - the model can be invalid sometimes */ - Ref get_associate_model() const { - return parent_bone; - } - /* link node is the parent bone */ - Ref get_link(const ImportState &state) const; - Transform get_vertex_skin_xform(const ImportState &state, Transform mesh_global_position, bool &valid); - Transform vertex_transform_matrix; - Transform local_cluster_matrix; // set_bone_pose - - mutable const FBXDocParser::Deformer *skin = nullptr; - mutable const FBXDocParser::Cluster *cluster = nullptr; mutable const FBXDocParser::Geometry *geometry = nullptr; mutable const FBXDocParser::ModelLimbNode *limb_node = nullptr; - void set_pivot_xform(Ref p_pivot_xform) { - pivot_xform = p_pivot_xform; + void set_node(Ref p_node) { + node = p_node; } - // pose node / if assigned - Transform pose_node = Transform(); - bool assigned_pose_node = false; - Ref parent_bone = Ref(); - Ref pivot_xform = Ref(); - Ref fbx_skeleton = Ref(); + // Stores the pivot xform for this bone + + Ref node = nullptr; + Ref parent_bone = nullptr; + Ref fbx_skeleton = nullptr; +}; + +struct FBXSkinDeformer { + FBXSkinDeformer(Ref p_bone, const FBXDocParser::Cluster *p_cluster) : + cluster(p_cluster), bone(p_bone) {} + ~FBXSkinDeformer() {} + const FBXDocParser::Cluster *cluster; + Ref bone; + + /* get associate model - the model can be invalid sometimes */ + Ref get_associate_model() const { + return bone->parent_bone; + } + + Ref get_link(const ImportState &state) const; }; #endif // FBX_BONE_H diff --git a/modules/fbx/data/fbx_material.cpp b/modules/fbx/data/fbx_material.cpp index 906e6cf1c34..362e33b28b2 100644 --- a/modules/fbx/data/fbx_material.cpp +++ b/modules/fbx/data/fbx_material.cpp @@ -29,9 +29,9 @@ /*************************************************************************/ #include "fbx_material.h" - #include "scene/resources/material.h" #include "scene/resources/texture.h" +#include "tools/validation_tools.h" String FBXMaterial::get_material_name() const { return material_name; @@ -286,10 +286,16 @@ Ref FBXMaterial::import_material(ImportState &state) { } if (desc == PROPERTY_DESC_IGNORE) { - WARN_PRINT("[Ignored] The FBX material parameter: `" + String(name.c_str()) + "` is ignored."); + //WARN_PRINT("[Ignored] The FBX material parameter: `" + String(name.c_str()) + "` is ignored."); continue; } else { print_verbose("FBX Material parameter: " + String(name.c_str())); + + // Check for Diffuse material system / lambert materials / legacy basically + if (name == "Diffuse" && !warning_non_pbr_material) { + ValidationTracker::get_singleton()->add_validation_error(state.path, "Invalid material settings change to Ai Standard Surface shader, mat name: " + material_name.c_escape()); + warning_non_pbr_material = true; + } } // DISABLE when adding support for all weird and wonderful material formats @@ -313,20 +319,41 @@ Ref FBXMaterial::import_material(ImportState &state) { const FBXDocParser::TypedProperty *vector_value = dynamic_cast *>(prop); if (!real_value && !vector_value) { - WARN_PRINT("unsupported datatype in property: " + String(name.c_str())); + //WARN_PRINT("unsupported datatype in property: " + String(name.c_str())); continue; } + // + // Zero / default value properties + // TODO: implement fields correctly tomorrow so we check 'has x mapping' before 'read x mapping' etc. + + // if(real_value) + // { + // if(real_value->Value() == 0 && !vector_value) + // { + // continue; + // } + // } + + if (vector_value && !real_value) { + if (vector_value->Value() == Vector3(0, 0, 0) && !real_value) { + continue; + } + } + switch (desc) { case PROPERTY_DESC_ALBEDO_COLOR: { if (vector_value) { const Vector3 &color = vector_value->Value(); // Make sure to not lost any eventual opacity. - Color c = spatial_material->get_albedo(); - c[0] = color[0]; - c[1] = color[1]; - c[2] = color[2]; - spatial_material->set_albedo(c); + if (color != Vector3(0, 0, 0)) { + Color c = Color(); + c[0] = color[0]; + c[1] = color[1]; + c[2] = color[2]; + spatial_material->set_albedo(c); + } + } else if (real_value) { print_error("albedo is unsupported format?"); } @@ -336,7 +363,7 @@ Ref FBXMaterial::import_material(ImportState &state) { const real_t opacity = real_value->Value(); if (opacity < (1.0 - CMP_EPSILON)) { Color c = spatial_material->get_albedo(); - c[3] = opacity; + c.a = opacity; spatial_material->set_albedo(c); material_info.features.push_back(SpatialMaterial::Feature::FEATURE_TRANSPARENT); spatial_material->set_depth_draw_mode(SpatialMaterial::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS); @@ -355,16 +382,7 @@ Ref FBXMaterial::import_material(ImportState &state) { print_error("unsupported specular vector value: " + vector_value->Value()); } } break; - case PROPERTY_DESC_SPECULAR_ROUGHNESS: { - if (real_value) { - print_verbose("specular roughness value:" + rtos(real_value->Value())); - spatial_material->set_roughness(MIN(1.0f, real_value->Value())); - } - if (vector_value) { - print_error("unsupported specular roughness color: " + vector_value->Value()); - } - } break; case PROPERTY_DESC_SPECULAR_COLOR: { if (vector_value) { print_error("unsupported specular color: " + vector_value->Value()); @@ -401,7 +419,8 @@ Ref FBXMaterial::import_material(ImportState &state) { } } break; case PROPERTY_DESC_COAT_ROUGHNESS: { - if (real_value) { + // meaning is that approx equal to zero is disabled not actually zero. ;) + if (real_value && Math::is_equal_approx(real_value->Value(), 0.0f)) { print_verbose("clearcoat real value: " + rtos(real_value->Value())); spatial_material->set_clearcoat_gloss(1.0 - real_value->Value()); @@ -411,11 +430,11 @@ Ref FBXMaterial::import_material(ImportState &state) { } } break; case PROPERTY_DESC_EMISSIVE: { - if (real_value) { + if (real_value && Math::is_equal_approx(real_value->Value(), 0.0f)) { print_verbose("Emissive real value: " + rtos(real_value->Value())); spatial_material->set_emission_energy(real_value->Value()); material_info.features.push_back(SpatialMaterial::Feature::FEATURE_EMISSION); - } else { + } else if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) { const Vector3 &color = vector_value->Value(); Color c; c[0] = color[0]; @@ -426,7 +445,7 @@ Ref FBXMaterial::import_material(ImportState &state) { } } break; case PROPERTY_DESC_EMISSIVE_COLOR: { - if (vector_value) { + if (vector_value && !vector_value->Value().is_equal_approx(Vector3(0, 0, 0))) { const Vector3 &color = vector_value->Value(); Color c; c[0] = color[0]; diff --git a/modules/fbx/data/fbx_material.h b/modules/fbx/data/fbx_material.h index f6e6b13e755..37e7ebf8fa0 100644 --- a/modules/fbx/data/fbx_material.h +++ b/modules/fbx/data/fbx_material.h @@ -38,6 +38,7 @@ struct FBXMaterial : public Reference { String material_name = String(); + bool warning_non_pbr_material = false; FBXDocParser::Material *material = nullptr; /* Godot materials @@ -149,6 +150,8 @@ struct FBXMaterial : public Reference { { "Maya|SpecularTexture|file", SpatialMaterial::TextureParam::TEXTURE_METALLIC }, /* Roughness */ + // Arnold Roughness Map + { "Maya|specularRoughness", SpatialMaterial::TextureParam::TEXTURE_ROUGHNESS }, { "3dsMax|Parameters|roughness_map", SpatialMaterial::TextureParam::TEXTURE_ROUGHNESS }, { "Maya|TEX_roughness_map", SpatialMaterial::TextureParam::TEXTURE_ROUGHNESS }, @@ -169,7 +172,6 @@ struct FBXMaterial : public Reference { //{ "Maya|diffuseRoughness", SpatialMaterial::TextureParam::UNSUPPORTED }, //{ "Maya|diffuseRoughness|file", SpatialMaterial::TextureParam::UNSUPPORTED }, - //{ "Maya|specularRoughness", SpatialMaterial::TextureParam::UNSUPPORTED }, //{ "ShininessExponent", SpatialMaterial::TextureParam::UNSUPPORTED }, //{ "ReflectionFactor", SpatialMaterial::TextureParam::UNSUPPORTED }, //{ "TransparentColor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA }, @@ -185,7 +187,6 @@ struct FBXMaterial : public Reference { PROPERTY_DESC_ROUGHNESS, PROPERTY_DESC_SPECULAR, PROPERTY_DESC_SPECULAR_COLOR, - PROPERTY_DESC_SPECULAR_ROUGHNESS, PROPERTY_DESC_SHINYNESS, PROPERTY_DESC_COAT, PROPERTY_DESC_COAT_ROUGHNESS, @@ -203,8 +204,8 @@ struct FBXMaterial : public Reference { { "Maya|specular", PROPERTY_DESC_SPECULAR }, { "Maya|specularColor", PROPERTY_DESC_SPECULAR_COLOR }, - /* Specular roughness */ - { "Maya|specularRoughness", PROPERTY_DESC_SPECULAR_ROUGHNESS }, + /* Specular roughness - arnold roughness map */ + { "Maya|specularRoughness", PROPERTY_DESC_ROUGHNESS }, /* Transparent */ { "Opacity", PROPERTY_DESC_TRANSPARENT }, @@ -221,10 +222,10 @@ struct FBXMaterial : public Reference { { "Maya|roughness", PROPERTY_DESC_ROUGHNESS }, /* Coat */ - { "Maya|coat", PROPERTY_DESC_COAT }, + //{ "Maya|coat", PROPERTY_DESC_COAT }, /* Coat roughness */ - { "Maya|coatRoughness", PROPERTY_DESC_COAT_ROUGHNESS }, + //{ "Maya|coatRoughness", PROPERTY_DESC_COAT_ROUGHNESS }, /* Emissive */ { "Maya|emission", PROPERTY_DESC_EMISSIVE }, diff --git a/modules/fbx/data/fbx_mesh_data.cpp b/modules/fbx/data/fbx_mesh_data.cpp index 1c4c9957e2a..a6138bd7c38 100644 --- a/modules/fbx/data/fbx_mesh_data.cpp +++ b/modules/fbx/data/fbx_mesh_data.cpp @@ -115,8 +115,9 @@ struct SurfaceData { Array morphs; }; -MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *mesh_geometry, const FBXDocParser::Model *model, bool use_compression) { +MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression) { + mesh_geometry = p_mesh_geometry; // todo: make this just use a uint64_t FBX ID this is a copy of our original materials unfortunately. const std::vector &material_lookup = model->GetMaterials(); @@ -195,7 +196,7 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo // TODO please add skinning. //mesh_id = mesh_geometry->ID(); - sanitize_vertex_weights(); + sanitize_vertex_weights(state); // Re organize polygon vertices to to correctly take into account strange // UVs. @@ -214,7 +215,6 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo // Make sure that from this moment on the mesh_geometry is no used anymore. // This is a safety step, because the mesh_geometry data are no more valid // at this point. - mesh_geometry = nullptr; const int vertex_count = vertices.size(); @@ -305,7 +305,7 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo // This must be done before add_vertex because the surface tool is // expecting this before the st->add_vertex() call - add_vertex( + add_vertex(state, surface->surface_tool, state.scale, vertex, @@ -350,6 +350,7 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo for (unsigned int vi = 0; vi < surface->vertices_map.size(); vi += 1) { const Vertex vertex = surface->vertices_map[vi]; add_vertex( + state, morph_st, state.scale, vertex, @@ -405,8 +406,26 @@ MeshInstance *FBXMeshData::create_fbx_mesh(const ImportState &state, const FBXDo return godot_mesh; } -void FBXMeshData::sanitize_vertex_weights() { - const int max_bones = VS::ARRAY_WEIGHTS_SIZE; +void FBXMeshData::sanitize_vertex_weights(const ImportState &state) { + const int max_vertex_influence_count = VS::ARRAY_WEIGHTS_SIZE; + Map skeleton_to_skin_bind_id; + // TODO: error's need added + const FBXDocParser::Skin *fbx_skin = mesh_geometry->DeformerSkin(); + + if (fbx_skin == nullptr || fbx_skin->Clusters().size() == 0) { + return; // do nothing + } + + // + // Precalculate the skin cluster mapping + // + + int bind_id = 0; + for (const FBXDocParser::Cluster *cluster : fbx_skin->Clusters()) { + Ref bone = state.fbx_bone_map[cluster->TargetNode()->ID()]; + skeleton_to_skin_bind_id.insert(bone->godot_bone_id, bind_id); + bind_id++; + } for (const Vertex *v = vertex_weights.next(nullptr); v != nullptr; v = vertex_weights.next(v)) { VertexWeightMapping *vm = vertex_weights.getptr(*v); @@ -414,7 +433,6 @@ void FBXMeshData::sanitize_vertex_weights() { ERR_CONTINUE(vm->bones_ref.size() != vm->weights.size()); // No message, already checked. const int initial_size = vm->weights.size(); - { // Init bone id int *bones_ptr = vm->bones.ptrw(); @@ -423,7 +441,7 @@ void FBXMeshData::sanitize_vertex_weights() { for (int i = 0; i < vm->weights.size(); i += 1) { // At this point this is not possible because the skeleton is already initialized. CRASH_COND(bones_ref_ptr[i]->godot_bone_id == -2); - bones_ptr[i] = bones_ref_ptr[i]->godot_bone_id; + bones_ptr[i] = skeleton_to_skin_bind_id[bones_ref_ptr[i]->godot_bone_id]; } // From this point on the data is no more valid. @@ -446,18 +464,18 @@ void FBXMeshData::sanitize_vertex_weights() { { // Resize - vm->weights.resize(max_bones); - vm->bones.resize(max_bones); + vm->weights.resize(max_vertex_influence_count); + vm->bones.resize(max_vertex_influence_count); real_t *weights_ptr = vm->weights.ptrw(); int *bones_ptr = vm->bones.ptrw(); - for (int i = initial_size; i < max_bones; i += 1) { + for (int i = initial_size; i < max_vertex_influence_count; i += 1) { weights_ptr[i] = 0.0; bones_ptr[i] = 0; } // Normalize real_t sum = 0.0; - for (int i = 0; i < max_bones; i += 1) { + for (int i = 0; i < max_vertex_influence_count; i += 1) { sum += weights_ptr[i]; } if (sum > 0.0) { @@ -499,9 +517,22 @@ void FBXMeshData::reorganize_vertices( // Take the normal and see if we need to duplicate this polygon. if (r_normals_raw.has(index)) { const HashMap *nrml_arr = r_normals_raw.getptr(index); + if (nrml_arr->has(polygon_index)) { this_vert_poly_normal = nrml_arr->get(polygon_index); + } else if (nrml_arr->has(-1)) { + this_vert_poly_normal = nrml_arr->get(-1); + } else { + print_error("invalid normal detected: " + itos(index) + " polygon index: " + itos(polygon_index)); + for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) { + print_verbose("debug contents key: " + itos(*pid)); + + if (nrml_arr->has(*pid)) { + print_verbose("contents valid: " + nrml_arr->get(*pid)); + } + } } + // Now, check if we need to duplicate it. for (const PolygonId *pid = nrml_arr->next(nullptr); pid != nullptr; pid = nrml_arr->next(pid)) { if (*pid == polygon_index) { @@ -683,6 +714,7 @@ void FBXMeshData::reorganize_vertices( } void FBXMeshData::add_vertex( + const ImportState &state, Ref p_surface_tool, real_t p_scale, Vertex p_vertex, @@ -719,7 +751,22 @@ void FBXMeshData::add_vertex( // TODO what about binormals? // TODO there is other? - gen_weight_info(p_surface_tool, p_vertex); + if (vertex_weights.has(p_vertex)) { + // Let's extract the weight info. + const VertexWeightMapping *vm = vertex_weights.getptr(p_vertex); + const Vector &bones = vm->bones; + + // the bug is that the bone idx is wrong because it is not ref'ing the skin. + + if (bones.size() > VS::ARRAY_WEIGHTS_SIZE) { + print_error("[weight overflow detected]"); + } + + p_surface_tool->add_weights(vm->weights); + // 0 1 2 3 4 5 6 7 < local skeleton / skin for mesh + // 0 1 2 3 4 5 6 7 8 9 10 < actual skeleton with all joints + p_surface_tool->add_bones(bones); + } // The surface tool want the vertex position as last thing. p_surface_tool->add_vertex((p_vertices_position[p_vertex] + p_morph_value) * p_scale); @@ -895,14 +942,7 @@ void FBXMeshData::gen_weight_info(Ref st, Vertex vertex_id) const { const VertexWeightMapping *vm = vertex_weights.getptr(vertex_id); st->add_weights(vm->weights); st->add_bones(vm->bones); - print_verbose("[doc] Triangle added weights to mesh for bones"); - } else { - // This vertex doesn't have any bone info, while the model is using the - // bones. - // So nothing more to do. } - - print_verbose("[doc] Triangle added weights to mesh for bones"); } int FBXMeshData::get_vertex_from_polygon_vertex(const std::vector &p_polygon_indices, int p_index) const { diff --git a/modules/fbx/data/fbx_mesh_data.h b/modules/fbx/data/fbx_mesh_data.h index 41bc27ee929..76f861280bd 100644 --- a/modules/fbx/data/fbx_mesh_data.h +++ b/modules/fbx/data/fbx_mesh_data.h @@ -40,6 +40,7 @@ #include "import_state.h" #include "tools/import_utils.h" +struct FBXNode; struct FBXMeshData; struct FBXBone; struct ImportState; @@ -68,6 +69,10 @@ struct FBXMeshData : Reference { Vector normals; }; + // FIXME: remove this is a hack for testing only + mutable const FBXDocParser::MeshGeometry *mesh_geometry = nullptr; + + Ref mesh_node = nullptr; /// vertex id, Weight Info /// later: perf we can use array here HashMap vertex_weights; @@ -75,7 +80,7 @@ struct FBXMeshData : Reference { // translate fbx mesh data from document context to FBX Mesh Geometry Context bool valid_weight_indexes = false; - MeshInstance *create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *mesh_geometry, const FBXDocParser::Model *model, bool use_compression); + MeshInstance *create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression); void gen_weight_info(Ref st, int vertex_id) const; @@ -87,7 +92,7 @@ struct FBXMeshData : Reference { MeshInstance *godot_mesh_instance = nullptr; private: - void sanitize_vertex_weights(); + void sanitize_vertex_weights(const ImportState &state); /// Make sure to reorganize the vertices so that the correct UV is taken. /// This step is needed because differently from the normal, that can be @@ -107,6 +112,7 @@ private: HashMap > &r_uv_2_raw); void add_vertex( + const ImportState &state, Ref p_surface_tool, real_t p_scale, int p_vertex, @@ -135,17 +141,17 @@ private: /// Returns -1 if `p_index` is invalid. int get_vertex_from_polygon_vertex(const std::vector &p_face_indices, int p_index) const; - /// Retuns true if this polygon_vertex_index is the end of a new polygon. + /// Returns true if this polygon_vertex_index is the end of a new polygon. bool is_end_of_polygon(const std::vector &p_face_indices, int p_index) const; - /// Retuns true if this polygon_vertex_index is the begin of a new polygon. + /// Returns true if this polygon_vertex_index is the begin of a new polygon. bool is_start_of_polygon(const std::vector &p_face_indices, int p_index) const; /// Returns the number of polygons. int count_polygons(const std::vector &p_face_indices) const; - /// Used to extract data from the `MappingData` alligned with vertex. - /// Useful to extract normal/uvs/colors/tangets/etc... + /// Used to extract data from the `MappingData` aligned with vertex. + /// Useful to extract normal/uvs/colors/tangents/etc... /// If the function fails somehow, it returns an hollow vector and print an error. template HashMap extract_per_vertex_data( @@ -157,7 +163,7 @@ private: R p_fall_back) const; /// Used to extract data from the `MappingData` organized per polygon. - /// Useful to extract the materila + /// Useful to extract the material /// If the function fails somehow, it returns an hollow vector and print an error. template HashMap extract_per_polygon( diff --git a/modules/fbx/data/fbx_skeleton.cpp b/modules/fbx/data/fbx_skeleton.cpp index 4f86e72c2fa..47437bf215e 100644 --- a/modules/fbx/data/fbx_skeleton.cpp +++ b/modules/fbx/data/fbx_skeleton.cpp @@ -40,7 +40,6 @@ void FBXSkeleton::init_skeleton(const ImportState &state) { if (skeleton == nullptr && skeleton_bone_count > 0) { skeleton = memnew(Skeleton); - Ref skeleton_parent_node; if (fbx_node.is_valid()) { // cache skeleton attachment for later during node creation // can't be done until after node hierarchy is built @@ -100,11 +99,11 @@ void FBXSkeleton::init_skeleton(const ImportState &state) { ERR_FAIL_COND_MSG(skeleton->get_bone_count() != bone_count, "Not all bones got added, is the file corrupted?"); for (Map >::Element *bone_element = bone_map.front(); bone_element; bone_element = bone_element->next()) { - Ref bone = bone_element->value(); + const Ref bone = bone_element->value(); int bone_index = bone_element->key(); print_verbose("working on bone: " + itos(bone_index) + " bone name:" + bone->bone_name); - skeleton->set_bone_rest(bone->godot_bone_id, get_unscaled_transform(bone->pivot_xform->LocalTransform, state.scale)); + skeleton->set_bone_rest(bone->godot_bone_id, get_unscaled_transform(bone->node->pivot_transform->LocalTransform, state.scale)); // lookup parent ID if (bone->valid_parent && state.fbx_bone_map.has(bone->parent_bone_id)) { diff --git a/modules/fbx/data/fbx_skeleton.h b/modules/fbx/data/fbx_skeleton.h index 984c42ec766..325ef678e9a 100644 --- a/modules/fbx/data/fbx_skeleton.h +++ b/modules/fbx/data/fbx_skeleton.h @@ -42,7 +42,7 @@ struct FBXNode; struct ImportState; struct FBXBone; -struct FBXSkeleton : Reference, ModelAbstraction { +struct FBXSkeleton : Reference { Ref fbx_node = Ref(); Vector > skeleton_bones = Vector >(); Skeleton *skeleton = nullptr; diff --git a/modules/fbx/data/import_state.h b/modules/fbx/data/import_state.h index 18cf87cdb49..61d3fb33a6e 100644 --- a/modules/fbx/data/import_state.h +++ b/modules/fbx/data/import_state.h @@ -32,7 +32,9 @@ #define IMPORT_STATE_H #include "fbx_mesh_data.h" -#include "modules/fbx/tools/import_utils.h" +#include "tools/import_utils.h" +#include "tools/validation_tools.h" + #include "pivot_transform.h" #include "core/bind/core_bind.h" diff --git a/modules/fbx/data/pivot_transform.cpp b/modules/fbx/data/pivot_transform.cpp index 99a5493af8d..c12e94097fd 100644 --- a/modules/fbx/data/pivot_transform.cpp +++ b/modules/fbx/data/pivot_transform.cpp @@ -80,14 +80,37 @@ void PivotTransform::ReadTransformChain() { const Vector3 &GeometricScaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet(props, "GeometricScaling", ok)); if (ok) { geometric_scaling = GeometricScaling; + } else { + geometric_scaling = Vector3(0, 0, 0); } + const Vector3 &GeometricRotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet(props, "GeometricRotation", ok)); if (ok) { geometric_rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(GeometricRotation)); + } else { + geometric_rotation = Quat(); } + const Vector3 &GeometricTranslation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet(props, "GeometricTranslation", ok)); if (ok) { geometric_translation = ImportUtils::FixAxisConversions(GeometricTranslation); + } else { + geometric_translation = Vector3(0, 0, 0); + } + + if (geometric_rotation != Quat()) { + print_error("geometric rotation is unsupported!"); + //CRASH_COND(true); + } + + if (!geometric_scaling.is_equal_approx(Vector3(1, 1, 1))) { + print_error("geometric scaling is unsupported!"); + //CRASH_COND(true); + } + + if (!geometric_translation.is_equal_approx(Vector3(0, 0, 0))) { + print_error("geometric translation is unsupported."); + //CRASH_COND(true); } } @@ -114,6 +137,20 @@ Transform PivotTransform::ComputeLocalTransform(Vector3 p_translation, Quat p_ro return T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse(); } +Transform PivotTransform::ComputeGlobalTransform(Transform t) const { + Vector3 pos = t.origin; + Vector3 scale = t.basis.get_scale(); + Quat rot = t.basis.get_rotation_quat(); + return ComputeGlobalTransform(pos, rot, scale); +} + +Transform PivotTransform::ComputeLocalTransform(Transform t) const { + Vector3 pos = t.origin; + Vector3 scale = t.basis.get_scale(); + Quat rot = t.basis.get_rotation_quat(); + return ComputeLocalTransform(pos, rot, scale); +} + Transform PivotTransform::ComputeGlobalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const { Transform T, Roff, Rp, Soff, Sp, S; diff --git a/modules/fbx/data/pivot_transform.h b/modules/fbx/data/pivot_transform.h index 3935a8e9caa..1b204f9abaf 100644 --- a/modules/fbx/data/pivot_transform.h +++ b/modules/fbx/data/pivot_transform.h @@ -31,6 +31,7 @@ #ifndef PIVOT_TRANSFORM_H #define PIVOT_TRANSFORM_H +#include "core/math/transform.h" #include "core/reference.h" #include "model_abstraction.h" @@ -84,6 +85,9 @@ struct PivotTransform : Reference, ModelAbstraction { print_verbose("raw pre_rotation " + raw_pre_rotation * (180 / Math_PI)); print_verbose("raw post_rotation " + raw_post_rotation * (180 / Math_PI)); } + + Transform ComputeGlobalTransform(Transform t) const; + Transform ComputeLocalTransform(Transform t) const; Transform ComputeGlobalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const; Transform ComputeLocalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const; diff --git a/modules/fbx/editor_scene_importer_fbx.cpp b/modules/fbx/editor_scene_importer_fbx.cpp index c5b45a9accc..debd64eb3d9 100644 --- a/modules/fbx/editor_scene_importer_fbx.cpp +++ b/modules/fbx/editor_scene_importer_fbx.cpp @@ -151,17 +151,51 @@ Node *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_flag // safety for version handling if (doc.IsSafeToImport()) { - Spatial *spatial = _generate_scene(p_path, &doc, p_flags, p_bake_fps, 8); - - // todo: move to document shutdown (will need to be validated after moving; this code has been validated already) - for (FBXDocParser::TokenPtr token : tokens) { - if (token) { - delete token; - token = nullptr; + bool is_blender_fbx = false; + //const FBXDocParser::PropertyPtr app_vendor = p_document->GlobalSettingsPtr()->Props() + // p_document->Creator() + const FBXDocParser::PropertyTable *import_props = doc.GetMetadataProperties(); + const FBXDocParser::PropertyPtr app_name = import_props->Get("Original|ApplicationName"); + const FBXDocParser::PropertyPtr app_vendor = import_props->Get("Original|ApplicationVendor"); + const FBXDocParser::PropertyPtr app_version = import_props->Get("Original|ApplicationVersion"); + // + if (app_name) { + const FBXDocParser::TypedProperty *app_name_string = dynamic_cast *>(app_name); + if (app_name_string) { + print_verbose("FBX App Name: " + String(app_name_string->Value().c_str())); } } - return spatial; + if (app_vendor) { + const FBXDocParser::TypedProperty *app_vendor_string = dynamic_cast *>(app_vendor); + if (app_vendor_string) { + print_verbose("FBX App Vendor: " + String(app_vendor_string->Value().c_str())); + is_blender_fbx = app_vendor_string->Value().find("Blender") != std::string::npos; + } + } + + if (app_version) { + const FBXDocParser::TypedProperty *app_version_string = dynamic_cast *>(app_version); + if (app_version_string) { + print_verbose("FBX App Version: " + String(app_version_string->Value().c_str())); + } + } + + if (!is_blender_fbx) { + Spatial *spatial = _generate_scene(p_path, &doc, p_flags, p_bake_fps, 8); + // todo: move to document shutdown (will need to be validated after moving; this code has been validated already) + for (FBXDocParser::TokenPtr token : tokens) { + if (token) { + delete token; + token = nullptr; + } + } + + return spatial; + } else { + print_error("We can't import blender FBX files, they're not implemented correctly at export time and would require several hacks in the FBX importer which could break Maya imports."); + } + } else { print_error("Cannot import file: " + p_path + " version of file is unsupported, please re-export in your modelling package file version is: " + itos(doc.FBXVersion())); } @@ -356,6 +390,7 @@ Spatial *EditorSceneImporterFBX::_generate_scene( // Size relative to cm. const real_t fbx_unit_scale = p_document->GlobalSettingsPtr()->UnitScaleFactor(); + print_verbose("FBX unit scale import value: " + rtos(fbx_unit_scale)); // Set FBX file scale is relative to CM must be converted to M state.scale = fbx_unit_scale / 100.0; print_verbose("FBX unit scale is: " + rtos(state.scale)); @@ -366,6 +401,11 @@ Spatial *EditorSceneImporterFBX::_generate_scene( state.enable_animation_import = true; Ref root_node; root_node.instance(); + + // make sure fake noFBXDocParser::PropertyPtr ptrde always has a transform too ;) + Ref pivot_transform; + pivot_transform.instance(); + root_node->pivot_transform = pivot_transform; root_node->node_name = "root node"; root_node->current_node_id = 0; root_node->godot_node = state.root; @@ -376,9 +416,16 @@ Spatial *EditorSceneImporterFBX::_generate_scene( // cache basic node information from FBX document // grabs all FBX bones BuildDocumentBones(Ref(), state, p_document, 0L); - BuildDocumentNodes(nullptr, state, p_document, 0L, nullptr); + BuildDocumentNodes(Ref(), state, p_document, 0L, nullptr); // Build document skinning information + + // Algorithm is this: + // Get Deformer: object with "Skin" class. + // Deformer:: has link to Geometry:: (correct mesh for skin) + // Deformer:: has Source which is the SubDeformer:: (e.g. the Cluster) + // Notes at the end it configures the vertex weight mapping. + for (uint64_t skin_id : p_document->GetSkinIDs()) { // Validate the parser FBXDocParser::LazyObject *lazy_skin = p_document->GetObject(skin_id); @@ -389,7 +436,6 @@ Spatial *EditorSceneImporterFBX::_generate_scene( ERR_CONTINUE_MSG(skin == nullptr, "invalid skin added to skin list [parser bug]"); const std::vector source_to_destination = p_document->GetConnectionsBySourceSequenced(skin_id); - const std::vector destination_to_source = p_document->GetConnectionsByDestinationSequenced(skin_id); FBXDocParser::MeshGeometry *mesh = nullptr; uint64_t mesh_id = 0; @@ -409,21 +455,13 @@ Spatial *EditorSceneImporterFBX::_generate_scene( // Validate the mesh exists and was retrieved ERR_CONTINUE_MSG(mesh_id == 0, "mesh id is invalid"); + const std::vector clusters = skin->Clusters(); // NOTE: this will ONLY work on skinned bones (it is by design.) // A cluster is a skinned bone so SKINS won't contain unskinned bones so we need to pre-add all bones and parent them in a step beforehand. - for (const FBXDocParser::Connection *con : destination_to_source) { - FBXDocParser::Object *ob = con->SourceObject(); - - // - // Read the FBX Document bone information - // - - // Get bone weight data - const FBXDocParser::Cluster *deformer = dynamic_cast(ob); - ERR_CONTINUE_MSG(deformer == nullptr, "invalid bone cluster"); - - const uint64_t deformer_id = deformer->ID(); + for (const FBXDocParser::Cluster *cluster : clusters) { + ERR_CONTINUE_MSG(cluster == nullptr, "invalid bone cluster"); + const uint64_t deformer_id = cluster->ID(); std::vector connections = p_document->GetConnectionsByDestinationSequenced(deformer_id); // Weight data always has a node in the scene lets grab the limb's node in the scene :) (reverse set to true since it's the opposite way around) @@ -444,8 +482,8 @@ Spatial *EditorSceneImporterFBX::_generate_scene( // // Cache Weight Information into bone for later usage if you want the raw data. - const std::vector &indexes = deformer->GetIndices(); - const std::vector &weights = deformer->GetWeights(); + const std::vector &indexes = cluster->GetIndices(); + const std::vector &weights = cluster->GetWeights(); Ref mesh_vertex_data; // this data will pre-exist if vertex weight information is found @@ -468,7 +506,7 @@ Spatial *EditorSceneImporterFBX::_generate_scene( VertexWeightMapping &vm = mesh_vertex_data->vertex_weights[vertex_index]; vm.weights.push_back(influence_weight); - vm.bones.push_back(0); + vm.bones.push_back(0); // bone id is pushed on here during sanitization phase vm.bones_ref.push_back(bone_element); } @@ -531,31 +569,6 @@ Spatial *EditorSceneImporterFBX::_generate_scene( // we opted to merge the entire scene onto one skeleton for now // if we need to change this we have an archive of the old code. - const std::vector &bind_pose_ids = p_document->GetBindPoseIDs(); - - for (uint64_t skin_id : bind_pose_ids) { - - FBXDocParser::LazyObject *lazy_skin = p_document->GetObject(skin_id); - const FBXDocParser::FbxPose *active_skin = lazy_skin->Get(); - - if (active_skin) { - const std::vector &bind_poses = active_skin->GetBindPoses(); - - for (FBXDocParser::FbxPoseNode *pose_node : bind_poses) { - Transform t = pose_node->GetBindPose(); - uint64_t fbx_node_id = pose_node->GetNodeID(); - if (state.fbx_bone_map.has(fbx_node_id)) { - Ref bone = state.fbx_bone_map[fbx_node_id]; - if (bone.is_valid()) { - print_verbose("assigned skin pose from the file for bone " + bone->bone_name + ", transform: " + t); - bone->pose_node = t; - bone->assigned_pose_node = true; - } - } - } - } - } - // bind pose normally only has 1 per mesh but can have more than one // this is the point of skins // in FBX first bind pose is the master for the first skin @@ -586,15 +599,22 @@ Spatial *EditorSceneImporterFBX::_generate_scene( // now populate bone on the armature node list fbx_skeleton_inst->skeleton_bones.push_back(bone); + CRASH_COND_MSG(!state.fbx_target_map.has(armature_id), "invalid armature [serious]"); + + Ref node = state.fbx_target_map[armature_id]; + + CRASH_COND_MSG(node.is_null(), "invalid node [serious]"); + CRASH_COND_MSG(node->pivot_transform.is_null(), "invalid pivot transform [serious]"); + fbx_skeleton_inst->fbx_node = node; + + ERR_CONTINUE_MSG(fbx_skeleton_inst->fbx_node.is_null(), "invalid skeleton node [serious]"); + // we need to have a valid armature id and the model configured for the bone to be assigned fully. // happens once per skeleton - if (state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->has_model()) { - Ref node = state.fbx_target_map[armature_id]; - fbx_skeleton_inst->set_model(node->get_model()); - fbx_skeleton_inst->fbx_node = node; - print_verbose("allocated fbx skeleton primary / armature node for the level: " + node->node_name); - } else if (!state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->has_model()) { + if (state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) { + print_verbose("allocated fbx skeleton primary / armature node for the level: " + fbx_skeleton_inst->fbx_node->node_name); + } else if (!state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) { print_error("bones are not mapped to an armature node for armature id: " + itos(armature_id) + " bone: " + bone->bone_name); // this means bone will be removed and not used, which is safe actually and no skeleton will be created. } @@ -602,7 +622,14 @@ Spatial *EditorSceneImporterFBX::_generate_scene( // setup skeleton instances if required :) for (Map >::Element *skeleton_node = state.skeleton_map.front(); skeleton_node; skeleton_node = skeleton_node->next()) { - skeleton_node->value()->init_skeleton(state); + Ref &skeleton = skeleton_node->value(); + skeleton->init_skeleton(state); + + ERR_CONTINUE_MSG(skeleton->fbx_node.is_null(), "invalid fbx target map, missing skeleton"); + } + + // This list is not populated + for (Map >::Element *skin_mesh = state.MeshNodes.front(); skin_mesh; skin_mesh = skin_mesh->next()) { } } @@ -638,6 +665,8 @@ Spatial *EditorSceneImporterFBX::_generate_scene( state.renderer_mesh_data.insert(mesh_id, mesh_data_precached); } + mesh_data_precached->mesh_node = fbx_node; + // mesh node, mesh id mesh_node = mesh_data_precached->create_fbx_mesh(state, mesh_geometry, fbx_node->fbx_model, (p_flags & IMPORT_USE_COMPRESSION) != 0); if (!state.MeshNodes.has(mesh_id)) { @@ -681,33 +710,69 @@ Spatial *EditorSceneImporterFBX::_generate_scene( } } - for (Map >::Element *skin_mesh = state.MeshNodes.front(); skin_mesh; skin_mesh = skin_mesh->next()) { - const uint64_t mesh_id = skin_mesh->key(); - Ref fbx_node = skin_mesh->value(); + for (Map >::Element *mesh_data = state.renderer_mesh_data.front(); mesh_data; mesh_data = mesh_data->next()) { + const uint64_t mesh_id = mesh_data->key(); + Ref mesh = mesh_data->value(); - ERR_CONTINUE_MSG(state.MeshSkins.has(skin_mesh->key()), "invalid skin already exists for this mesh?"); - print_verbose("[doc] caching skin for " + itos(mesh_id) + ", mesh node name: " + fbx_node->node_name); - Ref skin; - skin.instance(); + const FBXDocParser::MeshGeometry *mesh_geometry = p_document->GetObject(mesh_id)->Get(); - for (Map >::Element *elem = state.fbx_bone_map.front(); elem; elem = elem->next()) { - Ref bone = elem->value(); - Transform ignore_t; - Ref skeleton = bone->fbx_skeleton; - // grab the skin bind - bool valid_bind = false; - Transform bind = bone->get_vertex_skin_xform(state, fbx_node->pivot_transform->GlobalTransform, valid_bind); + ERR_CONTINUE_MSG(mesh->mesh_node.is_null(), "invalid mesh allocation"); - ERR_CONTINUE_MSG(!valid_bind, "invalid bind"); + const FBXDocParser::Skin *mesh_skin = mesh_geometry->DeformerSkin(); - if (bind.basis.determinant() == 0) { - bind = Transform(Basis(), bind.origin); - } - - skin->add_named_bind(bone->bone_name, get_unscaled_transform(bind, state.scale)); + if (!mesh_skin) { + continue; // safe to continue } - state.MeshSkins.insert(mesh_id, skin); + // + // Skin bone configuration + // + + // + // Get Mesh Node Xform only + // + // ERR_CONTINUE_MSG(!state.fbx_target_map.has(mesh_id), "invalid xform for the skin pose: " + itos(mesh_id)); + // Ref mesh_node_xform_data = state.fbx_target_map[mesh_id]; + + if (!mesh_skin) { + continue; // not a deformer. + } + + if (mesh_skin->Clusters().size() == 0) { + continue; // possibly buggy mesh + } + + // Lookup skin or create it if it's not found. + Ref skin; + if (!state.MeshSkins.has(mesh_id)) { + print_verbose("Created new skin"); + skin.instance(); + state.MeshSkins.insert(mesh_id, skin); + } else { + print_verbose("Grabbed skin"); + skin = state.MeshSkins[mesh_id]; + } + + for (const FBXDocParser::Cluster *cluster : mesh_skin->Clusters()) { + // node or bone this cluster targets (in theory will only be a bone target) + uint64_t skin_target_id = cluster->TargetNode()->ID(); + + print_verbose("adding cluster [" + itos(cluster->ID()) + "] " + String(cluster->Name().c_str()) + " for target: [" + itos(skin_target_id) + "] " + String(cluster->TargetNode()->Name().c_str())); + ERR_CONTINUE_MSG(!state.fbx_bone_map.has(skin_target_id), "no bone found by that ID? locator"); + + const Ref bone = state.fbx_bone_map[skin_target_id]; + const Ref skeleton = bone->fbx_skeleton; + const Ref skeleton_node = skeleton->fbx_node; + + skin->add_named_bind( + bone->bone_name, + get_unscaled_transform( + skeleton_node->pivot_transform->GlobalTransform.affine_inverse() * cluster->TransformLink().affine_inverse(), state.scale)); + } + + print_verbose("cluster name / id: " + String(mesh_skin->Name().c_str()) + " [" + itos(mesh_skin->ID()) + "]"); + print_verbose("skeleton has " + itos(state.fbx_bone_map.size()) + " binds"); + print_verbose("fbx skin has " + itos(mesh_skin->Clusters().size()) + " binds"); } // mesh data iteration for populating skeleton mapping @@ -1211,7 +1276,7 @@ Spatial *EditorSceneImporterFBX::_generate_scene( for (Map >::Element *element = state.fbx_bone_map.front(); element; element = element->next()) { Ref bone = element->value(); bone->parent_bone.unref(); - bone->pivot_xform.unref(); + bone->node.unref(); bone->fbx_skeleton.unref(); } @@ -1303,27 +1368,10 @@ void EditorSceneImporterFBX::BuildDocumentBones(Ref p_parent_bone, } uint64_t limb_id = limb_node->ID(); - const FBXDocParser::Cluster *deformer = ProcessDOMConnection(p_doc, limb_id); - + bone_element->bone_id = limb_id; bone_element->bone_name = ImportUtils::FBXNodeToName(model->Name()); bone_element->parent_bone = p_parent_bone; - if (deformer != nullptr) { - - print_verbose("[doc] Mesh Cluster: " + String(deformer->Name().c_str()) + ", " + deformer->TransformLink()); - print_verbose("fbx node: debug name: " + String(model->Name().c_str()) + "bone name: " + String(deformer->Name().c_str())); - - // assign FBX animation bind pose compensation data; - bone_element->transform_link = deformer->TransformLink(); - bone_element->transform_matrix = deformer->GetTransform(); - bone_element->cluster = deformer; - - // skin configures target node ID. - bone_element->target_node_id = deformer->TargetNode()->ID(); - bone_element->valid_target = true; - bone_element->bone_id = limb_id; - } - // insert limb by ID into list. state.fbx_bone_map.insert(limb_node->ID(), bone_element); } @@ -1389,7 +1437,7 @@ void EditorSceneImporterFBX::BuildDocumentNodes( if (state.fbx_bone_map.has(current_node_id)) { Ref bone = state.fbx_bone_map[current_node_id]; if (bone.is_valid()) { - bone->set_pivot_xform(fbx_transform); + bone->set_node(new_node); print_verbose("allocated bone data: " + bone->bone_name); } } @@ -1403,11 +1451,14 @@ void EditorSceneImporterFBX::BuildDocumentNodes( new_node->set_parent(state.fbx_root_node); } + CRASH_COND_MSG(new_node->pivot_transform.is_null(), "invalid fbx target map pivot transform [serious]"); + // populate lookup tables with references // [fbx_node_id, fbx_node] state.fbx_node_list.push_back(new_node); if (!state.fbx_target_map.has(new_node->current_node_id)) { + state.fbx_target_map[new_node->current_node_id] = new_node; } diff --git a/modules/fbx/editor_scene_importer_fbx.h b/modules/fbx/editor_scene_importer_fbx.h index 22ba052934a..1317b58330e 100644 --- a/modules/fbx/editor_scene_importer_fbx.h +++ b/modules/fbx/editor_scene_importer_fbx.h @@ -37,7 +37,10 @@ #include "tools/import_utils.h" #include "core/bind/core_bind.h" +#include "core/dictionary.h" #include "core/io/resource_importer.h" +#include "core/local_vector.h" +#include "core/ustring.h" #include "core/vector.h" #include "editor/import/resource_importer_scene.h" #include "editor/project_settings_editor.h" diff --git a/modules/fbx/fbx_parser/FBXDocument.cpp b/modules/fbx/fbx_parser/FBXDocument.cpp index 17d0d8769fb..4c534e8105f 100644 --- a/modules/fbx/fbx_parser/FBXDocument.cpp +++ b/modules/fbx/fbx_parser/FBXDocument.cpp @@ -184,7 +184,6 @@ ObjectPtr LazyObject::LoadObject() { if (!strcmp(classtag.c_str(), "Cluster")) { object.reset(new Cluster(id, element, doc, name)); } else if (!strcmp(classtag.c_str(), "Skin")) { - object.reset(new Skin(id, element, doc, name)); } else if (!strcmp(classtag.c_str(), "BlendShape")) { object.reset(new BlendShape(id, element, doc, name)); @@ -288,12 +287,15 @@ Document::~Document() { delete v.second; } + if (metadata_properties != nullptr) { + delete metadata_properties; + } // clear globals import pointer globals.reset(); } // ------------------------------------------------------------------------------------------------ -static const unsigned int LowerSupportedVersion = 7100; +static const unsigned int LowerSupportedVersion = 7300; static const unsigned int UpperSupportedVersion = 7700; bool Document::ReadHeader() { @@ -310,11 +312,11 @@ bool Document::ReadHeader() { // While we may have some success with newer files, we don't support // the older 6.n fbx format if (fbxVersion < LowerSupportedVersion) { - DOMWarning("unsupported, old format version, supported are only FBX 2011, FBX 2012 and FBX 2013, you can re-export using Maya, 3DS, blender or Autodesk FBX tool"); + DOMWarning("unsupported, old format version, FBX 2015-2020, you must re-export in a more modern version of your original modelling application"); return false; } if (fbxVersion > UpperSupportedVersion) { - DOMWarning("unsupported, newer format version, supported are only FBX 2011, up to FBX 2020" + DOMWarning("unsupported, newer format version, supported are only FBX 2015, up to FBX 2020" " trying to read it nevertheless"); } @@ -323,6 +325,20 @@ bool Document::ReadHeader() { creator = ParseTokenAsString(GetRequiredToken(ecreator, 0)); } + // + // Scene Info + // + + const ElementPtr scene_info = shead->GetElement("SceneInfo"); + + if (scene_info) { + PropertyTable *fileExportProps = const_cast(GetPropertyTable(*this, "", scene_info, scene_info->Compound(), true)); + + if (fileExportProps) { + metadata_properties = fileExportProps; + } + } + const ElementPtr etimestamp = shead->GetElement("CreationTimeStamp"); if (etimestamp && etimestamp->Compound()) { const ScopePtr stimestamp = etimestamp->Compound(); diff --git a/modules/fbx/fbx_parser/FBXDocument.h b/modules/fbx/fbx_parser/FBXDocument.h index ce37a2e79fd..d23e63302a7 100644 --- a/modules/fbx/fbx_parser/FBXDocument.h +++ b/modules/fbx/fbx_parser/FBXDocument.h @@ -1196,6 +1196,10 @@ public: return globals.get(); } + const PropertyTable *GetMetadataProperties() const { + return metadata_properties; + } + const PropertyTemplateMap &Templates() const { return templates; } @@ -1289,6 +1293,7 @@ private: std::vector materials; std::vector skins; mutable std::vector animationStacksResolved; + PropertyTable *metadata_properties = nullptr; std::shared_ptr globals = nullptr; }; diff --git a/modules/fbx/fbx_parser/FBXMeshGeometry.h b/modules/fbx/fbx_parser/FBXMeshGeometry.h index e4167c638ad..bd89572c696 100644 --- a/modules/fbx/fbx_parser/FBXMeshGeometry.h +++ b/modules/fbx/fbx_parser/FBXMeshGeometry.h @@ -106,7 +106,7 @@ public: } private: - const Skin *skin; + const Skin *skin = nullptr; std::vector blendShapes; }; diff --git a/modules/fbx/fbx_parser/FBXUtil.cpp b/modules/fbx/fbx_parser/FBXUtil.cpp index 704e44d8c7b..463fe2dcfee 100644 --- a/modules/fbx/fbx_parser/FBXUtil.cpp +++ b/modules/fbx/fbx_parser/FBXUtil.cpp @@ -168,10 +168,10 @@ char EncodeBase64(char byte) { } /** Encodes a block of 4 bytes to base64 encoding -* * @param bytes Bytes to encode. * @param out_string String to write encoded values to. -* @param string_pos Position in out_string.*/ +* @param string_pos Position in out_string. +*/ void EncodeByteBlock(const char *bytes, std::string &out_string, size_t string_pos) { char b0 = (bytes[0] & 0xFC) >> 2; char b1 = (bytes[0] & 0x03) << 4 | ((bytes[1] & 0xF0) >> 4); diff --git a/modules/fbx/fbx_parser/FBXUtil.h b/modules/fbx/fbx_parser/FBXUtil.h index c152dec317a..7ddec32a0bc 100644 --- a/modules/fbx/fbx_parser/FBXUtil.h +++ b/modules/fbx/fbx_parser/FBXUtil.h @@ -83,14 +83,6 @@ namespace FBXDocParser { namespace Util { -/** helper for std::for_each to delete all heap-allocated items in a container */ -template -struct delete_fun { - void operator()(const volatile T *del) { - delete del; - } -}; - /** Get a string representation for a #TokenType. */ const char *TokenTypeString(TokenType t); diff --git a/modules/fbx/tools/validation_tools.cpp b/modules/fbx/tools/validation_tools.cpp new file mode 100644 index 00000000000..b240b038b57 --- /dev/null +++ b/modules/fbx/tools/validation_tools.cpp @@ -0,0 +1,48 @@ +/*************************************************************************/ +/* validation_tools.cpp */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#include "validation_tools.h" + +#ifdef TOOLS_ENABLED + +#include "core/print_string.h" +#include "core/ustring.h" + +ValidationTracker::Entries *ValidationTracker::entries_singleton = memnew(ValidationTracker::Entries); + +// for printing our CSV to dump validation problems of files +// later we can make some agnostic tooling for this but this is fine for the time being. +void ValidationTracker::Entries::add_validation_error(String asset_path, String message) { + print_error(message); + // note: implementation is static + validation_entries[asset_path].push_back(message); +} + +#endif // TOOLS_ENABLED diff --git a/modules/fbx/tools/validation_tools.h b/modules/fbx/tools/validation_tools.h new file mode 100644 index 00000000000..b4c1b19fb6c --- /dev/null +++ b/modules/fbx/tools/validation_tools.h @@ -0,0 +1,93 @@ +/*************************************************************************/ +/* validation_tools.h */ +/*************************************************************************/ +/* This file is part of: */ +/* GODOT ENGINE */ +/* https://godotengine.org */ +/*************************************************************************/ +/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur. */ +/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md). */ +/* */ +/* Permission is hereby granted, free of charge, to any person obtaining */ +/* a copy of this software and associated documentation files (the */ +/* "Software"), to deal in the Software without restriction, including */ +/* without limitation the rights to use, copy, modify, merge, publish, */ +/* distribute, sublicense, and/or sell copies of the Software, and to */ +/* permit persons to whom the Software is furnished to do so, subject to */ +/* the following conditions: */ +/* */ +/* The above copyright notice and this permission notice shall be */ +/* included in all copies or substantial portions of the Software. */ +/* */ +/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, */ +/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF */ +/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/ +/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY */ +/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, */ +/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE */ +/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +/*************************************************************************/ + +#ifndef FBX_VALIDATION_TOOLS_H +#define FBX_VALIDATION_TOOLS_H + +#ifdef TOOLS_ENABLED + +#include "core/local_vector.h" +#include "core/map.h" +#include "core/ustring.h" +#include +#include +#include + +class ValidationTracker { +protected: + struct Entries { + Map > validation_entries = Map >(); + + // for printing our CSV to dump validation problems of files + // later we can make some agnostic tooling for this but this is fine for the time being. + void add_validation_error(String asset_path, String message); + void print_to_csv() { + print_verbose("Exporting assset validation log please wait"); + String massive_log_file; + + String csv_header = "file_path, error message, extra data\n"; + massive_log_file += csv_header; + + for (Map >::Element *element = validation_entries.front(); element; element = element->next()) { + for (unsigned int x = 0; x < element->value().size(); x++) { + const String &line_entry = element->key() + ", " + element->value()[x].c_escape() + "\n"; + massive_log_file += line_entry; + } + } + + String path = "asset_validation_errors.csv"; + Error err; + FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &err); + if (!file || err) { + if (file) + memdelete(file); + print_error("ValidationTracker Error - failed to create file - path: %s\n" + path); + return; + } + + file->store_string(massive_log_file); + if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) { + print_error("ValidationTracker Error - failed to write to file - path: %s\n" + path); + } + file->close(); + memdelete(file); + } + }; + // asset path, error messages + static Entries *entries_singleton; + +public: + static Entries *get_singleton() { + return entries_singleton; + } +}; + +#endif // TOOLS_ENABLED +#endif // FBX_VALIDATION_TOOLS_H