summaryrefslogtreecommitdiff
path: root/modules/fbx/data
diff options
context:
space:
mode:
authorGordon MacPherson <gordon@gordonite.tech>2020-12-22 22:29:48 +0000
committerGordon MacPherson <gordon@gordonite.tech>2020-12-23 00:45:03 +0000
commit6607fc7da90d1e17f6bb952b8325d02bc593cccc (patch)
tree837d17d309b01304e3c5ee9e641c38e9645bb120 /modules/fbx/data
parenta003ff0cf2a729edf3e08922ca5341ab8acd05c9 (diff)
Port FBX module from commit 68013d23932688e57b489600f4517dd280edc464
Ports FBX module from 3.2 branch to 4.0 This is the only time the plugin will be updated from 3.2 and marks the final time we do this, from now on we will backport FBX to 3.2 with fixes. Changelog: - fixed crash importing files with buggy format (because of bad newlines in ASCII data, this is yet to be fixed fully) - fixed const correctness with C++/C version change - rewrote material handling to be simpler and better - ports from 3.2 to 4.0 the fbx importer
Diffstat (limited to 'modules/fbx/data')
-rw-r--r--modules/fbx/data/fbx_anim_container.h46
-rw-r--r--modules/fbx/data/fbx_bone.cpp56
-rw-r--r--modules/fbx/data/fbx_bone.h90
-rw-r--r--modules/fbx/data/fbx_material.cpp464
-rw-r--r--modules/fbx/data/fbx_material.h286
-rw-r--r--modules/fbx/data/fbx_mesh_data.cpp1461
-rw-r--r--modules/fbx/data/fbx_mesh_data.h183
-rw-r--r--modules/fbx/data/fbx_node.h63
-rw-r--r--modules/fbx/data/fbx_skeleton.cpp123
-rw-r--r--modules/fbx/data/fbx_skeleton.h53
-rw-r--r--modules/fbx/data/import_state.h112
-rw-r--r--modules/fbx/data/model_abstraction.h52
-rw-r--r--modules/fbx/data/pivot_transform.cpp294
-rw-r--r--modules/fbx/data/pivot_transform.h115
14 files changed, 3398 insertions, 0 deletions
diff --git a/modules/fbx/data/fbx_anim_container.h b/modules/fbx/data/fbx_anim_container.h
new file mode 100644
index 0000000000..e45dd84d6a
--- /dev/null
+++ b/modules/fbx/data/fbx_anim_container.h
@@ -0,0 +1,46 @@
+/*************************************************************************/
+/* fbx_anim_container.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_ANIM_CONTAINER_H
+#define FBX_ANIM_CONTAINER_H
+
+#include "core/math/vector3.h"
+
+// Generic keyframes 99.99 percent of files will be vector3, except if quat interp is used, or visibility tracks
+// FBXTrack is used in a map in the implementation in fbx/editor_scene_importer_fbx.cpp
+// to avoid having to rewrite the entire logic I refactored this into the code instead.
+// once it works I can rewrite so we can add the fun misc features / small features
+struct FBXTrack {
+ bool has_default = false;
+ Vector3 default_value;
+ std::map<int64_t, Vector3> keyframes;
+};
+
+#endif //MODEL_ABSTRACTION_ANIM_CONTAINER_H
diff --git a/modules/fbx/data/fbx_bone.cpp b/modules/fbx/data/fbx_bone.cpp
new file mode 100644
index 0000000000..7ccb0b3717
--- /dev/null
+++ b/modules/fbx/data/fbx_bone.cpp
@@ -0,0 +1,56 @@
+/*************************************************************************/
+/* fbx_bone.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 "fbx_bone.h"
+
+#include "fbx_node.h"
+#include "import_state.h"
+
+Ref<FBXNode> 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) {
+ return nullptr;
+ }
+
+ ERR_FAIL_COND_V_MSG(cluster->TargetNode() == nullptr, nullptr, "bone has invalid target node");
+
+ Ref<FBXNode> link_node;
+ uint64_t id = cluster->TargetNode()->ID();
+ if (state.fbx_target_map.has(id)) {
+ link_node = state.fbx_target_map[id];
+ } else {
+ print_error("link node not found for " + itos(id));
+ }
+
+ // the node in space this is for, like if it's FOR a target.
+ return link_node;
+}
diff --git a/modules/fbx/data/fbx_bone.h b/modules/fbx/data/fbx_bone.h
new file mode 100644
index 0000000000..5d797da24f
--- /dev/null
+++ b/modules/fbx/data/fbx_bone.h
@@ -0,0 +1,90 @@
+/*************************************************************************/
+/* fbx_bone.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_BONE_H
+#define FBX_BONE_H
+
+#include "fbx_node.h"
+#include "import_state.h"
+
+#include "fbx_parser/FBXDocument.h"
+
+struct PivotTransform;
+
+struct FBXBone : public Reference {
+ uint64_t parent_bone_id = 0;
+ uint64_t bone_id = 0;
+
+ bool valid_parent = false; // if the parent bone id is set up.
+ String bone_name = String(); // bone name
+
+ bool is_root_bone() const {
+ return !valid_parent;
+ }
+
+ // Godot specific data
+ int godot_bone_id = -2; // godot internal bone id assigned after import
+
+ // if a bone / armature is the root then FBX skeleton will contain the bone not any other skeleton.
+ // this is to support joints by themselves in scenes
+ bool valid_armature_id = false;
+ uint64_t armature_id = 0;
+
+ /* link node is the parent bone */
+ mutable const FBXDocParser::Geometry *geometry = nullptr;
+ mutable const FBXDocParser::ModelLimbNode *limb_node = nullptr;
+
+ void set_node(Ref<FBXNode> p_node) {
+ node = p_node;
+ }
+
+ // Stores the pivot xform for this bone
+
+ Ref<FBXNode> node = nullptr;
+ Ref<FBXBone> parent_bone = nullptr;
+ Ref<FBXSkeleton> fbx_skeleton = nullptr;
+};
+
+struct FBXSkinDeformer {
+ FBXSkinDeformer(Ref<FBXBone> p_bone, const FBXDocParser::Cluster *p_cluster) :
+ cluster(p_cluster), bone(p_bone) {}
+ ~FBXSkinDeformer() {}
+ const FBXDocParser::Cluster *cluster;
+ Ref<FBXBone> bone;
+
+ /* get associate model - the model can be invalid sometimes */
+ Ref<FBXBone> get_associate_model() const {
+ return bone->parent_bone;
+ }
+
+ Ref<FBXNode> 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
new file mode 100644
index 0000000000..b00ac2083e
--- /dev/null
+++ b/modules/fbx/data/fbx_material.cpp
@@ -0,0 +1,464 @@
+/*************************************************************************/
+/* fbx_material.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 "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;
+}
+
+void FBXMaterial::set_imported_material(FBXDocParser::Material *p_material) {
+ material = p_material;
+}
+
+void FBXMaterial::add_search_string(String p_filename, String p_current_directory, String search_directory, Vector<String> &texture_search_paths) {
+ if (search_directory.empty()) {
+ texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file(p_filename));
+ } else {
+ texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file(search_directory + "/" + p_filename));
+ texture_search_paths.push_back(p_current_directory.get_base_dir().plus_file("../" + search_directory + "/" + p_filename));
+ }
+}
+
+String find_file(const String &p_base, const String &p_file_to_find) {
+ _Directory dir;
+ dir.open(p_base);
+
+ dir.list_dir_begin();
+ String n = dir.get_next();
+ while (n != String()) {
+ if (n == "." || n == "..") {
+ n = dir.get_next();
+ continue;
+ }
+ if (dir.current_is_dir()) {
+ // Don't use `path_to` or the returned path will be wrong.
+ const String f = find_file(p_base + "/" + n, p_file_to_find);
+ if (f != "") {
+ return f;
+ }
+ } else if (n == p_file_to_find) {
+ return p_base + "/" + n;
+ }
+ n = dir.get_next();
+ }
+ dir.list_dir_end();
+
+ return String();
+}
+
+// fbx will not give us good path information and let's not regex them to fix them
+// no relative paths are in fbx generally they have a rel field but it's populated incorrectly by the SDK.
+String FBXMaterial::find_texture_path_by_filename(const String p_filename, const String p_current_directory) {
+ _Directory dir;
+ Vector<String> paths;
+ add_search_string(p_filename, p_current_directory, "", paths);
+ add_search_string(p_filename, p_current_directory, "texture", paths);
+ add_search_string(p_filename, p_current_directory, "textures", paths);
+ add_search_string(p_filename, p_current_directory, "Textures", paths);
+ add_search_string(p_filename, p_current_directory, "materials", paths);
+ add_search_string(p_filename, p_current_directory, "mats", paths);
+ add_search_string(p_filename, p_current_directory, "pictures", paths);
+ add_search_string(p_filename, p_current_directory, "images", paths);
+
+ for (int i = 0; i < paths.size(); i++) {
+ if (dir.file_exists(paths[i])) {
+ return paths[i];
+ }
+ }
+
+ // We were not able to find the texture in the common locations,
+ // try to find it into the project globally.
+ // The common textures can be stored into one of those folders:
+ // res://asset
+ // res://texture
+ // res://material
+ // res://mat
+ // res://image
+ // res://picture
+ //
+ // Note the folders can also be called with custom names, like:
+ // res://my_assets
+ // since the keyword `asset` is into the directory name the textures will be
+ // searched there too.
+
+ dir.open("res://");
+ dir.list_dir_begin();
+ String n = dir.get_next();
+ while (n != String()) {
+ if (n == "." || n == "..") {
+ n = dir.get_next();
+ continue;
+ }
+ if (dir.current_is_dir()) {
+ const String lower_n = n.to_lower();
+ if (
+ // Don't need to use plural.
+ lower_n.find("asset") >= 0 ||
+ lower_n.find("texture") >= 0 ||
+ lower_n.find("material") >= 0 ||
+ lower_n.find("mat") >= 0 ||
+ lower_n.find("image") >= 0 ||
+ lower_n.find("picture") >= 0) {
+ // Don't use `path_to` or the returned path will be wrong.
+ const String f = find_file(String("res://") + n, p_filename);
+ if (f != "") {
+ return f;
+ }
+ }
+ }
+ n = dir.get_next();
+ }
+ dir.list_dir_end();
+
+ return "";
+}
+
+template <class T>
+T extract_from_prop(FBXDocParser::PropertyPtr prop, const T &p_default, const std::string &p_name, const String &p_type) {
+ ERR_FAIL_COND_V_MSG(prop == nullptr, p_default, "invalid property passed to extractor");
+ const FBXDocParser::TypedProperty<T> *val = dynamic_cast<const FBXDocParser::TypedProperty<T> *>(prop);
+
+ ERR_FAIL_COND_V_MSG(val == nullptr, p_default, "The FBX is corrupted, the property `" + String(p_name.c_str()) + "` is a `" + String(typeid(*prop).name()) + "` but should be a " + p_type);
+ // Make sure to not lost any eventual opacity.
+ return val->Value();
+}
+
+Ref<StandardMaterial3D> FBXMaterial::import_material(ImportState &state) {
+ ERR_FAIL_COND_V(material == nullptr, nullptr);
+
+ const String p_fbx_current_directory = state.path;
+
+ Ref<StandardMaterial3D> spatial_material;
+ spatial_material.instance();
+
+ // read the material file
+ // is material two sided
+ // read material name
+ print_verbose("[material] material name: " + ImportUtils::FBXNodeToName(material->Name()));
+
+ material_name = ImportUtils::FBXNodeToName(material->Name());
+
+ for (const std::pair<std::string, const FBXDocParser::Texture *> iter : material->Textures()) {
+ const uint64_t texture_id = iter.second->ID();
+ const std::string &fbx_mapping_name = iter.first;
+ const FBXDocParser::Texture *fbx_texture_data = iter.second;
+ const String absolute_texture_path = iter.second->FileName().c_str();
+ const String texture_name = absolute_texture_path.get_file();
+ const String file_extension = absolute_texture_path.get_extension().to_upper();
+
+ const String debug_string = "texture id: " + itos(texture_id) + " texture name: " + String(iter.second->Name().c_str()) + " mapping name: " + String(fbx_mapping_name.c_str());
+ // remember errors STILL need this string at the end for when you aren't in verbose debug mode :) they need context for when you're not verbose-ing.
+ print_verbose(debug_string);
+
+ const String file_extension_uppercase = file_extension.to_upper();
+
+ if (fbx_transparency_flags.count(fbx_mapping_name) > 0) {
+ // just enable it later let's make this fine-tuned.
+ spatial_material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);
+ }
+
+ ERR_CONTINUE_MSG(file_extension.empty(), "your texture has no file extension so we had to ignore it, let us know if you think this is wrong file an issue on github! " + debug_string);
+ ERR_CONTINUE_MSG(fbx_texture_map.count(fbx_mapping_name) <= 0, "This material has a texture with mapping name: " + String(fbx_mapping_name.c_str()) + " which is not yet supported by this importer. Consider opening an issue so we can support it.");
+ ERR_CONTINUE_MSG(
+ file_extension_uppercase != "PNG" &&
+ file_extension_uppercase != "JPEG" &&
+ file_extension_uppercase != "JPG" &&
+ file_extension_uppercase != "TGA" &&
+ file_extension_uppercase != "WEBP" &&
+ file_extension_uppercase != "DDS",
+ "The FBX file contains a texture with an unrecognized extension: " + file_extension_uppercase);
+
+ print_verbose("Getting FBX mapping mode for " + String(fbx_mapping_name.c_str()));
+ // get the texture map type
+ const StandardMaterial3D::TextureParam mapping_mode = fbx_texture_map.at(fbx_mapping_name);
+ print_verbose("Set FBX mapping mode to " + get_texture_param_name(mapping_mode));
+
+ Ref<Texture> texture;
+ print_verbose("texture mapping name: " + texture_name);
+
+ if (state.cached_image_searches.has(texture_name)) {
+ texture = state.cached_image_searches[texture_name];
+ } else {
+ String path = find_texture_path_by_filename(texture_name, p_fbx_current_directory);
+ if (!path.empty()) {
+ Ref<Texture2D> image_texture = ResourceLoader::load(path);
+
+ ERR_CONTINUE(image_texture.is_null());
+
+ texture = image_texture;
+ state.cached_image_searches.insert(texture_name, texture);
+ print_verbose("Created texture from loaded image file.");
+
+ } else if (fbx_texture_data != nullptr && fbx_texture_data->Media() != nullptr && fbx_texture_data->Media()->IsEmbedded()) {
+ // This is an embedded texture. Extract it.
+ Ref<Image> image;
+ //image.instance(); // oooo double instance bug? why make Image::_png_blah call
+
+ const String extension = texture_name.get_extension().to_upper();
+ if (extension == "PNG") {
+ // The stored file is a PNG.
+ image = Image::_png_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength());
+ ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded PNG image load fail.");
+
+ } else if (
+ extension == "JPEG" ||
+ extension == "JPG") {
+ // The stored file is a JPEG.
+ image = Image::_jpg_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength());
+ ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded JPEG image load fail.");
+
+ } else if (extension == "TGA") {
+ // The stored file is a TGA.
+ image = Image::_tga_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength());
+ ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded TGA image load fail.");
+
+ } else if (extension == "WEBP") {
+ // The stored file is a WEBP.
+ image = Image::_webp_mem_loader_func(fbx_texture_data->Media()->Content(), fbx_texture_data->Media()->ContentLength());
+ ERR_CONTINUE_MSG(image.is_valid() == false, "FBX Embedded WEBP image load fail.");
+
+ // } else if (extension == "DDS") {
+ // // In this moment is not possible to extract a DDS from a buffer, TODO consider add it to godot. See `textureloader_dds.cpp::load().
+ // // The stored file is a DDS.
+ } else {
+ ERR_CONTINUE_MSG(true, "The embedded image with extension: " + extension + " is not yet supported. Open an issue please.");
+ }
+
+ Ref<ImageTexture> image_texture;
+ image_texture.instance();
+ image_texture->create_from_image(image);
+
+ texture = image_texture;
+
+ // TODO: this is potentially making something with the same name have a match incorrectly USE FBX ID as Hash. #fuck it later.
+ state.cached_image_searches[texture_name] = texture;
+ print_verbose("Created texture from embedded image.");
+ } else {
+ ERR_CONTINUE_MSG(true, "The FBX texture, with name: `" + texture_name + "`, is not found into the project nor is stored as embedded file. Make sure to insert the texture as embedded file or into the project, then reimport.");
+ }
+ }
+
+ spatial_material->set_texture(mapping_mode, texture);
+ }
+
+ if (spatial_material.is_valid()) {
+ spatial_material->set_name(material_name);
+ }
+
+ /// ALL below is related to properties
+ for (FBXDocParser::LazyPropertyMap::value_type iter : material->Props()->GetLazyProperties()) {
+ const std::string name = iter.first;
+
+ if (name.empty()) {
+ continue;
+ }
+
+ PropertyDesc desc = PROPERTY_DESC_NOT_FOUND;
+ if (fbx_properties_desc.count(name) > 0) {
+ desc = fbx_properties_desc.at(name);
+ }
+
+ // check if we can ignore this it will be done at the next phase
+ if (desc == PROPERTY_DESC_NOT_FOUND || desc == PROPERTY_DESC_IGNORE) {
+ // count the texture mapping references. Skip this one if it's found and we can't look up a property value.
+ if (fbx_texture_map.count(name) > 0) {
+ continue; // safe to ignore it's a texture mapping.
+ }
+ }
+
+ if (desc == PROPERTY_DESC_IGNORE) {
+ //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
+ if (desc == PROPERTY_DESC_NOT_FOUND) {
+ continue;
+ }
+
+ ERR_CONTINUE_MSG(desc == PROPERTY_DESC_NOT_FOUND, "The FBX material parameter: `" + String(name.c_str()) + "` was not recognized. Please open an issue so we can add the support to it.");
+
+ const FBXDocParser::PropertyTable *tbl = material->Props();
+ FBXDocParser::PropertyPtr prop = tbl->Get(name);
+
+ ERR_CONTINUE_MSG(prop == nullptr, "This file may be corrupted because is not possible to extract the material parameter: " + String(name.c_str()));
+
+ if (spatial_material.is_null()) {
+ // Done here so if no data no material is created.
+ spatial_material.instance();
+ }
+
+ const FBXDocParser::TypedProperty<real_t> *real_value = dynamic_cast<const FBXDocParser::TypedProperty<real_t> *>(prop);
+ const FBXDocParser::TypedProperty<Vector3> *vector_value = dynamic_cast<const FBXDocParser::TypedProperty<Vector3> *>(prop);
+
+ if (!real_value && !vector_value) {
+ //WARN_PRINT("unsupported datatype in property: " + String(name.c_str()));
+ 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.
+ 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?");
+ }
+ } break;
+ case PROPERTY_DESC_TRANSPARENT: {
+ if (real_value) {
+ const real_t opacity = real_value->Value();
+ if (opacity < (1.0 - CMP_EPSILON)) {
+ Color c = spatial_material->get_albedo();
+ c.a = opacity;
+ spatial_material->set_albedo(c);
+
+ spatial_material->set_transparency(BaseMaterial3D::TRANSPARENCY_ALPHA);
+ spatial_material->set_depth_draw_mode(BaseMaterial3D::DEPTH_DRAW_OPAQUE_ONLY);
+ }
+ } else if (vector_value) {
+ print_error("unsupported transparent desc type vector!");
+ }
+ } break;
+ case PROPERTY_DESC_SPECULAR: {
+ if (real_value) {
+ print_verbose("specular real value: " + rtos(real_value->Value()));
+ spatial_material->set_specular(MIN(1.0, real_value->Value()));
+ }
+
+ if (vector_value) {
+ print_error("unsupported specular vector value: " + vector_value->Value());
+ }
+ } break;
+
+ case PROPERTY_DESC_SPECULAR_COLOR: {
+ if (vector_value) {
+ print_error("unsupported specular color: " + vector_value->Value());
+ }
+ } break;
+ case PROPERTY_DESC_SHINYNESS: {
+ if (real_value) {
+ print_error("unsupported shinyness:" + rtos(real_value->Value()));
+ }
+ } break;
+ case PROPERTY_DESC_METALLIC: {
+ if (real_value) {
+ print_verbose("metallic real value: " + rtos(real_value->Value()));
+ spatial_material->set_metallic(MIN(1.0f, real_value->Value()));
+ } else {
+ print_error("unsupported value type for metallic");
+ }
+ } break;
+ case PROPERTY_DESC_ROUGHNESS: {
+ if (real_value) {
+ print_verbose("roughness real value: " + rtos(real_value->Value()));
+ spatial_material->set_roughness(MIN(1.0f, real_value->Value()));
+ } else {
+ print_error("unsupported value type for roughness");
+ }
+ } break;
+ case PROPERTY_DESC_COAT: {
+ if (real_value) {
+ print_verbose("clearcoat real value: " + rtos(real_value->Value()));
+ spatial_material->set_clearcoat(MIN(1.0f, real_value->Value()));
+ } else {
+ print_error("unsupported value type for clearcoat");
+ }
+ } break;
+ case PROPERTY_DESC_COAT_ROUGHNESS: {
+ // 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());
+ } else {
+ print_error("unsupported value type for clearcoat gloss");
+ }
+ } break;
+ case PROPERTY_DESC_EMISSIVE: {
+ 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());
+ } 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];
+ c[1] = color[1];
+ c[2] = color[2];
+ spatial_material->set_emission(c);
+ }
+ } break;
+ case PROPERTY_DESC_EMISSIVE_COLOR: {
+ 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];
+ c[1] = color[1];
+ c[2] = color[2];
+ spatial_material->set_emission(c);
+ } else {
+ print_error("unsupported value type for emissive color");
+ }
+ } break;
+ case PROPERTY_DESC_NOT_FOUND:
+ case PROPERTY_DESC_IGNORE:
+ break;
+ default:
+ break;
+ }
+ }
+
+ return spatial_material;
+}
diff --git a/modules/fbx/data/fbx_material.h b/modules/fbx/data/fbx_material.h
new file mode 100644
index 0000000000..08b93ac01b
--- /dev/null
+++ b/modules/fbx/data/fbx_material.h
@@ -0,0 +1,286 @@
+/*************************************************************************/
+/* fbx_material.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_MATERIAL_H
+#define FBX_MATERIAL_H
+
+#include "tools/import_utils.h"
+
+#include "core/object/reference.h"
+#include "core/string/ustring.h"
+
+struct FBXMaterial : public Reference {
+ String material_name = String();
+ bool warning_non_pbr_material = false;
+ FBXDocParser::Material *material = nullptr;
+
+ /* Godot materials
+ *** Texture Maps:
+ * Albedo - color, texture
+ * Metallic - specular, metallic, texture
+ * Roughness - roughness, texture
+ * Emission - color, texture
+ * Normal Map - scale, texture
+ * Ambient Occlusion - texture
+ * Refraction - scale, texture
+ *** Has Settings for:
+ * UV1 - SCALE, OFFSET
+ * UV2 - SCALE, OFFSET
+ *** Flags for
+ * Transparent
+ * Cull Mode
+ */
+
+ enum class MapMode {
+ AlbedoM = 0,
+ MetallicM,
+ SpecularM,
+ EmissionM,
+ RoughnessM,
+ NormalM,
+ AmbientOcclusionM,
+ RefractionM,
+ ReflectionM,
+ };
+
+ /* Returns the string representation of the TextureParam enum */
+ static String get_texture_param_name(StandardMaterial3D::TextureParam param) {
+ switch (param) {
+ case StandardMaterial3D::TEXTURE_ALBEDO:
+ return "TEXTURE_ALBEDO";
+ case StandardMaterial3D::TEXTURE_METALLIC:
+ return "TEXTURE_METALLIC";
+ case StandardMaterial3D::TEXTURE_ROUGHNESS:
+ return "TEXTURE_ROUGHNESS";
+ case StandardMaterial3D::TEXTURE_EMISSION:
+ return "TEXTURE_EMISSION";
+ case StandardMaterial3D::TEXTURE_NORMAL:
+ return "TEXTURE_NORMAL";
+ case StandardMaterial3D::TEXTURE_RIM:
+ return "TEXTURE_RIM";
+ case StandardMaterial3D::TEXTURE_CLEARCOAT:
+ return "TEXTURE_CLEARCOAT";
+ case StandardMaterial3D::TEXTURE_FLOWMAP:
+ return "TEXTURE_FLOWMAP";
+ case StandardMaterial3D::TEXTURE_AMBIENT_OCCLUSION:
+ return "TEXTURE_AMBIENT_OCCLUSION";
+ // case StandardMaterial3D::TEXTURE_DEPTH: // TODO: work out how to make this function again!
+ // return "TEXTURE_DEPTH";
+ case StandardMaterial3D::TEXTURE_SUBSURFACE_SCATTERING:
+ return "TEXTURE_SUBSURFACE_SCATTERING";
+ // case StandardMaterial3D::TEXTURE_TRANSMISSION: // TODO: work out how to make this function again!
+ // return "TEXTURE_TRANSMISSION";
+ case StandardMaterial3D::TEXTURE_REFRACTION:
+ return "TEXTURE_REFRACTION";
+ case StandardMaterial3D::TEXTURE_DETAIL_MASK:
+ return "TEXTURE_DETAIL_MASK";
+ case StandardMaterial3D::TEXTURE_DETAIL_ALBEDO:
+ return "TEXTURE_DETAIL_ALBEDO";
+ case StandardMaterial3D::TEXTURE_DETAIL_NORMAL:
+ return "TEXTURE_DETAIL_NORMAL";
+ case StandardMaterial3D::TEXTURE_MAX:
+ return "TEXTURE_MAX";
+ default:
+ return "broken horribly";
+ }
+ };
+
+ // TODO make this static?
+ const std::map<std::string, bool> fbx_transparency_flags = {
+ /* Transparent */
+ { "TransparentColor", true },
+ { "Maya|opacity", true }
+ };
+
+ // TODO make this static?
+ const std::map<std::string, StandardMaterial3D::TextureParam> fbx_texture_map = {
+ /* Diffuse */
+ { "Maya|base", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ { "DiffuseColor", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ { "Maya|DiffuseTexture", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ { "Maya|baseColor", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ { "Maya|baseColor|file", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ { "3dsMax|Parameters|base_color_map", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ { "Maya|TEX_color_map|file", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ { "Maya|TEX_color_map", StandardMaterial3D::TextureParam::TEXTURE_ALBEDO },
+ /* Emission */
+ { "EmissiveColor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION },
+ { "EmissiveFactor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION },
+ { "Maya|emissionColor", StandardMaterial3D::TextureParam::TEXTURE_EMISSION },
+ { "Maya|emissionColor|file", StandardMaterial3D::TextureParam::TEXTURE_EMISSION },
+ { "3dsMax|Parameters|emission_map", StandardMaterial3D::TextureParam::TEXTURE_EMISSION },
+ { "Maya|TEX_emissive_map", StandardMaterial3D::TextureParam::TEXTURE_EMISSION },
+ { "Maya|TEX_emissive_map|file", StandardMaterial3D::TextureParam::TEXTURE_EMISSION },
+ /* Metallic */
+ { "Maya|metalness", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ { "Maya|metalness|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ { "3dsMax|Parameters|metalness_map", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ { "Maya|TEX_metallic_map", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ { "Maya|TEX_metallic_map|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+
+ /* Roughness */
+ // Arnold Roughness Map
+ { "Maya|specularRoughness", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS },
+
+ { "3dsMax|Parameters|roughness_map", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS },
+ { "Maya|TEX_roughness_map", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS },
+ { "Maya|TEX_roughness_map|file", StandardMaterial3D::TextureParam::TEXTURE_ROUGHNESS },
+
+ /* Normal */
+ { "NormalMap", StandardMaterial3D::TextureParam::TEXTURE_NORMAL },
+ //{ "Bump", Material::TextureParam::TEXTURE_NORMAL },
+ //{ "3dsMax|Parameters|bump_map", Material::TextureParam::TEXTURE_NORMAL },
+ { "Maya|NormalTexture", StandardMaterial3D::TextureParam::TEXTURE_NORMAL },
+ //{ "Maya|normalCamera", Material::TextureParam::TEXTURE_NORMAL },
+ //{ "Maya|normalCamera|file", Material::TextureParam::TEXTURE_NORMAL },
+ { "Maya|TEX_normal_map", StandardMaterial3D::TextureParam::TEXTURE_NORMAL },
+ { "Maya|TEX_normal_map|file", StandardMaterial3D::TextureParam::TEXTURE_NORMAL },
+ /* AO */
+ { "Maya|TEX_ao_map", StandardMaterial3D::TextureParam::TEXTURE_AMBIENT_OCCLUSION },
+ { "Maya|TEX_ao_map|file", StandardMaterial3D::TextureParam::TEXTURE_AMBIENT_OCCLUSION },
+
+ // TODO: specular workflow conversion
+ // { "SpecularColor", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ // { "Maya|specularColor", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ // { "Maya|SpecularTexture", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ // { "Maya|SpecularTexture|file", StandardMaterial3D::TextureParam::TEXTURE_METALLIC },
+ // { "ShininessExponent", SpatialMaterial::TextureParam::UNSUPPORTED },
+ // { "ReflectionFactor", SpatialMaterial::TextureParam::UNSUPPORTED },
+
+ //{ "TransparentColor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA },
+ //{ "TransparencyFactor",SpatialMaterial::TextureParam::TEXTURE_CHANNEL_ALPHA }
+
+ // TODO: diffuse roughness
+ //{ "Maya|diffuseRoughness", SpatialMaterial::TextureParam::UNSUPPORTED },
+ //{ "Maya|diffuseRoughness|file", SpatialMaterial::TextureParam::UNSUPPORTED },
+
+ };
+
+ // TODO make this static?
+ enum PropertyDesc {
+ PROPERTY_DESC_NOT_FOUND,
+ PROPERTY_DESC_ALBEDO_COLOR,
+ PROPERTY_DESC_TRANSPARENT,
+ PROPERTY_DESC_METALLIC,
+ PROPERTY_DESC_ROUGHNESS,
+ PROPERTY_DESC_SPECULAR,
+ PROPERTY_DESC_SPECULAR_COLOR,
+ PROPERTY_DESC_SHINYNESS,
+ PROPERTY_DESC_COAT,
+ PROPERTY_DESC_COAT_ROUGHNESS,
+ PROPERTY_DESC_EMISSIVE,
+ PROPERTY_DESC_EMISSIVE_COLOR,
+ PROPERTY_DESC_IGNORE
+ };
+
+ const std::map<std::string, PropertyDesc> fbx_properties_desc = {
+ /* Albedo */
+ { "DiffuseColor", PROPERTY_DESC_ALBEDO_COLOR },
+ { "Maya|baseColor", PROPERTY_DESC_ALBEDO_COLOR },
+
+ /* Specular */
+ { "Maya|specular", PROPERTY_DESC_SPECULAR },
+ { "Maya|specularColor", PROPERTY_DESC_SPECULAR_COLOR },
+
+ /* Specular roughness - arnold roughness map */
+ { "Maya|specularRoughness", PROPERTY_DESC_ROUGHNESS },
+
+ /* Transparent */
+ { "Opacity", PROPERTY_DESC_TRANSPARENT },
+ { "TransparencyFactor", PROPERTY_DESC_TRANSPARENT },
+ { "Maya|opacity", PROPERTY_DESC_TRANSPARENT },
+
+ /* Metallic */
+ { "Shininess", PROPERTY_DESC_METALLIC },
+ { "Reflectivity", PROPERTY_DESC_METALLIC },
+ { "Maya|metalness", PROPERTY_DESC_METALLIC },
+ { "Maya|metallic", PROPERTY_DESC_METALLIC },
+
+ /* Roughness */
+ { "Maya|roughness", PROPERTY_DESC_ROUGHNESS },
+
+ /* Coat */
+ //{ "Maya|coat", PROPERTY_DESC_COAT },
+
+ /* Coat roughness */
+ //{ "Maya|coatRoughness", PROPERTY_DESC_COAT_ROUGHNESS },
+
+ /* Emissive */
+ { "Maya|emission", PROPERTY_DESC_EMISSIVE },
+ { "Maya|emissive", PROPERTY_DESC_EMISSIVE },
+
+ /* Emissive color */
+ { "EmissiveColor", PROPERTY_DESC_EMISSIVE_COLOR },
+ { "Maya|emissionColor", PROPERTY_DESC_EMISSIVE_COLOR },
+
+ /* Ignore */
+ { "Maya|diffuseRoughness", PROPERTY_DESC_IGNORE },
+ { "Maya", PROPERTY_DESC_IGNORE },
+ { "Diffuse", PROPERTY_DESC_ALBEDO_COLOR },
+ { "Maya|TypeId", PROPERTY_DESC_IGNORE },
+ { "Ambient", PROPERTY_DESC_IGNORE },
+ { "AmbientColor", PROPERTY_DESC_IGNORE },
+ { "ShininessExponent", PROPERTY_DESC_IGNORE },
+ { "Specular", PROPERTY_DESC_IGNORE },
+ { "SpecularColor", PROPERTY_DESC_IGNORE },
+ { "SpecularFactor", PROPERTY_DESC_IGNORE },
+ //{ "BumpFactor", PROPERTY_DESC_IGNORE },
+ { "Maya|exitToBackground", PROPERTY_DESC_IGNORE },
+ { "Maya|indirectDiffuse", PROPERTY_DESC_IGNORE },
+ { "Maya|indirectSpecular", PROPERTY_DESC_IGNORE },
+ { "Maya|internalReflections", PROPERTY_DESC_IGNORE },
+ { "DiffuseFactor", PROPERTY_DESC_IGNORE },
+ { "AmbientFactor", PROPERTY_DESC_IGNORE },
+ { "ReflectionColor", PROPERTY_DESC_IGNORE },
+ { "Emissive", PROPERTY_DESC_IGNORE },
+ { "Maya|coatColor", PROPERTY_DESC_IGNORE },
+ { "Maya|coatNormal", PROPERTY_DESC_IGNORE },
+ { "Maya|coatIOR", PROPERTY_DESC_IGNORE },
+ };
+
+ /* storing the texture properties like color */
+ template <class T>
+ struct TexturePropertyMapping : Reference {
+ StandardMaterial3D::TextureParam map_mode = StandardMaterial3D::TextureParam::TEXTURE_ALBEDO;
+ const T property = T();
+ };
+
+ static void add_search_string(String p_filename, String p_current_directory, String search_directory, Vector<String> &texture_search_paths);
+
+ static String find_texture_path_by_filename(const String p_filename, const String p_current_directory);
+
+ String get_material_name() const;
+
+ void set_imported_material(FBXDocParser::Material *p_material);
+
+ Ref<StandardMaterial3D> import_material(ImportState &state);
+};
+
+#endif // FBX_MATERIAL_H
diff --git a/modules/fbx/data/fbx_mesh_data.cpp b/modules/fbx/data/fbx_mesh_data.cpp
new file mode 100644
index 0000000000..3dc84bd1f4
--- /dev/null
+++ b/modules/fbx/data/fbx_mesh_data.cpp
@@ -0,0 +1,1461 @@
+/*************************************************************************/
+/* fbx_mesh_data.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 "fbx_mesh_data.h"
+
+#include "core/templates/local_vector.h"
+#include "scene/resources/mesh.h"
+#include "scene/resources/surface_tool.h"
+
+#include "thirdparty/misc/triangulator.h"
+
+template <class T>
+T collect_first(const Vector<VertexData<T>> *p_data, T p_fall_back) {
+ if (p_data->empty()) {
+ return p_fall_back;
+ }
+
+ return (*p_data)[0].data;
+}
+
+template <class T>
+HashMap<int, T> collect_all(const Vector<VertexData<T>> *p_data, HashMap<int, T> p_fall_back) {
+ if (p_data->empty()) {
+ return p_fall_back;
+ }
+
+ HashMap<int, T> collection;
+ for (int i = 0; i < p_data->size(); i += 1) {
+ const VertexData<T> &vd = (*p_data)[i];
+ collection[vd.polygon_index] = vd.data;
+ }
+ return collection;
+}
+
+template <class T>
+T collect_average(const Vector<VertexData<T>> *p_data, T p_fall_back) {
+ if (p_data->empty()) {
+ return p_fall_back;
+ }
+
+ T combined = (*p_data)[0].data; // Make sure the data is always correctly initialized.
+ print_verbose("size of data: " + itos(p_data->size()));
+ for (int i = 1; i < p_data->size(); i += 1) {
+ combined += (*p_data)[i].data;
+ }
+ combined = combined / real_t(p_data->size());
+
+ return combined.normalized();
+}
+
+HashMap<int, Vector3> collect_normal(const Vector<VertexData<Vector3>> *p_data, HashMap<int, Vector3> p_fall_back) {
+ if (p_data->empty()) {
+ return p_fall_back;
+ }
+
+ HashMap<int, Vector3> collection;
+ for (int i = 0; i < p_data->size(); i += 1) {
+ const VertexData<Vector3> &vd = (*p_data)[i];
+ collection[vd.polygon_index] = vd.data;
+ }
+ return collection;
+}
+
+HashMap<int, Vector2> collect_uv(const Vector<VertexData<Vector2>> *p_data, HashMap<int, Vector2> p_fall_back) {
+ if (p_data->empty()) {
+ return p_fall_back;
+ }
+
+ HashMap<int, Vector2> collection;
+ for (int i = 0; i < p_data->size(); i += 1) {
+ const VertexData<Vector2> &vd = (*p_data)[i];
+ collection[vd.polygon_index] = vd.data;
+ }
+ return collection;
+}
+
+typedef int Vertex;
+typedef int SurfaceId;
+typedef int PolygonId;
+typedef int DataIndex;
+
+struct SurfaceData {
+ Ref<SurfaceTool> surface_tool;
+ OrderedHashMap<Vertex, int> lookup_table; // proposed fix is to replace lookup_table[vertex_id] to give the position of the vertices_map[int] index.
+ LocalVector<Vertex> vertices_map; // this must be ordered the same as insertion <-- slow to do find() operation.
+ Ref<Material> material;
+ HashMap<PolygonId, Vector<DataIndex>> surface_polygon_vertex;
+ Array morphs;
+};
+
+EditorSceneImporterMeshNode *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<const FBXDocParser::Material *> &material_lookup = model->GetMaterials();
+
+ // TODO: perf hotspot on large files
+ // this can be a very large copy
+ std::vector<int> polygon_indices = mesh_geometry->get_polygon_indices();
+ std::vector<Vector3> vertices = mesh_geometry->get_vertices();
+
+ // Phase 1. Parse all FBX data.
+ HashMap<int, Vector3> normals;
+ HashMap<int, HashMap<int, Vector3>> normals_raw = extract_per_vertex_data(
+ vertices.size(),
+ mesh_geometry->get_edge_map(),
+ polygon_indices,
+ mesh_geometry->get_normals(),
+ &collect_all,
+ HashMap<int, Vector3>());
+
+ // List<int> keys;
+ // normals.get_key_list(&keys);
+ //
+ // const std::vector<Assimp::FBX::MeshGeometry::Edge>& edges = mesh_geometry->get_edge_map();
+ // for (int index = 0; index < keys.size(); index++) {
+ // const int key = keys[index];
+ // const int v1 = edges[key].vertex_0;
+ // const int v2 = edges[key].vertex_1;
+ // const Vector3& n1 = normals.get(v1);
+ // const Vector3& n2 = normals.get(v2);
+ // print_verbose("[" + itos(v1) + "] n1: " + n1 + "\n[" + itos(v2) + "] n2: " + n2);
+ // //print_verbose("[" + itos(key) + "] n1: " + n1 + ", n2: " + n2) ;
+ // //print_verbose("vindex: " + itos(edges[key].vertex_0) + ", vindex2: " + itos(edges[key].vertex_1));
+ // //Vector3 ver1 = vertices[edges[key].vertex_0];
+ // //Vector3 ver2 = vertices[edges[key].vertex_1];
+ // /*real_t angle1 = Math::rad2deg(n1.angle_to(n2));
+ // real_t angle2 = Math::rad2deg(n2.angle_to(n1));
+ // print_verbose("angle of normals: " + rtos(angle1) + " angle 2" + rtos(angle2));*/
+ // }
+
+ HashMap<int, Vector2> uvs_0;
+ HashMap<int, HashMap<int, Vector2>> uvs_0_raw = extract_per_vertex_data(
+ vertices.size(),
+ mesh_geometry->get_edge_map(),
+ polygon_indices,
+ mesh_geometry->get_uv_0(),
+ &collect_all,
+ HashMap<int, Vector2>());
+
+ HashMap<int, Vector2> uvs_1;
+ HashMap<int, HashMap<int, Vector2>> uvs_1_raw = extract_per_vertex_data(
+ vertices.size(),
+ mesh_geometry->get_edge_map(),
+ polygon_indices,
+ mesh_geometry->get_uv_1(),
+ &collect_all,
+ HashMap<int, Vector2>());
+
+ HashMap<int, Color> colors;
+ HashMap<int, HashMap<int, Color>> colors_raw = extract_per_vertex_data(
+ vertices.size(),
+ mesh_geometry->get_edge_map(),
+ polygon_indices,
+ mesh_geometry->get_colors(),
+ &collect_all,
+ HashMap<int, Color>());
+
+ // TODO what about tangents?
+ // TODO what about bi-nomials?
+ // TODO there is other?
+
+ HashMap<int, SurfaceId> polygon_surfaces = extract_per_polygon(
+ vertices.size(),
+ polygon_indices,
+ mesh_geometry->get_material_allocation_id(),
+ -1);
+
+ HashMap<String, MorphVertexData> morphs;
+ extract_morphs(mesh_geometry, morphs);
+
+ // TODO please add skinning.
+ //mesh_id = mesh_geometry->ID();
+
+ sanitize_vertex_weights(state);
+
+ // Re organize polygon vertices to to correctly take into account strange
+ // UVs.
+ reorganize_vertices(
+ polygon_indices,
+ vertices,
+ normals,
+ uvs_0,
+ uvs_1,
+ colors,
+ morphs,
+ normals_raw,
+ colors_raw,
+ uvs_0_raw,
+ uvs_1_raw);
+
+ const int color_count = colors.size();
+ print_verbose("Vertex color count: " + itos(color_count));
+
+ // 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.
+
+ const int vertex_count = vertices.size();
+
+ print_verbose("Vertex count: " + itos(vertex_count));
+
+ // The map key is the material allocator id that is also used as surface id.
+ HashMap<SurfaceId, SurfaceData> surfaces;
+
+ // Phase 2. For each material create a surface tool (So a different mesh).
+ {
+ if (polygon_surfaces.empty()) {
+ // No material, just use the default one with index -1.
+ // Set -1 to all polygons.
+ const int polygon_count = count_polygons(polygon_indices);
+ for (int p = 0; p < polygon_count; p += 1) {
+ polygon_surfaces[p] = -1;
+ }
+ }
+
+ // Create the surface now.
+ for (const int *polygon_id = polygon_surfaces.next(nullptr); polygon_id != nullptr; polygon_id = polygon_surfaces.next(polygon_id)) {
+ const int surface_id = polygon_surfaces[*polygon_id];
+ if (surfaces.has(surface_id) == false) {
+ SurfaceData sd;
+ sd.surface_tool.instance();
+ sd.surface_tool->begin(Mesh::PRIMITIVE_TRIANGLES);
+
+ if (surface_id < 0) {
+ // nothing to do
+ } else if (surface_id < (int)material_lookup.size()) {
+ const FBXDocParser::Material *mat_mapping = material_lookup.at(surface_id);
+ const uint64_t mapping_id = mat_mapping->ID();
+ if (state.cached_materials.has(mapping_id)) {
+ sd.material = state.cached_materials[mapping_id];
+ }
+ } else {
+ WARN_PRINT("out of bounds surface detected, FBX file has corrupt material data");
+ }
+
+ surfaces.set(surface_id, sd);
+ }
+ }
+ }
+
+ // Phase 3. Map the vertices relative to each surface, in this way we can
+ // just insert the vertices that we need per each surface.
+ {
+ PolygonId polygon_index = -1;
+ SurfaceId surface_id = -1;
+ SurfaceData *surface_data = nullptr;
+
+ for (size_t polygon_vertex = 0; polygon_vertex < polygon_indices.size(); polygon_vertex += 1) {
+ if (is_start_of_polygon(polygon_indices, polygon_vertex)) {
+ polygon_index += 1;
+ ERR_FAIL_COND_V_MSG(polygon_surfaces.has(polygon_index) == false, nullptr, "The FBX file is corrupted, This surface_index is not expected.");
+ surface_id = polygon_surfaces[polygon_index];
+ surface_data = surfaces.getptr(surface_id);
+ CRASH_COND(surface_data == nullptr); // Can't be null.
+ }
+
+ const int vertex = get_vertex_from_polygon_vertex(polygon_indices, polygon_vertex);
+
+ // The vertex position in the surface
+ // Uses a lookup table for speed with large scenes
+ bool has_polygon_vertex_index = surface_data->lookup_table.has(vertex);
+ int surface_polygon_vertex_index = -1;
+
+ if (has_polygon_vertex_index) {
+ surface_polygon_vertex_index = surface_data->lookup_table[vertex];
+ } else {
+ surface_polygon_vertex_index = surface_data->vertices_map.size();
+ surface_data->lookup_table[vertex] = surface_polygon_vertex_index;
+ surface_data->vertices_map.push_back(vertex);
+ }
+
+ surface_data->surface_polygon_vertex[polygon_index].push_back(surface_polygon_vertex_index);
+ }
+ }
+
+ //print_verbose("[debug UV 1] UV1: " + itos(uvs_0.size()));
+ //print_verbose("[debug UV 2] UV2: " + itos(uvs_1.size()));
+
+ // Phase 4. Per each surface just insert the vertices and add the indices.
+ for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) {
+ SurfaceData *surface = surfaces.getptr(*surface_id);
+
+ // Just add the vertices data.
+ for (unsigned int i = 0; i < surface->vertices_map.size(); i += 1) {
+ const Vertex vertex = surface->vertices_map[i];
+
+ // This must be done before add_vertex because the surface tool is
+ // expecting this before the st->add_vertex() call
+ add_vertex(state,
+ surface->surface_tool,
+ state.scale,
+ vertex,
+ vertices,
+ normals,
+ uvs_0,
+ uvs_1,
+ colors);
+ }
+
+ // Triangulate the various polygons and add the indices.
+ for (const PolygonId *polygon_id = surface->surface_polygon_vertex.next(nullptr); polygon_id != nullptr; polygon_id = surface->surface_polygon_vertex.next(polygon_id)) {
+ const Vector<DataIndex> *indices = surface->surface_polygon_vertex.getptr(*polygon_id);
+
+ triangulate_polygon(
+ surface->surface_tool,
+ *indices,
+ surface->vertices_map,
+ vertices);
+ }
+ }
+
+ // Phase 5. Compose the morphs if any.
+ for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) {
+ SurfaceData *surface = surfaces.getptr(*surface_id);
+
+ for (const String *morph_name = morphs.next(nullptr); morph_name != nullptr; morph_name = morphs.next(morph_name)) {
+ MorphVertexData *morph_data = morphs.getptr(*morph_name);
+
+ // As said by the docs, this is not supposed to be different than
+ // vertex_count.
+ CRASH_COND(morph_data->vertices.size() != vertex_count);
+ CRASH_COND(morph_data->normals.size() != vertex_count);
+
+ Vector3 *vertices_ptr = morph_data->vertices.ptrw();
+ Vector3 *normals_ptr = morph_data->normals.ptrw();
+
+ Ref<SurfaceTool> morph_st;
+ morph_st.instance();
+ morph_st->begin(Mesh::PRIMITIVE_TRIANGLES);
+
+ 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,
+ vertices,
+ normals,
+ uvs_0,
+ uvs_1,
+ colors,
+ vertices_ptr[vertex],
+ normals_ptr[vertex]);
+ }
+
+ morph_st->generate_tangents();
+ surface->morphs.push_back(morph_st->commit_to_arrays());
+ }
+ }
+
+ // Phase 6. Compose the mesh and return it.
+ Ref<EditorSceneImporterMesh> mesh;
+ mesh.instance();
+
+ // Add blend shape info.
+ for (const String *morph_name = morphs.next(nullptr); morph_name != nullptr; morph_name = morphs.next(morph_name)) {
+ mesh->add_blend_shape(*morph_name);
+ }
+
+ // TODO always normalized, Why?
+ mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
+
+ // Add surfaces.
+ int in_mesh_surface_id = 0;
+ for (const SurfaceId *surface_id = surfaces.next(nullptr); surface_id != nullptr; surface_id = surfaces.next(surface_id)) {
+ SurfaceData *surface = surfaces.getptr(*surface_id);
+
+ // you can't generate them without a valid uv map.
+ if (uvs_0_raw.size() > 0) {
+ surface->surface_tool->generate_tangents();
+ }
+
+ Array mesh_array = surface->surface_tool->commit_to_arrays();
+ Array blend_shapes = surface->morphs;
+
+ if (surface->material.is_valid()) {
+ mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, mesh_array, blend_shapes, Dictionary(), surface->material, surface->material->get_name());
+ } else {
+ mesh->add_surface(Mesh::PRIMITIVE_TRIANGLES, mesh_array, blend_shapes);
+ }
+
+ in_mesh_surface_id += 1;
+ }
+
+ EditorSceneImporterMeshNode *godot_mesh = memnew(EditorSceneImporterMeshNode);
+ godot_mesh->set_mesh(mesh);
+ return godot_mesh;
+}
+
+void FBXMeshData::sanitize_vertex_weights(const ImportState &state) {
+ const int max_vertex_influence_count = RS::ARRAY_WEIGHTS_SIZE;
+ Map<int, int> 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<FBXBone> 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);
+ ERR_CONTINUE(vm->bones.size() != vm->weights.size()); // No message, already checked.
+ 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();
+ Ref<FBXBone> *bones_ref_ptr = vm->bones_ref.ptrw();
+
+ 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] = skeleton_to_skin_bind_id[bones_ref_ptr[i]->godot_bone_id];
+ }
+
+ // From this point on the data is no more valid.
+ vm->bones_ref.clear();
+ }
+
+ {
+ // Sort
+ real_t *weights_ptr = vm->weights.ptrw();
+ int *bones_ptr = vm->bones.ptrw();
+ for (int i = 0; i < vm->weights.size(); i += 1) {
+ for (int x = i + 1; x < vm->weights.size(); x += 1) {
+ if (weights_ptr[i] < weights_ptr[x]) {
+ SWAP(weights_ptr[i], weights_ptr[x]);
+ SWAP(bones_ptr[i], bones_ptr[x]);
+ }
+ }
+ }
+ }
+
+ {
+ // Resize
+ 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_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_vertex_influence_count; i += 1) {
+ sum += weights_ptr[i];
+ }
+ if (sum > 0.0) {
+ for (int i = 0; i < vm->weights.size(); i += 1) {
+ weights_ptr[i] = weights_ptr[i] / sum;
+ }
+ }
+ }
+ }
+}
+
+void FBXMeshData::reorganize_vertices(
+ // TODO: perf hotspot on insane files
+ std::vector<int> &r_polygon_indices,
+ std::vector<Vector3> &r_vertices,
+ HashMap<int, Vector3> &r_normals,
+ HashMap<int, Vector2> &r_uv_1,
+ HashMap<int, Vector2> &r_uv_2,
+ HashMap<int, Color> &r_color,
+ HashMap<String, MorphVertexData> &r_morphs,
+ HashMap<int, HashMap<int, Vector3>> &r_normals_raw,
+ HashMap<int, HashMap<int, Color>> &r_colors_raw,
+ HashMap<int, HashMap<int, Vector2>> &r_uv_1_raw,
+ HashMap<int, HashMap<int, Vector2>> &r_uv_2_raw) {
+ // Key: OldVertex; Value: [New vertices];
+ HashMap<int, Vector<int>> duplicated_vertices;
+
+ PolygonId polygon_index = -1;
+ for (int pv = 0; pv < (int)r_polygon_indices.size(); pv += 1) {
+ if (is_start_of_polygon(r_polygon_indices, pv)) {
+ polygon_index += 1;
+ }
+ const Vertex index = get_vertex_from_polygon_vertex(r_polygon_indices, pv);
+
+ bool need_duplication = false;
+ Vector2 this_vert_poly_uv1 = Vector2();
+ Vector2 this_vert_poly_uv2 = Vector2();
+ Vector3 this_vert_poly_normal = Vector3();
+ Color this_vert_poly_color = Color();
+
+ // Take the normal and see if we need to duplicate this polygon.
+ if (r_normals_raw.has(index)) {
+ const HashMap<PolygonId, Vector3> *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) {
+ continue;
+ }
+
+ const Vector3 vert_poly_normal = *nrml_arr->getptr(*pid);
+ if ((this_vert_poly_normal - vert_poly_normal).length_squared() > CMP_EPSILON) {
+ // Yes this polygon need duplication.
+ need_duplication = true;
+ break;
+ }
+ }
+ }
+
+ // TODO: make me vertex color
+ // Take the normal and see if we need to duplicate this polygon.
+ if (r_colors_raw.has(index)) {
+ const HashMap<PolygonId, Color> *color_arr = r_colors_raw.getptr(index);
+
+ if (color_arr->has(polygon_index)) {
+ this_vert_poly_color = color_arr->get(polygon_index);
+ } else if (color_arr->has(-1)) {
+ this_vert_poly_color = color_arr->get(-1);
+ } else {
+ print_error("invalid color detected: " + itos(index) + " polygon index: " + itos(polygon_index));
+ for (const PolygonId *pid = color_arr->next(nullptr); pid != nullptr; pid = color_arr->next(pid)) {
+ print_verbose("debug contents key: " + itos(*pid));
+
+ if (color_arr->has(*pid)) {
+ print_verbose("contents valid: " + color_arr->get(*pid));
+ }
+ }
+ }
+
+ // Now, check if we need to duplicate it.
+ for (const PolygonId *pid = color_arr->next(nullptr); pid != nullptr; pid = color_arr->next(pid)) {
+ if (*pid == polygon_index) {
+ continue;
+ }
+
+ const Color vert_poly_color = *color_arr->getptr(*pid);
+ if (!this_vert_poly_color.is_equal_approx(vert_poly_color)) {
+ // Yes this polygon need duplication.
+ need_duplication = true;
+ break;
+ }
+ }
+ }
+
+ // Take the UV1 and UV2 and see if we need to duplicate this polygon.
+ {
+ HashMap<int, HashMap<int, Vector2>> *uv_raw = &r_uv_1_raw;
+ Vector2 *this_vert_poly_uv = &this_vert_poly_uv1;
+ for (int kk = 0; kk < 2; kk++) {
+ if (uv_raw->has(index)) {
+ const HashMap<PolygonId, Vector2> *uvs = uv_raw->getptr(index);
+
+ if (uvs->has(polygon_index)) {
+ // This Polygon has its own uv.
+ (*this_vert_poly_uv) = *uvs->getptr(polygon_index);
+
+ // Check if we need to duplicate it.
+ for (const PolygonId *pid = uvs->next(nullptr); pid != nullptr; pid = uvs->next(pid)) {
+ if (*pid == polygon_index) {
+ continue;
+ }
+ const Vector2 vert_poly_uv = *uvs->getptr(*pid);
+ if (((*this_vert_poly_uv) - vert_poly_uv).length_squared() > CMP_EPSILON) {
+ // Yes this polygon need duplication.
+ need_duplication = true;
+ break;
+ }
+ }
+ } else if (uvs->has(-1)) {
+ // It has the default UV.
+ (*this_vert_poly_uv) = *uvs->getptr(-1);
+ } else if (uvs->size() > 0) {
+ // No uv, this is strange, just take the first and duplicate.
+ (*this_vert_poly_uv) = *uvs->getptr(*uvs->next(nullptr));
+ WARN_PRINT("No UVs for this polygon, while there is no default and some other polygons have it. This FBX file may be corrupted.");
+ }
+ }
+ uv_raw = &r_uv_2_raw;
+ this_vert_poly_uv = &this_vert_poly_uv2;
+ }
+ }
+
+ // If we want to duplicate it, Let's see if we already duplicated this
+ // vertex.
+ if (need_duplication) {
+ if (duplicated_vertices.has(index)) {
+ Vertex similar_vertex = -1;
+ // Let's see if one of the new vertices has the same data of this.
+ const Vector<int> *new_vertices = duplicated_vertices.getptr(index);
+ for (int j = 0; j < new_vertices->size(); j += 1) {
+ const Vertex new_vertex = (*new_vertices)[j];
+ bool same_uv1 = false;
+ bool same_uv2 = false;
+ bool same_normal = false;
+ bool same_color = false;
+
+ if (r_uv_1.has(new_vertex)) {
+ if ((this_vert_poly_uv1 - (*r_uv_1.getptr(new_vertex))).length_squared() <= CMP_EPSILON) {
+ same_uv1 = true;
+ }
+ }
+
+ if (r_uv_2.has(new_vertex)) {
+ if ((this_vert_poly_uv2 - (*r_uv_2.getptr(new_vertex))).length_squared() <= CMP_EPSILON) {
+ same_uv2 = true;
+ }
+ }
+
+ if (r_color.has(new_vertex)) {
+ if (this_vert_poly_color.is_equal_approx((*r_color.getptr(new_vertex)))) {
+ same_color = true;
+ }
+ }
+
+ if (r_normals.has(new_vertex)) {
+ if ((this_vert_poly_normal - (*r_normals.getptr(new_vertex))).length_squared() <= CMP_EPSILON) {
+ same_uv2 = true;
+ }
+ }
+
+ if (same_uv1 && same_uv2 && same_normal && same_color) {
+ similar_vertex = new_vertex;
+ break;
+ }
+ }
+
+ if (similar_vertex != -1) {
+ // Update polygon.
+ if (is_end_of_polygon(r_polygon_indices, pv)) {
+ r_polygon_indices[pv] = ~similar_vertex;
+ } else {
+ r_polygon_indices[pv] = similar_vertex;
+ }
+ need_duplication = false;
+ }
+ }
+ }
+
+ if (need_duplication) {
+ const Vertex old_index = index;
+ const Vertex new_index = r_vertices.size();
+
+ // Polygon index.
+ if (is_end_of_polygon(r_polygon_indices, pv)) {
+ r_polygon_indices[pv] = ~new_index;
+ } else {
+ r_polygon_indices[pv] = new_index;
+ }
+
+ // Vertex position.
+ r_vertices.push_back(r_vertices[old_index]);
+
+ // Normals
+ if (r_normals_raw.has(old_index)) {
+ r_normals.set(new_index, this_vert_poly_normal);
+ r_normals_raw.getptr(old_index)->erase(polygon_index);
+ r_normals_raw[new_index][polygon_index] = this_vert_poly_normal;
+ }
+
+ // Vertex Color
+ if (r_colors_raw.has(old_index)) {
+ r_color.set(new_index, this_vert_poly_color);
+ r_colors_raw.getptr(old_index)->erase(polygon_index);
+ r_colors_raw[new_index][polygon_index] = this_vert_poly_color;
+ }
+
+ // UV 0
+ if (r_uv_1_raw.has(old_index)) {
+ r_uv_1.set(new_index, this_vert_poly_uv1);
+ r_uv_1_raw.getptr(old_index)->erase(polygon_index);
+ r_uv_1_raw[new_index][polygon_index] = this_vert_poly_uv1;
+ }
+
+ // UV 1
+ if (r_uv_2_raw.has(old_index)) {
+ r_uv_2.set(new_index, this_vert_poly_uv2);
+ r_uv_2_raw.getptr(old_index)->erase(polygon_index);
+ r_uv_2_raw[new_index][polygon_index] = this_vert_poly_uv2;
+ }
+
+ // Morphs
+ for (const String *mname = r_morphs.next(nullptr); mname != nullptr; mname = r_morphs.next(mname)) {
+ MorphVertexData *d = r_morphs.getptr(*mname);
+ // This can't never happen.
+ CRASH_COND(d == nullptr);
+ if (d->vertices.size() > old_index) {
+ d->vertices.push_back(d->vertices[old_index]);
+ }
+ if (d->normals.size() > old_index) {
+ d->normals.push_back(d->normals[old_index]);
+ }
+ }
+
+ if (vertex_weights.has(old_index)) {
+ vertex_weights.set(new_index, vertex_weights[old_index]);
+ }
+
+ duplicated_vertices[old_index].push_back(new_index);
+ } else {
+ if (r_normals_raw.has(index) &&
+ r_normals.has(index) == false) {
+ r_normals.set(index, this_vert_poly_normal);
+ }
+
+ if (r_colors_raw.has(index) && r_color.has(index) == false) {
+ r_color.set(index, this_vert_poly_color);
+ }
+
+ if (r_uv_1_raw.has(index) &&
+ r_uv_1.has(index) == false) {
+ r_uv_1.set(index, this_vert_poly_uv1);
+ }
+
+ if (r_uv_2_raw.has(index) &&
+ r_uv_2.has(index) == false) {
+ r_uv_2.set(index, this_vert_poly_uv2);
+ }
+ }
+ }
+}
+
+void FBXMeshData::add_vertex(
+ const ImportState &state,
+ Ref<SurfaceTool> p_surface_tool,
+ real_t p_scale,
+ Vertex p_vertex,
+ const std::vector<Vector3> &p_vertices_position,
+ const HashMap<int, Vector3> &p_normals,
+ const HashMap<int, Vector2> &p_uvs_0,
+ const HashMap<int, Vector2> &p_uvs_1,
+ const HashMap<int, Color> &p_colors,
+ const Vector3 &p_morph_value,
+ const Vector3 &p_morph_normal) {
+ ERR_FAIL_INDEX_MSG(p_vertex, (Vertex)p_vertices_position.size(), "FBX file is corrupted, the position of the vertex can't be retrieved.");
+
+ if (p_normals.has(p_vertex)) {
+ p_surface_tool->set_normal(p_normals[p_vertex] + p_morph_normal);
+ }
+
+ if (p_uvs_0.has(p_vertex)) {
+ //print_verbose("uv1: [" + itos(p_vertex) + "] " + p_uvs_0[p_vertex]);
+ // Inverts Y UV.
+ p_surface_tool->set_uv(Vector2(p_uvs_0[p_vertex].x, 1 - p_uvs_0[p_vertex].y));
+ }
+
+ if (p_uvs_1.has(p_vertex)) {
+ //print_verbose("uv2: [" + itos(p_vertex) + "] " + p_uvs_1[p_vertex]);
+ // Inverts Y UV.
+ p_surface_tool->set_uv2(Vector2(p_uvs_1[p_vertex].x, 1 - p_uvs_1[p_vertex].y));
+ }
+
+ if (p_colors.has(p_vertex)) {
+ p_surface_tool->set_color(p_colors[p_vertex]);
+ }
+
+ // TODO what about binormals?
+ // TODO there is other?
+
+ if (vertex_weights.has(p_vertex)) {
+ // Let's extract the weight info.
+ const VertexWeightMapping *vm = vertex_weights.getptr(p_vertex);
+ const Vector<int> &bones = vm->bones;
+
+ // the bug is that the bone idx is wrong because it is not ref'ing the skin.
+
+ if (bones.size() > RS::ARRAY_WEIGHTS_SIZE) {
+ print_error("[weight overflow detected]");
+ }
+
+ p_surface_tool->set_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->set_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);
+}
+
+void FBXMeshData::triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon_vertex, const Vector<Vertex> p_surface_vertex_map, const std::vector<Vector3> &p_vertices) const {
+ const int polygon_vertex_count = p_polygon_vertex.size();
+ if (polygon_vertex_count == 1) {
+ // point to triangle
+ st->add_index(p_polygon_vertex[0]);
+ st->add_index(p_polygon_vertex[0]);
+ st->add_index(p_polygon_vertex[0]);
+ return;
+ } else if (polygon_vertex_count == 2) {
+ // line to triangle
+ st->add_index(p_polygon_vertex[1]);
+ st->add_index(p_polygon_vertex[1]);
+ st->add_index(p_polygon_vertex[0]);
+ return;
+ } else if (polygon_vertex_count == 3) {
+ // triangle to triangle
+ st->add_index(p_polygon_vertex[0]);
+ st->add_index(p_polygon_vertex[2]);
+ st->add_index(p_polygon_vertex[1]);
+ return;
+ } else if (polygon_vertex_count == 4) {
+ // quad to triangle - this code is awesome for import times
+ // it prevents triangles being generated slowly
+ st->add_index(p_polygon_vertex[0]);
+ st->add_index(p_polygon_vertex[2]);
+ st->add_index(p_polygon_vertex[1]);
+ st->add_index(p_polygon_vertex[2]);
+ st->add_index(p_polygon_vertex[0]);
+ st->add_index(p_polygon_vertex[3]);
+ return;
+ } else {
+ // non triangulated - we must run the triangulation algorithm
+ bool is_simple_convex = false;
+ // this code is 'slow' but required it triangulates all the unsupported geometry.
+ // Doesn't allow for bigger polygons because those are unlikely be convex
+ if (polygon_vertex_count <= 6) {
+ // Start from true, check if it's false.
+ is_simple_convex = true;
+ Vector3 first_vec;
+ for (int i = 0; i < polygon_vertex_count; i += 1) {
+ const Vector3 p1 = p_vertices[p_surface_vertex_map[p_polygon_vertex[i]]];
+ const Vector3 p2 = p_vertices[p_surface_vertex_map[p_polygon_vertex[(i + 1) % polygon_vertex_count]]];
+ const Vector3 p3 = p_vertices[p_surface_vertex_map[p_polygon_vertex[(i + 2) % polygon_vertex_count]]];
+
+ const Vector3 edge1 = p1 - p2;
+ const Vector3 edge2 = p3 - p2;
+
+ const Vector3 res = edge1.normalized().cross(edge2.normalized()).normalized();
+ if (i == 0) {
+ first_vec = res;
+ } else {
+ if (first_vec.dot(res) < 0.0) {
+ // Ok we found an angle that is not the same dir of the
+ // others.
+ is_simple_convex = false;
+ break;
+ }
+ }
+ }
+ }
+
+ if (is_simple_convex) {
+ // This is a convex polygon, so just triangulate it.
+ for (int i = 0; i < (polygon_vertex_count - 2); i += 1) {
+ st->add_index(p_polygon_vertex[2 + i]);
+ st->add_index(p_polygon_vertex[1 + i]);
+ st->add_index(p_polygon_vertex[0]);
+ }
+ return;
+ }
+ }
+
+ {
+ // This is a concave polygon.
+
+ std::vector<Vector3> poly_vertices(polygon_vertex_count);
+ for (int i = 0; i < polygon_vertex_count; i += 1) {
+ poly_vertices[i] = p_vertices[p_surface_vertex_map[p_polygon_vertex[i]]];
+ }
+
+ const Vector3 poly_norm = get_poly_normal(poly_vertices);
+ if (poly_norm.length_squared() <= CMP_EPSILON) {
+ ERR_FAIL_COND_MSG(poly_norm.length_squared() <= CMP_EPSILON, "The normal of this poly was not computed. Is this FBX file corrupted.");
+ }
+
+ // Select the plan coordinate.
+ int axis_1_coord = 0;
+ int axis_2_coord = 1;
+ {
+ real_t inv = poly_norm.z;
+
+ const real_t axis_x = ABS(poly_norm.x);
+ const real_t axis_y = ABS(poly_norm.y);
+ const real_t axis_z = ABS(poly_norm.z);
+
+ if (axis_x > axis_y) {
+ if (axis_x > axis_z) {
+ // For the most part the normal point toward X.
+ axis_1_coord = 1;
+ axis_2_coord = 2;
+ inv = poly_norm.x;
+ }
+ } else if (axis_y > axis_z) {
+ // For the most part the normal point toward Y.
+ axis_1_coord = 2;
+ axis_2_coord = 0;
+ inv = poly_norm.y;
+ }
+
+ // Swap projection axes to take the negated projection vector into account
+ if (inv < 0.0f) {
+ SWAP(axis_1_coord, axis_2_coord);
+ }
+ }
+
+ TriangulatorPoly triangulator_poly;
+ triangulator_poly.Init(polygon_vertex_count);
+ std::vector<Vector2> projected_vertices(polygon_vertex_count);
+ for (int i = 0; i < polygon_vertex_count; i += 1) {
+ const Vector2 pv(poly_vertices[i][axis_1_coord], poly_vertices[i][axis_2_coord]);
+ projected_vertices[i] = pv;
+ triangulator_poly.GetPoint(i) = pv;
+ }
+ triangulator_poly.SetOrientation(TRIANGULATOR_CCW);
+
+ List<TriangulatorPoly> out_poly;
+
+ TriangulatorPartition triangulator_partition;
+ if (triangulator_partition.Triangulate_OPT(&triangulator_poly, &out_poly) == 0) { // Good result.
+ if (triangulator_partition.Triangulate_EC(&triangulator_poly, &out_poly) == 0) { // Medium result.
+ if (triangulator_partition.Triangulate_MONO(&triangulator_poly, &out_poly) == 0) { // Really poor result.
+ ERR_FAIL_MSG("The triangulation of this polygon failed, please try to triangulate your mesh or check if it has broken polygons.");
+ }
+ }
+ }
+
+ std::vector<Vector2> tris(out_poly.size());
+ for (List<TriangulatorPoly>::Element *I = out_poly.front(); I; I = I->next()) {
+ TriangulatorPoly &tp = I->get();
+
+ ERR_FAIL_COND_MSG(tp.GetNumPoints() != 3, "The triangulator retuned more points, how this is possible?");
+ // Find Index
+ for (int i = 2; i >= 0; i -= 1) {
+ const Vector2 vertex = tp.GetPoint(i);
+ bool done = false;
+ // Find Index
+ for (int y = 0; y < polygon_vertex_count; y += 1) {
+ if ((projected_vertices[y] - vertex).length_squared() <= CMP_EPSILON) {
+ // This seems the right vertex
+ st->add_index(p_polygon_vertex[y]);
+ done = true;
+ break;
+ }
+ }
+ ERR_FAIL_COND(done == false);
+ }
+ }
+ }
+}
+
+void FBXMeshData::gen_weight_info(Ref<SurfaceTool> st, Vertex vertex_id) const {
+ if (vertex_weights.empty()) {
+ return;
+ }
+
+ if (vertex_weights.has(vertex_id)) {
+ // Let's extract the weight info.
+ const VertexWeightMapping *vm = vertex_weights.getptr(vertex_id);
+ st->set_weights(vm->weights);
+ st->set_bones(vm->bones);
+ }
+}
+
+int FBXMeshData::get_vertex_from_polygon_vertex(const std::vector<int> &p_polygon_indices, int p_index) const {
+ if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) {
+ return -1;
+ }
+
+ const int vertex = p_polygon_indices[p_index];
+ if (vertex >= 0) {
+ return vertex;
+ } else {
+ // Negative numbers are the end of the face, reversing the bits is
+ // possible to obtain the positive correct vertex number.
+ return ~vertex;
+ }
+}
+
+bool FBXMeshData::is_end_of_polygon(const std::vector<int> &p_polygon_indices, int p_index) const {
+ if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) {
+ return false;
+ }
+
+ const int vertex = p_polygon_indices[p_index];
+
+ // If the index is negative this is the end of the Polygon.
+ return vertex < 0;
+}
+
+bool FBXMeshData::is_start_of_polygon(const std::vector<int> &p_polygon_indices, int p_index) const {
+ if (p_index < 0 || p_index >= (int)p_polygon_indices.size()) {
+ return false;
+ }
+
+ if (p_index == 0) {
+ return true;
+ }
+
+ // If the previous indices is negative this is the begin of a new Polygon.
+ return p_polygon_indices[p_index - 1] < 0;
+}
+
+int FBXMeshData::count_polygons(const std::vector<int> &p_polygon_indices) const {
+ // The negative numbers define the end of the polygon. Counting the amount of
+ // negatives the numbers of polygons are obtained.
+ int count = 0;
+ for (size_t i = 0; i < p_polygon_indices.size(); i += 1) {
+ if (p_polygon_indices[i] < 0) {
+ count += 1;
+ }
+ }
+ return count;
+}
+
+template <class R, class T>
+HashMap<int, R> FBXMeshData::extract_per_vertex_data(
+ int p_vertex_count,
+ const std::vector<FBXDocParser::MeshGeometry::Edge> &p_edge_map,
+ const std::vector<int> &p_mesh_indices,
+ const FBXDocParser::MeshGeometry::MappingData<T> &p_mapping_data,
+ R (*collector_function)(const Vector<VertexData<T>> *p_vertex_data, R p_fall_back),
+ R p_fall_back) const {
+ /* When index_to_direct is set
+ * index size is 184 ( contains index for the data array [values 0, 96] )
+ * data size is 96 (contains uv coordinates)
+ * this means index is simple data reduction basically
+ */
+ ////
+ if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_mapping_data.index.size() == 0) {
+ print_verbose("debug count: index size: " + itos(p_mapping_data.index.size()) + ", data size: " + itos(p_mapping_data.data.size()));
+ print_verbose("vertex indices count: " + itos(p_mesh_indices.size()));
+ print_verbose("Edge map size: " + itos(p_edge_map.size()));
+ }
+
+ ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_mapping_data.index.size() == 0, (HashMap<int, R>()), "FBX importer needs to map correctly to this field, please specify the override index name to fix this problem!");
+ ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index && p_mapping_data.index.size() == 0, (HashMap<int, R>()), "The FBX seems corrupted");
+
+ // Aggregate vertex data.
+ HashMap<Vertex, Vector<VertexData<T>>> aggregate_vertex_data;
+
+ switch (p_mapping_data.map_type) {
+ case FBXDocParser::MeshGeometry::MapType::none: {
+ // No data nothing to do.
+ return (HashMap<int, R>());
+ }
+ case FBXDocParser::MeshGeometry::MapType::vertex: {
+ ERR_FAIL_COND_V_MSG(p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct, (HashMap<int, R>()), "We will support in future");
+
+ if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) {
+ // The data is mapped per vertex directly.
+ ERR_FAIL_COND_V_MSG((int)p_mapping_data.data.size() != p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR01");
+ for (size_t vertex_index = 0; vertex_index < p_mapping_data.data.size(); vertex_index += 1) {
+ aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[vertex_index] });
+ }
+ } else {
+ // The data is mapped per vertex using a reference.
+ // The indices array, contains a *reference_id for each vertex.
+ // * Note that the reference_id is the id of data into the data array.
+ //
+ // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html
+ ERR_FAIL_COND_V_MSG((int)p_mapping_data.index.size() != p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR02");
+ for (size_t vertex_index = 0; vertex_index < p_mapping_data.index.size(); vertex_index += 1) {
+ ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[vertex_index], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR03.");
+ aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[p_mapping_data.index[vertex_index]] });
+ }
+ }
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::polygon_vertex: {
+ if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct) {
+ // The data is mapped using each index from the indexes array then direct to the data (data reduction algorithm)
+ ERR_FAIL_COND_V_MSG((int)p_mesh_indices.size() != (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR04");
+ int polygon_id = -1;
+ for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.index.size(); polygon_vertex_index += 1) {
+ if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) {
+ polygon_id += 1;
+ }
+ const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index);
+ ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR05");
+ ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR06");
+ const int index_to_direct = p_mapping_data.index[polygon_vertex_index];
+ T value = p_mapping_data.data[index_to_direct];
+ aggregate_vertex_data[vertex_index].push_back({ polygon_id, value });
+ }
+ } else if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) {
+ // The data are mapped per polygon vertex directly.
+ ERR_FAIL_COND_V_MSG((int)p_mesh_indices.size() != (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR04");
+ int polygon_id = -1;
+ for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.data.size(); polygon_vertex_index += 1) {
+ if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) {
+ polygon_id += 1;
+ }
+ const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index);
+ ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR05");
+ ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR06");
+
+ aggregate_vertex_data[vertex_index].push_back({ polygon_id, p_mapping_data.data[polygon_vertex_index] });
+ }
+ } else {
+ // The data is mapped per polygon_vertex using a reference.
+ // The indices array, contains a *reference_id for each polygon_vertex.
+ // * Note that the reference_id is the id of data into the data array.
+ //
+ // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html
+ ERR_FAIL_COND_V_MSG(p_mesh_indices.size() != p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR7");
+ int polygon_id = -1;
+ for (size_t polygon_vertex_index = 0; polygon_vertex_index < p_mapping_data.index.size(); polygon_vertex_index += 1) {
+ if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) {
+ polygon_id += 1;
+ }
+ const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index);
+ ERR_FAIL_COND_V_MSG(vertex_index < 0, (HashMap<int, R>()), "FBX file corrupted: #ERR8");
+ ERR_FAIL_COND_V_MSG(vertex_index >= p_vertex_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR9.");
+ ERR_FAIL_COND_V_MSG(p_mapping_data.index[polygon_vertex_index] < 0, (HashMap<int, R>()), "FBX file seems corrupted: #ERR10.");
+ ERR_FAIL_COND_V_MSG(p_mapping_data.index[polygon_vertex_index] >= (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR11.");
+ aggregate_vertex_data[vertex_index].push_back({ polygon_id, p_mapping_data.data[p_mapping_data.index[polygon_vertex_index]] });
+ }
+ }
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::polygon: {
+ if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) {
+ // The data are mapped per polygon directly.
+ const int polygon_count = count_polygons(p_mesh_indices);
+ ERR_FAIL_COND_V_MSG(polygon_count != (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR12");
+
+ // Advance each polygon vertex, each new polygon advance the polygon index.
+ int polygon_index = -1;
+ for (size_t polygon_vertex_index = 0;
+ polygon_vertex_index < p_mesh_indices.size();
+ polygon_vertex_index += 1) {
+ if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) {
+ polygon_index += 1;
+ ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR13");
+ }
+
+ const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index);
+ ERR_FAIL_INDEX_V_MSG(vertex_index, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR14");
+
+ aggregate_vertex_data[vertex_index].push_back({ polygon_index, p_mapping_data.data[polygon_index] });
+ }
+ ERR_FAIL_COND_V_MSG((polygon_index + 1) != polygon_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR16. Not all Polygons are present in the file.");
+ } else {
+ // The data is mapped per polygon using a reference.
+ // The indices array, contains a *reference_id for each polygon.
+ // * Note that the reference_id is the id of data into the data array.
+ //
+ // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html
+ const int polygon_count = count_polygons(p_mesh_indices);
+ ERR_FAIL_COND_V_MSG(polygon_count != (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR17");
+
+ // Advance each polygon vertex, each new polygon advance the polygon index.
+ int polygon_index = -1;
+ for (size_t polygon_vertex_index = 0;
+ polygon_vertex_index < p_mesh_indices.size();
+ polygon_vertex_index += 1) {
+ if (is_start_of_polygon(p_mesh_indices, polygon_vertex_index)) {
+ polygon_index += 1;
+ ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR18");
+ ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[polygon_index], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR19");
+ }
+
+ const int vertex_index = get_vertex_from_polygon_vertex(p_mesh_indices, polygon_vertex_index);
+ ERR_FAIL_INDEX_V_MSG(vertex_index, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR20");
+
+ aggregate_vertex_data[vertex_index].push_back({ polygon_index, p_mapping_data.data[p_mapping_data.index[polygon_index]] });
+ }
+ ERR_FAIL_COND_V_MSG((polygon_index + 1) != polygon_count, (HashMap<int, R>()), "FBX file seems corrupted: #ERR22. Not all Polygons are present in the file.");
+ }
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::edge: {
+ if (p_mapping_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) {
+ // The data are mapped per edge directly.
+ ERR_FAIL_COND_V_MSG(p_edge_map.size() != p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR23");
+ for (size_t edge_index = 0; edge_index < p_mapping_data.data.size(); edge_index += 1) {
+ const FBXDocParser::MeshGeometry::Edge edge = FBXDocParser::MeshGeometry::get_edge(p_edge_map, edge_index);
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_0, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR24");
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_1, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR25");
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_0, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR26");
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_1, (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR27");
+ aggregate_vertex_data[edge.vertex_0].push_back({ -1, p_mapping_data.data[edge_index] });
+ aggregate_vertex_data[edge.vertex_1].push_back({ -1, p_mapping_data.data[edge_index] });
+ }
+ } else {
+ // The data is mapped per edge using a reference.
+ // The indices array, contains a *reference_id for each polygon.
+ // * Note that the reference_id is the id of data into the data array.
+ //
+ // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html
+ ERR_FAIL_COND_V_MSG(p_edge_map.size() != p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file seems corrupted: #ERR28");
+ for (size_t edge_index = 0; edge_index < p_mapping_data.data.size(); edge_index += 1) {
+ const FBXDocParser::MeshGeometry::Edge edge = FBXDocParser::MeshGeometry::get_edge(p_edge_map, edge_index);
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_0, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR29");
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_1, p_vertex_count, (HashMap<int, R>()), "FBX file corrupted: #ERR30");
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_0, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR31");
+ ERR_FAIL_INDEX_V_MSG(edge.vertex_1, (int)p_mapping_data.index.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR32");
+ ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[edge.vertex_0], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR33");
+ ERR_FAIL_INDEX_V_MSG(p_mapping_data.index[edge.vertex_1], (int)p_mapping_data.data.size(), (HashMap<int, R>()), "FBX file corrupted: #ERR34");
+ aggregate_vertex_data[edge.vertex_0].push_back({ -1, p_mapping_data.data[p_mapping_data.index[edge_index]] });
+ aggregate_vertex_data[edge.vertex_1].push_back({ -1, p_mapping_data.data[p_mapping_data.index[edge_index]] });
+ }
+ }
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::all_the_same: {
+ // No matter the mode, no matter the data size; The first always win
+ // and is set to all the vertices.
+ ERR_FAIL_COND_V_MSG(p_mapping_data.data.size() <= 0, (HashMap<int, R>()), "FBX file seems corrupted: #ERR35");
+ if (p_mapping_data.data.size() > 0) {
+ for (int vertex_index = 0; vertex_index < p_vertex_count; vertex_index += 1) {
+ aggregate_vertex_data[vertex_index].push_back({ -1, p_mapping_data.data[0] });
+ }
+ }
+ } break;
+ }
+
+ if (aggregate_vertex_data.size() == 0) {
+ return (HashMap<int, R>());
+ }
+
+ // A map is used because turns out that the some FBX file are not well organized
+ // with vertices well compacted. Using a map allows avoid those issues.
+ HashMap<Vertex, R> result;
+
+ // Aggregate the collected data.
+ for (const Vertex *index = aggregate_vertex_data.next(nullptr); index != nullptr; index = aggregate_vertex_data.next(index)) {
+ Vector<VertexData<T>> *aggregated_vertex = aggregate_vertex_data.getptr(*index);
+ // This can't be null because we are just iterating.
+ CRASH_COND(aggregated_vertex == nullptr);
+
+ ERR_FAIL_INDEX_V_MSG(0, aggregated_vertex->size(), (HashMap<int, R>()), "The FBX file is corrupted, No valid data for this vertex index.");
+ result[*index] = collector_function(aggregated_vertex, p_fall_back);
+ }
+
+ // Sanitize the data now, if the file is broken we can try import it anyway.
+ bool problem_found = false;
+ for (size_t i = 0; i < p_mesh_indices.size(); i += 1) {
+ const Vertex vertex = get_vertex_from_polygon_vertex(p_mesh_indices, i);
+ if (result.has(vertex) == false) {
+ result[vertex] = p_fall_back;
+ problem_found = true;
+ }
+ }
+ if (problem_found) {
+ WARN_PRINT("Some data is missing, this FBX file may be corrupted: #WARN0.");
+ }
+
+ return result;
+}
+
+template <class T>
+HashMap<int, T> FBXMeshData::extract_per_polygon(
+ int p_vertex_count,
+ const std::vector<int> &p_polygon_indices,
+ const FBXDocParser::MeshGeometry::MappingData<T> &p_fbx_data,
+ T p_fallback_value) const {
+ ERR_FAIL_COND_V_MSG(p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct && p_fbx_data.data.size() == 0, (HashMap<int, T>()), "invalid index to direct array");
+ ERR_FAIL_COND_V_MSG(p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index && p_fbx_data.index.size() == 0, (HashMap<int, T>()), "The FBX seems corrupted");
+
+ const int polygon_count = count_polygons(p_polygon_indices);
+
+ // Aggregate vertex data.
+ HashMap<int, Vector<T>> aggregate_polygon_data;
+
+ switch (p_fbx_data.map_type) {
+ case FBXDocParser::MeshGeometry::MapType::none: {
+ // No data nothing to do.
+ return (HashMap<int, T>());
+ }
+ case FBXDocParser::MeshGeometry::MapType::vertex: {
+ ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per vertex. This should not happen.");
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::polygon_vertex: {
+ ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per polygon vertex. This should not happen.");
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::polygon: {
+ if (p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::index_to_direct) {
+ // The data is stored efficiently index_to_direct allows less data in the FBX file.
+ for (int polygon_index = 0;
+ polygon_index < polygon_count;
+ polygon_index += 1) {
+ if (p_fbx_data.index.size() == 0) {
+ ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR62");
+ aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[polygon_index]);
+ } else {
+ ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR62");
+
+ const int index_to_direct = p_fbx_data.index[polygon_index];
+ T value = p_fbx_data.data[index_to_direct];
+ aggregate_polygon_data[polygon_index].push_back(value);
+ }
+ }
+ } else if (p_fbx_data.ref_type == FBXDocParser::MeshGeometry::ReferenceType::direct) {
+ // The data are mapped per polygon directly.
+ ERR_FAIL_COND_V_MSG(polygon_count != (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR51");
+
+ // Advance each polygon vertex, each new polygon advance the polygon index.
+ for (int polygon_index = 0;
+ polygon_index < polygon_count;
+ polygon_index += 1) {
+ ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR52");
+ aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[polygon_index]);
+ }
+ } else {
+ // The data is mapped per polygon using a reference.
+ // The indices array, contains a *reference_id for each polygon.
+ // * Note that the reference_id is the id of data into the data array.
+ //
+ // https://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_layer_element_html
+ ERR_FAIL_COND_V_MSG(polygon_count != (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file seems corrupted: #ERR52");
+
+ // Advance each polygon vertex, each new polygon advance the polygon index.
+ for (int polygon_index = 0;
+ polygon_index < polygon_count;
+ polygon_index += 1) {
+ ERR_FAIL_INDEX_V_MSG(polygon_index, (int)p_fbx_data.index.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR53");
+ ERR_FAIL_INDEX_V_MSG(p_fbx_data.index[polygon_index], (int)p_fbx_data.data.size(), (HashMap<int, T>()), "FBX file is corrupted: #ERR54");
+ aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[p_fbx_data.index[polygon_index]]);
+ }
+ }
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::edge: {
+ ERR_FAIL_V_MSG((HashMap<int, T>()), "This data can't be extracted and organized per polygon, since into the FBX is mapped per edge. This should not happen.");
+ } break;
+ case FBXDocParser::MeshGeometry::MapType::all_the_same: {
+ // No matter the mode, no matter the data size; The first always win
+ // and is set to all the vertices.
+ ERR_FAIL_COND_V_MSG(p_fbx_data.data.size() <= 0, (HashMap<int, T>()), "FBX file seems corrupted: #ERR55");
+ if (p_fbx_data.data.size() > 0) {
+ for (int polygon_index = 0; polygon_index < polygon_count; polygon_index += 1) {
+ aggregate_polygon_data[polygon_index].push_back(p_fbx_data.data[0]);
+ }
+ }
+ } break;
+ }
+
+ if (aggregate_polygon_data.size() == 0) {
+ return (HashMap<int, T>());
+ }
+
+ // A map is used because turns out that the some FBX file are not well organized
+ // with vertices well compacted. Using a map allows avoid those issues.
+ HashMap<int, T> polygons;
+
+ // Take the first value for each vertex.
+ for (const Vertex *index = aggregate_polygon_data.next(nullptr); index != nullptr; index = aggregate_polygon_data.next(index)) {
+ Vector<T> *aggregated_polygon = aggregate_polygon_data.getptr(*index);
+ // This can't be null because we are just iterating.
+ CRASH_COND(aggregated_polygon == nullptr);
+
+ ERR_FAIL_INDEX_V_MSG(0, (int)aggregated_polygon->size(), (HashMap<int, T>()), "The FBX file is corrupted, No valid data for this polygon index.");
+
+ // Validate the final value.
+ polygons[*index] = (*aggregated_polygon)[0];
+ }
+
+ // Sanitize the data now, if the file is broken we can try import it anyway.
+ bool problem_found = false;
+ for (int polygon_i = 0; polygon_i < polygon_count; polygon_i += 1) {
+ if (polygons.has(polygon_i) == false) {
+ polygons[polygon_i] = p_fallback_value;
+ problem_found = true;
+ }
+ }
+ if (problem_found) {
+ WARN_PRINT("Some data is missing, this FBX file may be corrupted: #WARN1.");
+ }
+
+ return polygons;
+}
+
+void FBXMeshData::extract_morphs(const FBXDocParser::MeshGeometry *mesh_geometry, HashMap<String, MorphVertexData> &r_data) {
+ r_data.clear();
+
+ const int vertex_count = mesh_geometry->get_vertices().size();
+
+ for (const FBXDocParser::BlendShape *blend_shape : mesh_geometry->get_blend_shapes()) {
+ for (const FBXDocParser::BlendShapeChannel *blend_shape_channel : blend_shape->BlendShapeChannels()) {
+ const std::vector<const FBXDocParser::ShapeGeometry *> &shape_geometries = blend_shape_channel->GetShapeGeometries();
+ for (const FBXDocParser::ShapeGeometry *shape_geometry : shape_geometries) {
+ String morph_name = ImportUtils::FBXAnimMeshName(shape_geometry->Name()).c_str();
+ if (morph_name.empty()) {
+ morph_name = "morph";
+ }
+
+ // TODO we have only these??
+ const std::vector<unsigned int> &morphs_vertex_indices = shape_geometry->GetIndices();
+ const std::vector<Vector3> &morphs_vertices = shape_geometry->GetVertices();
+ const std::vector<Vector3> &morphs_normals = shape_geometry->GetNormals();
+
+ ERR_FAIL_COND_MSG((int)morphs_vertex_indices.size() > vertex_count, "The FBX file is corrupted: #ERR103");
+ ERR_FAIL_COND_MSG(morphs_vertex_indices.size() != morphs_vertices.size(), "The FBX file is corrupted: #ERR104");
+ ERR_FAIL_COND_MSG((int)morphs_vertices.size() > vertex_count, "The FBX file is corrupted: #ERR105");
+ ERR_FAIL_COND_MSG(morphs_normals.size() != 0 && morphs_normals.size() != morphs_vertices.size(), "The FBX file is corrupted: #ERR106");
+
+ if (r_data.has(morph_name) == false) {
+ // This morph doesn't exist yet.
+ // Create it.
+ MorphVertexData md;
+ md.vertices.resize(vertex_count);
+ md.normals.resize(vertex_count);
+ r_data.set(morph_name, md);
+ }
+
+ MorphVertexData *data = r_data.getptr(morph_name);
+ Vector3 *data_vertices_ptr = data->vertices.ptrw();
+ Vector3 *data_normals_ptr = data->normals.ptrw();
+
+ for (int i = 0; i < (int)morphs_vertex_indices.size(); i += 1) {
+ const Vertex vertex = morphs_vertex_indices[i];
+
+ ERR_FAIL_INDEX_MSG(vertex, vertex_count, "The blend shapes of this FBX file are corrupted. It has a not valid vertex.");
+
+ data_vertices_ptr[vertex] = morphs_vertices[i];
+
+ if (morphs_normals.size() != 0) {
+ data_normals_ptr[vertex] = morphs_normals[i];
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/modules/fbx/data/fbx_mesh_data.h b/modules/fbx/data/fbx_mesh_data.h
new file mode 100644
index 0000000000..dd864443ce
--- /dev/null
+++ b/modules/fbx/data/fbx_mesh_data.h
@@ -0,0 +1,183 @@
+/*************************************************************************/
+/* fbx_mesh_data.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_MESH_DATA_H
+#define FBX_MESH_DATA_H
+
+#include "core/templates/hash_map.h"
+#include "editor/import/resource_importer_scene.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/resources/surface_tool.h"
+
+#include "fbx_bone.h"
+#include "fbx_parser/FBXMeshGeometry.h"
+#include "import_state.h"
+#include "tools/import_utils.h"
+
+struct FBXNode;
+struct FBXMeshData;
+struct FBXBone;
+struct ImportState;
+
+struct VertexWeightMapping {
+ Vector<real_t> weights;
+ Vector<int> bones;
+ // This extra vector is used because the bone id is computed in a second step.
+ // TODO Get rid of this extra step is a good idea.
+ Vector<Ref<FBXBone>> bones_ref;
+};
+
+template <class T>
+struct VertexData {
+ int polygon_index;
+ T data;
+};
+
+// Caches mesh information and instantiates meshes for you using helper functions.
+struct FBXMeshData : Reference {
+ struct MorphVertexData {
+ // TODO we have only these??
+ /// Each element is a vertex. Not supposed to be void.
+ Vector<Vector3> vertices;
+ /// Each element is a vertex. Not supposed to be void.
+ Vector<Vector3> normals;
+ };
+
+ // FIXME: remove this is a hack for testing only
+ mutable const FBXDocParser::MeshGeometry *mesh_geometry = nullptr;
+
+ Ref<FBXNode> mesh_node = nullptr;
+ /// vertex id, Weight Info
+ /// later: perf we can use array here
+ HashMap<int, VertexWeightMapping> vertex_weights;
+
+ // translate fbx mesh data from document context to FBX Mesh Geometry Context
+ bool valid_weight_indexes = false;
+
+ EditorSceneImporterMeshNode *create_fbx_mesh(const ImportState &state, const FBXDocParser::MeshGeometry *p_mesh_geometry, const FBXDocParser::Model *model, bool use_compression);
+
+ void gen_weight_info(Ref<SurfaceTool> st, int vertex_id) const;
+
+ /* mesh maximum weight count */
+ bool valid_weight_count = false;
+ int max_weight_count = 0;
+ uint64_t armature_id = 0;
+ bool valid_armature_id = false;
+ EditorSceneImporterMeshNode *godot_mesh_instance = nullptr;
+
+private:
+ 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
+ /// combined, the UV may need its own triangle because sometimes they have
+ /// really different UV for the same vertex but different polygon.
+ /// This function make sure to add another vertex for those UVS.
+ void reorganize_vertices(
+ std::vector<int> &r_polygon_indices,
+ std::vector<Vector3> &r_vertices,
+ HashMap<int, Vector3> &r_normals,
+ HashMap<int, Vector2> &r_uv_1,
+ HashMap<int, Vector2> &r_uv_2,
+ HashMap<int, Color> &r_color,
+ HashMap<String, MorphVertexData> &r_morphs,
+ HashMap<int, HashMap<int, Vector3>> &r_normals_raw,
+ HashMap<int, HashMap<int, Color>> &r_colors_raw,
+ HashMap<int, HashMap<int, Vector2>> &r_uv_1_raw,
+ HashMap<int, HashMap<int, Vector2>> &r_uv_2_raw);
+
+ void add_vertex(
+ const ImportState &state,
+ Ref<SurfaceTool> p_surface_tool,
+ real_t p_scale,
+ int p_vertex,
+ const std::vector<Vector3> &p_vertices_position,
+ const HashMap<int, Vector3> &p_normals,
+ const HashMap<int, Vector2> &p_uvs_0,
+ const HashMap<int, Vector2> &p_uvs_1,
+ const HashMap<int, Color> &p_colors,
+ const Vector3 &p_morph_value = Vector3(),
+ const Vector3 &p_morph_normal = Vector3());
+
+ void triangulate_polygon(Ref<SurfaceTool> st, Vector<int> p_polygon_vertex, Vector<int> p_surface_vertex_map, const std::vector<Vector3> &p_vertices) const;
+
+ /// This function is responsible to convert the FBX polygon vertex to
+ /// vertex index.
+ /// The polygon vertices are stored in an array with some negative
+ /// values. The negative values define the last face index.
+ /// For example the following `face_array` contains two faces, the former
+ /// with 3 vertices and the latter with a line:
+ /// [0,2,-2,3,-5]
+ /// Parsed as:
+ /// [0, 2, 1, 3, 4]
+ /// The negative values are computed using this formula: `(-value) - 1`
+ ///
+ /// Returns the vertex index from the poligon vertex.
+ /// Returns -1 if `p_index` is invalid.
+ int get_vertex_from_polygon_vertex(const std::vector<int> &p_face_indices, int p_index) const;
+
+ /// Returns true if this polygon_vertex_index is the end of a new polygon.
+ bool is_end_of_polygon(const std::vector<int> &p_face_indices, int p_index) const;
+
+ /// Returns true if this polygon_vertex_index is the begin of a new polygon.
+ bool is_start_of_polygon(const std::vector<int> &p_face_indices, int p_index) const;
+
+ /// Returns the number of polygons.
+ int count_polygons(const std::vector<int> &p_face_indices) const;
+
+ /// 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 <class R, class T>
+ HashMap<int, R> extract_per_vertex_data(
+ int p_vertex_count,
+ const std::vector<FBXDocParser::MeshGeometry::Edge> &p_edges,
+ const std::vector<int> &p_mesh_indices,
+ const FBXDocParser::MeshGeometry::MappingData<T> &p_mapping_data,
+ R (*collector_function)(const Vector<VertexData<T>> *p_vertex_data, R p_fall_back),
+ R p_fall_back) const;
+
+ /// Used to extract data from the `MappingData` organized per polygon.
+ /// Useful to extract the material
+ /// If the function fails somehow, it returns an hollow vector and print an error.
+ template <class T>
+ HashMap<int, T> extract_per_polygon(
+ int p_vertex_count,
+ const std::vector<int> &p_face_indices,
+ const FBXDocParser::MeshGeometry::MappingData<T> &p_fbx_data,
+ T p_fallback_value) const;
+
+ /// Extracts the morph data and organizes it per vertices.
+ /// The returned `MorphVertexData` arrays are never something different
+ /// then the `vertex_count`.
+ void extract_morphs(const FBXDocParser::MeshGeometry *mesh_geometry, HashMap<String, MorphVertexData> &r_data);
+};
+
+#endif // FBX_MESH_DATA_H
diff --git a/modules/fbx/data/fbx_node.h b/modules/fbx/data/fbx_node.h
new file mode 100644
index 0000000000..d8efcaa982
--- /dev/null
+++ b/modules/fbx/data/fbx_node.h
@@ -0,0 +1,63 @@
+/*************************************************************************/
+/* fbx_node.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_NODE_H
+#define FBX_NODE_H
+
+#include "fbx_skeleton.h"
+#include "model_abstraction.h"
+#include "pivot_transform.h"
+
+#include "fbx_parser/FBXDocument.h"
+
+class Node3D;
+struct PivotTransform;
+
+struct FBXNode : Reference, ModelAbstraction {
+ uint64_t current_node_id = 0;
+ String node_name = String();
+ Node3D *godot_node = nullptr;
+
+ // used to parent the skeleton once the tree is built.
+ Ref<FBXSkeleton> skeleton_node = Ref<FBXSkeleton>();
+
+ void set_parent(Ref<FBXNode> p_parent) {
+ fbx_parent = p_parent;
+ }
+
+ void set_pivot_transform(Ref<PivotTransform> p_pivot_transform) {
+ pivot_transform = p_pivot_transform;
+ }
+
+ Ref<PivotTransform> pivot_transform = Ref<PivotTransform>(); // local and global xform data
+ Ref<FBXNode> fbx_parent = Ref<FBXNode>(); // parent node
+};
+
+#endif // FBX_NODE_H
diff --git a/modules/fbx/data/fbx_skeleton.cpp b/modules/fbx/data/fbx_skeleton.cpp
new file mode 100644
index 0000000000..93aeffc132
--- /dev/null
+++ b/modules/fbx/data/fbx_skeleton.cpp
@@ -0,0 +1,123 @@
+/*************************************************************************/
+/* fbx_skeleton.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 "fbx_skeleton.h"
+
+#include "import_state.h"
+
+#include "tools/import_utils.h"
+
+void FBXSkeleton::init_skeleton(const ImportState &state) {
+ int skeleton_bone_count = skeleton_bones.size();
+
+ if (skeleton == nullptr && skeleton_bone_count > 0) {
+ skeleton = memnew(Skeleton3D);
+
+ if (fbx_node.is_valid()) {
+ // cache skeleton attachment for later during node creation
+ // can't be done until after node hierarchy is built
+ if (fbx_node->godot_node != state.root) {
+ fbx_node->skeleton_node = Ref<FBXSkeleton>(this);
+ print_verbose("cached armature skeleton attachment for node " + fbx_node->node_name);
+ } else {
+ // root node must never be a skeleton to prevent cyclic skeletons from being allowed (skeleton in a skeleton)
+ fbx_node->godot_node->add_child(skeleton);
+ skeleton->set_owner(state.root_owner);
+ skeleton->set_name("Skeleton3D");
+ print_verbose("created armature skeleton for root");
+ }
+ } else {
+ memfree(skeleton);
+ skeleton = nullptr;
+ print_error("[doc] skeleton has no valid node to parent nodes to - erasing");
+ skeleton_bones.clear();
+ return;
+ }
+ }
+
+ // Make the bone name uniques.
+ for (int x = 0; x < skeleton_bone_count; x++) {
+ Ref<FBXBone> bone = skeleton_bones[x];
+ if (bone.is_valid()) {
+ // Make sure the bone name is unique.
+ const String bone_name = bone->bone_name;
+ int same_name_count = 0;
+ for (int y = x; y < skeleton_bone_count; y++) {
+ Ref<FBXBone> other_bone = skeleton_bones[y];
+ if (other_bone.is_valid()) {
+ if (other_bone->bone_name == bone_name) {
+ same_name_count += 1;
+ other_bone->bone_name += "_" + itos(same_name_count);
+ }
+ }
+ }
+ }
+ }
+
+ Map<int, Ref<FBXBone>> bone_map;
+ // implement fbx cluster skin logic here this is where it goes
+ int bone_count = 0;
+ for (int x = 0; x < skeleton_bone_count; x++) {
+ Ref<FBXBone> bone = skeleton_bones[x];
+ if (bone.is_valid()) {
+ skeleton->add_bone(bone->bone_name);
+ bone->godot_bone_id = bone_count;
+ bone->fbx_skeleton = Ref<FBXSkeleton>(this);
+ bone_map.insert(bone_count, bone);
+ print_verbose("added bone " + itos(bone->bone_id) + " " + bone->bone_name);
+ bone_count++;
+ }
+ }
+
+ ERR_FAIL_COND_MSG(skeleton->get_bone_count() != bone_count, "Not all bones got added, is the file corrupted?");
+
+ for (Map<int, Ref<FBXBone>>::Element *bone_element = bone_map.front(); bone_element; bone_element = bone_element->next()) {
+ const Ref<FBXBone> 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->node->pivot_transform->LocalTransform, state.scale));
+
+ // lookup parent ID
+ if (bone->valid_parent && state.fbx_bone_map.has(bone->parent_bone_id)) {
+ Ref<FBXBone> parent_bone = state.fbx_bone_map[bone->parent_bone_id];
+ int bone_id = skeleton->find_bone(parent_bone->bone_name);
+ if (bone_id != -1) {
+ skeleton->set_bone_parent(bone_index, bone_id);
+ } else {
+ print_error("invalid bone parent: " + parent_bone->bone_name);
+ }
+ } else {
+ if (bone->godot_bone_id != -1) {
+ skeleton->set_bone_parent(bone_index, -1); // no parent for this bone
+ }
+ }
+ }
+}
diff --git a/modules/fbx/data/fbx_skeleton.h b/modules/fbx/data/fbx_skeleton.h
new file mode 100644
index 0000000000..a2e330114c
--- /dev/null
+++ b/modules/fbx/data/fbx_skeleton.h
@@ -0,0 +1,53 @@
+/*************************************************************************/
+/* fbx_skeleton.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_SKELETON_H
+#define FBX_SKELETON_H
+
+#include "fbx_bone.h"
+#include "fbx_node.h"
+#include "model_abstraction.h"
+
+#include "core/object/reference.h"
+#include "scene/3d/skeleton_3d.h"
+
+struct FBXNode;
+struct ImportState;
+struct FBXBone;
+
+struct FBXSkeleton : Reference {
+ Ref<FBXNode> fbx_node = Ref<FBXNode>();
+ Vector<Ref<FBXBone>> skeleton_bones = Vector<Ref<FBXBone>>();
+ Skeleton3D *skeleton = nullptr;
+
+ void init_skeleton(const ImportState &state);
+};
+
+#endif // FBX_SKELETON_H
diff --git a/modules/fbx/data/import_state.h b/modules/fbx/data/import_state.h
new file mode 100644
index 0000000000..34021ca7b1
--- /dev/null
+++ b/modules/fbx/data/import_state.h
@@ -0,0 +1,112 @@
+/*************************************************************************/
+/* import_state.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 IMPORT_STATE_H
+#define IMPORT_STATE_H
+
+#include "fbx_mesh_data.h"
+#include "tools/import_utils.h"
+#include "tools/validation_tools.h"
+
+#include "pivot_transform.h"
+
+#include "core/core_bind.h"
+#include "core/io/resource_importer.h"
+#include "core/templates/vector.h"
+#include "editor/import/resource_importer_scene.h"
+#include "editor/project_settings_editor.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/3d/node_3d.h"
+#include "scene/3d/skeleton_3d.h"
+#include "scene/animation/animation_player.h"
+#include "scene/resources/animation.h"
+#include "scene/resources/surface_tool.h"
+
+#include "modules/fbx/fbx_parser/FBXDocument.h"
+#include "modules/fbx/fbx_parser/FBXImportSettings.h"
+#include "modules/fbx/fbx_parser/FBXMeshGeometry.h"
+#include "modules/fbx/fbx_parser/FBXParser.h"
+#include "modules/fbx/fbx_parser/FBXTokenizer.h"
+#include "modules/fbx/fbx_parser/FBXUtil.h"
+
+struct FBXBone;
+struct FBXMeshData;
+struct FBXNode;
+struct FBXSkeleton;
+
+struct ImportState {
+ bool enable_material_import = true;
+ bool enable_animation_import = true;
+
+ Map<StringName, Ref<Texture>> cached_image_searches;
+ Map<uint64_t, Ref<Material>> cached_materials;
+
+ String path = String();
+ Node3D *root_owner = nullptr;
+ Node3D *root = nullptr;
+ real_t scale = 0.01;
+ Ref<FBXNode> fbx_root_node = Ref<FBXNode>();
+ // skeleton map - merged automatically when they are on the same x node in the tree so we can merge them automatically.
+ Map<uint64_t, Ref<FBXSkeleton>> skeleton_map = Map<uint64_t, Ref<FBXSkeleton>>();
+
+ // nodes on the same level get merged automatically.
+ //Map<uint64_t, Skeleton3D *> armature_map;
+ AnimationPlayer *animation_player = nullptr;
+
+ // Generation 4 - Raw document accessing for bone/skin/joint/kLocators
+ // joints are not necessarily bones but must be merged into the skeleton
+ // (bone id), bone
+ Map<uint64_t, Ref<FBXBone>> fbx_bone_map = Map<uint64_t, Ref<FBXBone>>(); // this is the bone name and setup information required for joints
+ // this will never contain joints only bones attached to a mesh.
+
+ // Generation 4 - Raw document for creating the nodes transforms in the scene
+ // this is a list of the nodes in the scene
+ // (id, node)
+ List<Ref<FBXNode>> fbx_node_list = List<Ref<FBXNode>>();
+
+ // All nodes which have been created in the scene
+ // this will not contain the root node of the scene
+ Map<uint64_t, Ref<FBXNode>> fbx_target_map = Map<uint64_t, Ref<FBXNode>>();
+
+ // mesh nodes which are created in node / mesh step - used for populating skin poses in MeshSkins
+ Map<uint64_t, Ref<FBXNode>> MeshNodes = Map<uint64_t, Ref<FBXNode>>();
+ // mesh skin map
+ Map<uint64_t, Ref<Skin>> MeshSkins = Map<uint64_t, Ref<Skin>>();
+
+ // this is the container for the mesh weight information and eventually
+ // any mesh data
+ // but not the skin, just stuff important for rendering
+ // skin is applied to mesh instance so not really required to be in here yet.
+ // maybe later
+ // fbx mesh id, FBXMeshData
+ Map<uint64_t, Ref<FBXMeshData>> renderer_mesh_data = Map<uint64_t, Ref<FBXMeshData>>();
+};
+
+#endif // IMPORT_STATE_H
diff --git a/modules/fbx/data/model_abstraction.h b/modules/fbx/data/model_abstraction.h
new file mode 100644
index 0000000000..b21ab0badb
--- /dev/null
+++ b/modules/fbx/data/model_abstraction.h
@@ -0,0 +1,52 @@
+/*************************************************************************/
+/* model_abstraction.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 MODEL_ABSTRACTION_H
+#define MODEL_ABSTRACTION_H
+
+#include "modules/fbx/fbx_parser/FBXDocument.h"
+
+struct ModelAbstraction {
+ mutable const FBXDocParser::Model *fbx_model = nullptr;
+
+ void set_model(const FBXDocParser::Model *p_model) {
+ fbx_model = p_model;
+ }
+
+ bool has_model() const {
+ return fbx_model != nullptr;
+ }
+
+ const FBXDocParser::Model *get_model() const {
+ return fbx_model;
+ }
+};
+
+#endif // MODEL_ABSTRACTION_H
diff --git a/modules/fbx/data/pivot_transform.cpp b/modules/fbx/data/pivot_transform.cpp
new file mode 100644
index 0000000000..c12e94097f
--- /dev/null
+++ b/modules/fbx/data/pivot_transform.cpp
@@ -0,0 +1,294 @@
+/*************************************************************************/
+/* pivot_transform.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 "pivot_transform.h"
+
+#include "tools/import_utils.h"
+
+void PivotTransform::ReadTransformChain() {
+ const FBXDocParser::PropertyTable *props = fbx_model->Props();
+ const FBXDocParser::Model::RotOrder &rot = fbx_model->RotationOrder();
+ const FBXDocParser::TransformInheritance &inheritType = fbx_model->InheritType();
+ inherit_type = inheritType; // copy the inherit type we need it in the second step.
+ print_verbose("Model: " + String(fbx_model->Name().c_str()) + " Has inherit type: " + itos(fbx_model->InheritType()));
+ bool ok = false;
+ raw_pre_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "PreRotation", ok));
+ if (ok) {
+ pre_rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(raw_pre_rotation));
+ print_verbose("valid pre_rotation: " + raw_pre_rotation + " euler conversion: " + (pre_rotation.get_euler() * (180 / Math_PI)));
+ }
+ raw_post_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "PostRotation", ok));
+ if (ok) {
+ post_rotation = ImportUtils::EulerToQuaternion(FBXDocParser::Model::RotOrder_EulerXYZ, ImportUtils::deg2rad(raw_post_rotation));
+ print_verbose("valid post_rotation: " + raw_post_rotation + " euler conversion: " + (pre_rotation.get_euler() * (180 / Math_PI)));
+ }
+ const Vector3 &RotationPivot = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "RotationPivot", ok));
+ if (ok) {
+ rotation_pivot = ImportUtils::FixAxisConversions(RotationPivot);
+ }
+ const Vector3 &RotationOffset = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "RotationOffset", ok));
+ if (ok) {
+ rotation_offset = ImportUtils::FixAxisConversions(RotationOffset);
+ }
+ const Vector3 &ScalingOffset = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "ScalingOffset", ok));
+ if (ok) {
+ scaling_offset = ImportUtils::FixAxisConversions(ScalingOffset);
+ }
+ const Vector3 &ScalingPivot = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "ScalingPivot", ok));
+ if (ok) {
+ scaling_pivot = ImportUtils::FixAxisConversions(ScalingPivot);
+ }
+ const Vector3 &Translation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Translation", ok));
+ if (ok) {
+ translation = ImportUtils::FixAxisConversions(Translation);
+ }
+ raw_rotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Rotation", ok));
+ if (ok) {
+ rotation = ImportUtils::EulerToQuaternion(rot, ImportUtils::deg2rad(raw_rotation));
+ }
+ const Vector3 &Scaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "Lcl Scaling", ok));
+ if (ok) {
+ scaling = Scaling;
+ }
+ const Vector3 &GeometricScaling = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(props, "GeometricScaling", ok));
+ if (ok) {
+ geometric_scaling = GeometricScaling;
+ } else {
+ geometric_scaling = Vector3(0, 0, 0);
+ }
+
+ const Vector3 &GeometricRotation = ImportUtils::safe_import_vector3(FBXDocParser::PropertyGet<Vector3>(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<Vector3>(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);
+ }
+}
+
+Transform PivotTransform::ComputeLocalTransform(Vector3 p_translation, Quat p_rotation, Vector3 p_scaling) const {
+ Transform T, Roff, Rp, Soff, Sp, S;
+
+ // Here I assume this is the operation which needs done.
+ // Its WorldTransform * V
+
+ // Origin pivots
+ T.set_origin(p_translation);
+ Roff.set_origin(rotation_offset);
+ Rp.set_origin(rotation_pivot);
+ Soff.set_origin(scaling_offset);
+ Sp.set_origin(scaling_pivot);
+
+ // Scaling node
+ S.scale(p_scaling);
+ // Rotation pivots
+ Transform Rpre = Transform(pre_rotation);
+ Transform R = Transform(p_rotation);
+ Transform Rpost = Transform(post_rotation);
+
+ 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;
+
+ // Here I assume this is the operation which needs done.
+ // Its WorldTransform * V
+
+ // Origin pivots
+ T.set_origin(p_translation);
+ Roff.set_origin(rotation_offset);
+ Rp.set_origin(rotation_pivot);
+ Soff.set_origin(scaling_offset);
+ Sp.set_origin(scaling_pivot);
+
+ // Scaling node
+ S.scale(p_scaling);
+
+ // Rotation pivots
+ Transform Rpre = Transform(pre_rotation);
+ Transform R = Transform(p_rotation);
+ Transform Rpost = Transform(post_rotation);
+
+ Transform parent_global_xform;
+ Transform parent_local_scaling_m;
+
+ if (parent_transform.is_valid()) {
+ parent_global_xform = parent_transform->GlobalTransform;
+ parent_local_scaling_m = parent_transform->Local_Scaling_Matrix;
+ }
+
+ Transform local_rotation_m, parent_global_rotation_m;
+ Quat parent_global_rotation = parent_global_xform.basis.get_rotation_quat();
+ parent_global_rotation_m.basis.set_quat(parent_global_rotation);
+ local_rotation_m = Rpre * R * Rpost;
+
+ //Basis parent_global_rotation = Basis(parent_global_xform.get_basis().get_rotation_quat().normalized());
+
+ Transform local_shear_scaling, parent_shear_scaling, parent_shear_rotation, parent_shear_translation;
+ Vector3 parent_translation = parent_global_xform.get_origin();
+ parent_shear_translation.origin = parent_translation;
+ parent_shear_rotation = parent_shear_translation.affine_inverse() * parent_global_xform;
+ parent_shear_scaling = parent_global_rotation_m.affine_inverse() * parent_shear_rotation;
+ local_shear_scaling = S;
+
+ // Inherit type handler - we don't care about T here, just reordering RSrs etc.
+ Transform global_rotation_scale;
+ if (inherit_type == FBXDocParser::Transform_RrSs) {
+ global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_shear_scaling * local_shear_scaling;
+ } else if (inherit_type == FBXDocParser::Transform_RSrs) {
+ global_rotation_scale = parent_global_rotation_m * parent_shear_scaling * local_rotation_m * local_shear_scaling;
+ } else if (inherit_type == FBXDocParser::Transform_Rrs) {
+ Transform parent_global_shear_m_noLocal = parent_shear_scaling * parent_local_scaling_m.affine_inverse();
+ global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_global_shear_m_noLocal * local_shear_scaling;
+ }
+ Transform local_transform = T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse();
+ //Transform local_translation_pivoted = Transform(Basis(), LocalTransform.origin);
+
+ // manual hack to force SSC not to be compensated for - until we can handle it properly with tests
+ return parent_global_xform * local_transform;
+}
+
+void PivotTransform::ComputePivotTransform() {
+ Transform T, Roff, Rp, Soff, Sp, S;
+
+ // Here I assume this is the operation which needs done.
+ // Its WorldTransform * V
+
+ // Origin pivots
+ T.set_origin(translation);
+ Roff.set_origin(rotation_offset);
+ Rp.set_origin(rotation_pivot);
+ Soff.set_origin(scaling_offset);
+ Sp.set_origin(scaling_pivot);
+
+ // Scaling node
+ if (!scaling.is_equal_approx(Vector3())) {
+ S.scale(scaling);
+ } else {
+ S.scale(Vector3(1, 1, 1));
+ }
+ Local_Scaling_Matrix = S; // copy for when node / child is looking for the value of this.
+
+ // Rotation pivots
+ Transform Rpre = Transform(pre_rotation);
+ Transform R = Transform(rotation);
+ Transform Rpost = Transform(post_rotation);
+
+ Transform parent_global_xform;
+ Transform parent_local_scaling_m;
+
+ if (parent_transform.is_valid()) {
+ parent_global_xform = parent_transform->GlobalTransform;
+ parent_local_scaling_m = parent_transform->Local_Scaling_Matrix;
+ }
+
+ Transform local_rotation_m, parent_global_rotation_m;
+ Quat parent_global_rotation = parent_global_xform.basis.get_rotation_quat();
+ parent_global_rotation_m.basis.set_quat(parent_global_rotation);
+ local_rotation_m = Rpre * R * Rpost;
+
+ //Basis parent_global_rotation = Basis(parent_global_xform.get_basis().get_rotation_quat().normalized());
+
+ Transform local_shear_scaling, parent_shear_scaling, parent_shear_rotation, parent_shear_translation;
+ Vector3 parent_translation = parent_global_xform.get_origin();
+ parent_shear_translation.origin = parent_translation;
+ parent_shear_rotation = parent_shear_translation.affine_inverse() * parent_global_xform;
+ parent_shear_scaling = parent_global_rotation_m.affine_inverse() * parent_shear_rotation;
+ local_shear_scaling = S;
+
+ // Inherit type handler - we don't care about T here, just reordering RSrs etc.
+ Transform global_rotation_scale;
+ if (inherit_type == FBXDocParser::Transform_RrSs) {
+ global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_shear_scaling * local_shear_scaling;
+ } else if (inherit_type == FBXDocParser::Transform_RSrs) {
+ global_rotation_scale = parent_global_rotation_m * parent_shear_scaling * local_rotation_m * local_shear_scaling;
+ } else if (inherit_type == FBXDocParser::Transform_Rrs) {
+ Transform parent_global_shear_m_noLocal = parent_shear_scaling * parent_local_scaling_m.inverse();
+ global_rotation_scale = parent_global_rotation_m * local_rotation_m * parent_global_shear_m_noLocal * local_shear_scaling;
+ }
+ LocalTransform = Transform();
+ LocalTransform = T * Roff * Rp * Rpre * R * Rpost.affine_inverse() * Rp.affine_inverse() * Soff * Sp * S * Sp.affine_inverse();
+
+ ERR_FAIL_COND_MSG(LocalTransform.basis.determinant() == 0, "invalid scale reset");
+
+ Transform local_translation_pivoted = Transform(Basis(), LocalTransform.origin);
+ GlobalTransform = Transform();
+ //GlobalTransform = parent_global_xform * LocalTransform;
+ Transform global_origin = Transform(Basis(), parent_translation);
+ GlobalTransform = (global_origin * local_translation_pivoted) * global_rotation_scale;
+
+ ImportUtils::debug_xform("local xform calculation", LocalTransform);
+ print_verbose("scale of node: " + S.basis.get_scale_local());
+ print_verbose("---------------------------------------------------------------");
+}
+
+void PivotTransform::Execute() {
+ ReadTransformChain();
+ ComputePivotTransform();
+
+ ImportUtils::debug_xform("global xform: ", GlobalTransform);
+ computed_global_xform = true;
+}
diff --git a/modules/fbx/data/pivot_transform.h b/modules/fbx/data/pivot_transform.h
new file mode 100644
index 0000000000..025c8dd58d
--- /dev/null
+++ b/modules/fbx/data/pivot_transform.h
@@ -0,0 +1,115 @@
+/*************************************************************************/
+/* pivot_transform.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 PIVOT_TRANSFORM_H
+#define PIVOT_TRANSFORM_H
+
+#include "core/math/transform.h"
+#include "core/object/reference.h"
+
+#include "model_abstraction.h"
+
+#include "fbx_parser/FBXDocument.h"
+#include "tools/import_utils.h"
+
+enum TransformationComp {
+ TransformationComp_Translation,
+ TransformationComp_Scaling,
+ TransformationComp_Rotation,
+ TransformationComp_RotationOffset,
+ TransformationComp_RotationPivot,
+ TransformationComp_PreRotation,
+ TransformationComp_PostRotation,
+ TransformationComp_ScalingOffset,
+ TransformationComp_ScalingPivot,
+ TransformationComp_GeometricTranslation,
+ TransformationComp_GeometricRotation,
+ TransformationComp_GeometricScaling,
+ TransformationComp_MAXIMUM
+};
+// Abstract away pivot data so its simpler to handle
+struct PivotTransform : Reference, ModelAbstraction {
+ // at the end we want to keep geometric_ everything, post and pre rotation
+ // these are used during animation data processing / keyframe ingestion the rest can be simplified down / out.
+ Quat pre_rotation = Quat();
+ Quat post_rotation = Quat();
+ Quat rotation = Quat();
+ Quat geometric_rotation = Quat();
+ Vector3 rotation_pivot = Vector3();
+ Vector3 rotation_offset = Vector3();
+ Vector3 scaling_offset = Vector3(1.0, 1.0, 1.0);
+ Vector3 scaling_pivot = Vector3(1.0, 1.0, 1.0);
+ Vector3 translation = Vector3();
+ Vector3 scaling = Vector3(1.0, 1.0, 1.0);
+ Vector3 geometric_scaling = Vector3(1.0, 1.0, 1.0);
+ Vector3 geometric_translation = Vector3();
+
+ Vector3 raw_rotation = Vector3();
+ Vector3 raw_post_rotation = Vector3();
+ Vector3 raw_pre_rotation = Vector3();
+
+ /* Read pivots from the document */
+ void ReadTransformChain();
+
+ void debug_pivot_xform(String p_name) {
+ print_verbose("debugging node name: " + p_name);
+ print_verbose("raw rotation: " + raw_rotation * (180 / Math_PI));
+ 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;
+
+ /* Extract into xforms and calculate once */
+ void ComputePivotTransform();
+
+ /* Execute the command for the pivot generation */
+ void Execute();
+
+ void set_parent(Ref<PivotTransform> p_parent) {
+ parent_transform = p_parent;
+ }
+
+ bool computed_global_xform = false;
+ Ref<PivotTransform> parent_transform = Ref<PivotTransform>();
+ //Transform chain[TransformationComp_MAXIMUM];
+
+ // cached for later use
+ Transform GlobalTransform = Transform();
+ Transform LocalTransform = Transform();
+ Transform Local_Scaling_Matrix = Transform(); // used for inherit type.
+ Transform GeometricTransform = Transform(); // 3DS max only
+ FBXDocParser::TransformInheritance inherit_type = FBXDocParser::TransformInheritance_MAX; // maya fbx requires this - sorry <3
+};
+
+#endif // PIVOT_TRANSFORM_H