Prechádzať zdrojové kódy

Merge pull request #44499 from RevoluPowered/fbx_plugin_port_4.0

[fbx] Port FBX module from 3.2 branch
Rémi Verschelde 4 rokov pred
rodič
commit
35a8ebaa10
53 zmenil súbory, kde vykonal 14712 pridanie a 0 odobranie
  1. 196 0
      modules/fbx/README.md
  2. 15 0
      modules/fbx/SCsub
  3. 16 0
      modules/fbx/config.py
  4. 46 0
      modules/fbx/data/fbx_anim_container.h
  5. 56 0
      modules/fbx/data/fbx_bone.cpp
  6. 90 0
      modules/fbx/data/fbx_bone.h
  7. 464 0
      modules/fbx/data/fbx_material.cpp
  8. 286 0
      modules/fbx/data/fbx_material.h
  9. 1461 0
      modules/fbx/data/fbx_mesh_data.cpp
  10. 183 0
      modules/fbx/data/fbx_mesh_data.h
  11. 63 0
      modules/fbx/data/fbx_node.h
  12. 123 0
      modules/fbx/data/fbx_skeleton.cpp
  13. 53 0
      modules/fbx/data/fbx_skeleton.h
  14. 112 0
      modules/fbx/data/import_state.h
  15. 52 0
      modules/fbx/data/model_abstraction.h
  16. 294 0
      modules/fbx/data/pivot_transform.cpp
  17. 115 0
      modules/fbx/data/pivot_transform.h
  18. 36 0
      modules/fbx/doc_classes/EditorSceneImporterFBX.xml
  19. 1423 0
      modules/fbx/editor_scene_importer_fbx.cpp
  20. 133 0
      modules/fbx/editor_scene_importer_fbx.h
  21. 282 0
      modules/fbx/fbx_parser/ByteSwapper.h
  22. 183 0
      modules/fbx/fbx_parser/CREDITS
  23. 290 0
      modules/fbx/fbx_parser/FBXAnimation.cpp
  24. 467 0
      modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp
  25. 110 0
      modules/fbx/fbx_parser/FBXCommon.h
  26. 279 0
      modules/fbx/fbx_parser/FBXDeformer.cpp
  27. 713 0
      modules/fbx/fbx_parser/FBXDocument.cpp
  28. 1319 0
      modules/fbx/fbx_parser/FBXDocument.h
  29. 172 0
      modules/fbx/fbx_parser/FBXDocumentUtil.cpp
  30. 141 0
      modules/fbx/fbx_parser/FBXDocumentUtil.h
  31. 173 0
      modules/fbx/fbx_parser/FBXImportSettings.h
  32. 407 0
      modules/fbx/fbx_parser/FBXMaterial.cpp
  33. 485 0
      modules/fbx/fbx_parser/FBXMeshGeometry.cpp
  34. 263 0
      modules/fbx/fbx_parser/FBXMeshGeometry.h
  35. 176 0
      modules/fbx/fbx_parser/FBXModel.cpp
  36. 183 0
      modules/fbx/fbx_parser/FBXNodeAttribute.cpp
  37. 111 0
      modules/fbx/fbx_parser/FBXParseTools.h
  38. 1295 0
      modules/fbx/fbx_parser/FBXParser.cpp
  39. 263 0
      modules/fbx/fbx_parser/FBXParser.h
  40. 104 0
      modules/fbx/fbx_parser/FBXPose.cpp
  41. 237 0
      modules/fbx/fbx_parser/FBXProperties.cpp
  42. 221 0
      modules/fbx/fbx_parser/FBXProperties.h
  43. 251 0
      modules/fbx/fbx_parser/FBXTokenizer.cpp
  44. 203 0
      modules/fbx/fbx_parser/FBXTokenizer.h
  45. 220 0
      modules/fbx/fbx_parser/FBXUtil.cpp
  46. 122 0
      modules/fbx/fbx_parser/FBXUtil.h
  47. 39 0
      modules/fbx/fbx_parser/LICENSE
  48. 58 0
      modules/fbx/register_types.cpp
  49. 37 0
      modules/fbx/register_types.h
  50. 151 0
      modules/fbx/tools/import_utils.cpp
  51. 400 0
      modules/fbx/tools/import_utils.h
  52. 48 0
      modules/fbx/tools/validation_tools.cpp
  53. 92 0
      modules/fbx/tools/validation_tools.h

+ 196 - 0
modules/fbx/README.md

@@ -0,0 +1,196 @@
+# Open Source FBX Specification for the Importer
+
+The goal of this document is to make everything in FBX clearly stated, any errors will be corrected over time this
+is a first draft.
+
+## fbx parser - originally from assimp
+
+- Folder: /modules/fbx/fbx_parser
+- Upstream: assimp
+- Original Version: git (308db73d0b3c2d1870cd3e465eaa283692a4cf23, 2019)
+- License: BSD-3-Clause
+
+This can never be updated from upstream, we have heavily modified the parser to provide memory safety and add some
+functionality. If anything we should give this parser back to assimp at some point as it has a lot of new features.
+
+# Updating assimp fbx parser
+
+Don't. it's not possible the code is rewritten in many areas to remove thirdparty deps and various bugs are fixed.
+
+Many days were put into rewriting the parser to use safe code and safe memory accessors.
+
+# File Headers
+
+FBX Binaries start with the header "Kaydara FBX Binary"
+
+FBX ASCII documents contain a larger header, sometimes with copyright information for a file.
+
+Detecting these is pretty simple.
+
+# What is an OP link?
+It's an object to property link. It lists the properties for that object in some cases. Source and destination based by
+ID.
+
+# What is a OO link?
+Its an object to object link, it contains the ID source and destination ID.
+
+# FBX Node connections
+
+Nodes in FBX are connected using OO links, This means Object to Object.
+
+FBX has a single other kind of link which is Object Property, this is used for Object to Property Links, this can be
+ extra attributes, defaults, or even some simple settings.
+
+# Bones / Joints / Locators
+
+Bones in FBX are nodes, they initially have the Model:: Type, then have links to SubDeformer the sub deformer
+is part of the skin there is also an explicit Skin link, which then links to the geometry using OO links in the
+document.
+
+# Rotation Order in FBX compared to Godot
+
+**Godot uses the rotation order:** YXZ
+
+**FBX has dynamic rotation order to prevent gimbal lock with complex animations**
+
+```cpp
+enum RotOrder {
+	RotOrder_EulerXYZ = 0
+	RotOrder_EulerXZY,
+	RotOrder_EulerYZX,
+	RotOrder_EulerYXZ,
+	RotOrder_EulerZXY,
+	RotOrder_EulerZYX,
+	RotOrder_SphericXYZ // nobody uses this - as far as we can tell
+};
+```
+
+
+# Pivot transforms
+
+### Pivot description:
+- Maya and 3DS max consider everything to be in node space (bones joints, skins, lights, cameras, etc)
+- Everything is a node, this means essentially nodes are auto or variants
+- They are local to the node in the tree.
+- They are used to calculate where a node is in space
+```c++
+// For a better reference you can check editor_scene_importer_fbx.h
+// references: GenFBXTransform / read the data in
+// references: ComputePivotTransform / run the calculation
+// This is the local pivot transform for the node, not the global transforms
+Transform ComputePivotTransform(
+		Transform chain[TransformationComp_MAXIMUM],
+		Transform &geometric_transform) {
+	// Maya pivots
+	Transform T = chain[TransformationComp_Translation];
+	Transform Roff = chain[TransformationComp_RotationOffset];
+	Transform Rp = chain[TransformationComp_RotationPivot];
+	Transform Rpre = chain[TransformationComp_PreRotation];
+	Transform R = chain[TransformationComp_Rotation];
+	Transform Rpost = chain[TransformationComp_PostRotation];
+	Transform Soff = chain[TransformationComp_ScalingOffset];
+	Transform Sp = chain[TransformationComp_ScalingPivot];
+	Transform S = chain[TransformationComp_Scaling];
+
+	// 3DS Max Pivots
+	Transform OT = chain[TransformationComp_GeometricTranslation];
+	Transform OR = chain[TransformationComp_GeometricRotation];
+	Transform OS = chain[TransformationComp_GeometricScaling];
+
+	// Calculate 3DS max pivot transform - use geometric space (e.g doesn't effect children nodes only the current node)
+	geometric_transform = OT * OR * OS;
+	// Calculate standard maya pivots
+	return T * Roff * Rp * Rpre * R * Rpost.inverse() * Rp.inverse() * Soff * Sp * S * Sp.inverse();
+}
+```
+
+# Transform inheritance for FBX Nodes
+
+The goal of below is to explain why they implement this in the first place.
+
+The use case is to make nodes have an option to override their local scaling or to make scaling influenced by orientation, which i would imagine would be useful for when you need to rotate a node and the child to scale based on the orientation rather than setting on the rotation matrix planes.
+```cpp
+// not modified the formatting here since this code must remain clear
+enum TransformInheritance {
+	Transform_RrSs = 0,
+	// Parent Rotation * Local Rotation * Parent Scale * Local Scale  -- Parent Rotation Offset * Parent ScalingOffset (Local scaling is offset by rotation of parent node)
+	Transform_RSrs = 1, // Parent Rotation * Parent Scale * Local Rotation * Local Scale -- Parent * Local (normal mode)
+	Transform_Rrs = 2, // Parent Rotation * Local Rotation * Local Scale -- Node transform scale is the only relevant component
+	TransformInheritance_MAX // end-of-enum sentinel
+};
+
+enum TransformInheritance {
+	Transform_RrSs = 0,
+	// Local scaling is offset by rotation of parent node
+	Transform_RSrs = 1,
+	// Parent * Local (normal mode)
+	Transform_Rrs = 2,
+	// Node transform scale is the only relevant component
+	TransformInheritance_MAX // end-of-enum sentinel
+};
+```
+
+# Axis in FBX
+
+Godot has one format for the declared axis
+
+AXIS X, AXIS Y, -AXIS Z
+
+FBX supports any format you can think of. As it has to support Maya and 3DS Max.
+
+#### FBX File Header
+```json
+GlobalSettings:  {
+	Version: 1000
+	Properties70:  {
+		P: "UpAxis", "int", "Integer", "",1
+		P: "UpAxisSign", "int", "Integer", "",1
+		P: "FrontAxis", "int", "Integer", "",2
+		P: "FrontAxisSign", "int", "Integer", "",1
+		P: "CoordAxis", "int", "Integer", "",0
+		P: "CoordAxisSign", "int", "Integer", "",1
+		P: "OriginalUpAxis", "int", "Integer", "",1
+		P: "OriginalUpAxisSign", "int", "Integer", "",1
+		P: "UnitScaleFactor", "double", "Number", "",1
+		P: "OriginalUnitScaleFactor", "double", "Number", "",1
+		P: "AmbientColor", "ColorRGB", "Color", "",0,0,0
+		P: "DefaultCamera", "KString", "", "", "Producer Perspective"
+		P: "TimeMode", "enum", "", "",6
+		P: "TimeProtocol", "enum", "", "",2
+		P: "SnapOnFrameMode", "enum", "", "",0
+		P: "TimeSpanStart", "KTime", "Time", "",0
+		P: "TimeSpanStop", "KTime", "Time", "",92372316000
+		P: "CustomFrameRate", "double", "Number", "",-1
+		P: "TimeMarker", "Compound", "", ""
+		P: "CurrentTimeMarker", "int", "Integer", "",-1
+	}
+}
+```
+
+#### FBX FILE declares axis dynamically using FBX header
+Coord is X
+Up is Y
+Front is Z
+
+#### GODOT - constant reference point
+Coord is X positive,
+Y is up positive,
+Front is -Z negative
+
+### Explaining MeshGeometry indexing
+
+Reference type declared:
+- Direct (directly related to the mapping information type)
+- IndexToDirect (Map with key value, meaning depends on the MappingInformationType)
+
+ControlPoint is a vertex
+* None The mapping is undetermined.
+* ByVertex There will be one mapping coordinate for each surface control point/vertex.
+    * If you have direct reference type vertices [x]
+    * If you have IndexToDirect reference type the UV
+* ByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part. (Sorted by polygon, referencing vertex)
+* ByPolygon There can be only one mapping coordinate for the whole polygon.
+    * One mapping per polygon polygon x has this normal x
+    * For each vertex of the polygon then set the normal to x
+* ByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements. (Mapping is referencing the edge id)
+* AllSame There can be only one mapping coordinate for the whole surface.

+ 15 - 0
modules/fbx/SCsub

@@ -0,0 +1,15 @@
+#!/usr/bin/env python
+
+Import("env")
+Import("env_modules")
+
+env_fbx = env_modules.Clone()
+
+# Make includes relative to the folder path specified here so our includes are clean
+env_fbx.Prepend(CPPPATH=["#modules/fbx/"])
+
+# Godot's own source files
+env_fbx.add_source_files(env.modules_sources, "tools/*.cpp")
+env_fbx.add_source_files(env.modules_sources, "data/*.cpp")
+env_fbx.add_source_files(env.modules_sources, "fbx_parser/*.cpp")
+env_fbx.add_source_files(env.modules_sources, "*.cpp")

+ 16 - 0
modules/fbx/config.py

@@ -0,0 +1,16 @@
+def can_build(env, platform):
+    return env["tools"]
+
+
+def configure(env):
+    pass
+
+
+def get_doc_classes():
+    return [
+        "EditorSceneImporterFBX",
+    ]
+
+
+def get_doc_path():
+    return "doc_classes"

+ 46 - 0
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

+ 56 - 0
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;
+}

+ 90 - 0
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

+ 464 - 0
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;
+}

+ 286 - 0
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

+ 1461 - 0
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];
+					}
+				}
+			}
+		}
+	}
+}

+ 183 - 0
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

+ 63 - 0
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

+ 123 - 0
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
+			}
+		}
+	}
+}

+ 53 - 0
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

+ 112 - 0
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

+ 52 - 0
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

+ 294 - 0
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;
+}

+ 115 - 0
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

+ 36 - 0
modules/fbx/doc_classes/EditorSceneImporterFBX.xml

@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorSceneImporterFBX" inherits="EditorSceneImporter" version="3.2">
+	<brief_description>
+		FBX 3D asset importer.
+	</brief_description>
+	<description>
+		This is an FBX 3D asset importer with full support for most FBX features.
+		If exporting a FBX scene from Autodesk Maya, use these FBX export settings:
+		[codeblock]
+		- Smoothing Groups
+		- Smooth Mesh
+		- Triangluate (for meshes with blend shapes)
+		- Bake Animation
+		- Resample All
+		- Deformed Models
+		- Skins
+		- Blend Shapes
+		- Curve Filters
+		- Constant Key Reducer
+		- Auto Tangents Only
+		- *Do not check* Constraints (as it will break the file)
+		- Can check Embed Media (embeds textures into the exported FBX file)
+		  - Note that when importing embedded media, the texture and mesh will be a single immutable file.
+		  - You will have to re-export then re-import the FBX if the texture has changed.
+		- Units: Centimeters
+		- Up Axis: Y
+		- Binary format in FBX 2017
+		[/codeblock]
+	</description>
+	<tutorials>
+	</tutorials>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 1423 - 0
modules/fbx/editor_scene_importer_fbx.cpp

@@ -0,0 +1,1423 @@
+/*************************************************************************/
+/*  editor_scene_importer_fbx.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 "editor_scene_importer_fbx.h"
+
+#include "data/fbx_anim_container.h"
+#include "data/fbx_material.h"
+#include "data/fbx_mesh_data.h"
+#include "data/fbx_skeleton.h"
+#include "tools/import_utils.h"
+
+#include "core/io/image_loader.h"
+#include "editor/editor_log.h"
+#include "editor/editor_node.h"
+#include "editor/import/resource_importer_scene.h"
+#include "scene/3d/bone_attachment_3d.h"
+#include "scene/3d/camera_3d.h"
+#include "scene/3d/light_3d.h"
+#include "scene/3d/mesh_instance_3d.h"
+#include "scene/main/node.h"
+#include "scene/resources/material.h"
+
+#include "fbx_parser/FBXDocument.h"
+#include "fbx_parser/FBXImportSettings.h"
+#include "fbx_parser/FBXMeshGeometry.h"
+#include "fbx_parser/FBXParser.h"
+#include "fbx_parser/FBXProperties.h"
+#include "fbx_parser/FBXTokenizer.h"
+
+#include <string>
+
+void EditorSceneImporterFBX::get_extensions(List<String> *r_extensions) const {
+	// register FBX as the one and only format for FBX importing
+	const String import_setting_string = "filesystem/import/fbx/";
+	const String fbx_str = "fbx";
+	Vector<String> exts;
+	exts.push_back(fbx_str);
+	_register_project_setting_import(fbx_str, import_setting_string, exts, r_extensions,
+			true);
+}
+
+void EditorSceneImporterFBX::_register_project_setting_import(const String generic,
+		const String import_setting_string,
+		const Vector<String> &exts,
+		List<String> *r_extensions,
+		const bool p_enabled) const {
+	const String use_generic = "use_" + generic;
+	_GLOBAL_DEF(import_setting_string + use_generic, p_enabled, true);
+	if (ProjectSettings::get_singleton()->get(import_setting_string + use_generic)) {
+		for (int32_t i = 0; i < exts.size(); i++) {
+			r_extensions->push_back(exts[i]);
+		}
+	}
+}
+
+uint32_t EditorSceneImporterFBX::get_import_flags() const {
+	return IMPORT_SCENE;
+}
+
+Node3D *EditorSceneImporterFBX::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps,
+		List<String> *r_missing_deps, Error *r_err) {
+	// done for performance when re-importing lots of files when testing importer in verbose only!
+	if (OS::get_singleton()->is_stdout_verbose()) {
+		EditorLog *log = EditorNode::get_log();
+		log->clear();
+	}
+	Error err;
+	FileAccessRef f = FileAccess::open(p_path, FileAccess::READ, &err);
+
+	ERR_FAIL_COND_V(!f, NULL);
+
+	{
+		PackedByteArray data;
+		// broadphase tokenizing pass in which we identify the core
+		// syntax elements of FBX (brackets, commas, key:value mappings)
+		FBXDocParser::TokenList tokens;
+
+		bool is_binary = false;
+		data.resize(f->get_len());
+		f->get_buffer(data.ptrw(), data.size());
+		PackedByteArray fbx_header;
+		fbx_header.resize(64);
+		for (int32_t byte_i = 0; byte_i < 64; byte_i++) {
+			fbx_header.ptrw()[byte_i] = data.ptr()[byte_i];
+		}
+
+		String fbx_header_string;
+		if (fbx_header.size() >= 0) {
+			fbx_header_string.parse_utf8((const char *)fbx_header.ptr(), fbx_header.size());
+		}
+
+		print_verbose("[doc] opening fbx file: " + p_path);
+		print_verbose("[doc] fbx header: " + fbx_header_string);
+
+		// safer to check this way as there can be different formatted headers
+		if (fbx_header_string.find("Kaydara FBX Binary", 0) != -1) {
+			is_binary = true;
+			print_verbose("[doc] is binary");
+			FBXDocParser::TokenizeBinary(tokens, (const char *)data.ptrw(), (size_t)data.size());
+		} else {
+			print_verbose("[doc] is ascii");
+			FBXDocParser::Tokenize(tokens, (const char *)data.ptrw(), (size_t)data.size());
+		}
+
+		// The import process explained:
+		// 1. Tokens are made, these are then taken into the 'parser' below
+		// 2. The parser constructs 'Elements' and all 'real' FBX Types.
+		// 3. This creates a problem: shared_ptr ownership, should Elements later 'take ownership'
+		// 4. No, it shouldn't so we should either a.) use weak ref for elements; but this is not correct.
+
+		// use this information to construct a very rudimentary
+		// parse-tree representing the FBX scope structure
+		FBXDocParser::Parser parser(tokens, is_binary);
+		FBXDocParser::ImportSettings settings;
+		settings.strictMode = false;
+
+		// this function leaks a lot
+		FBXDocParser::Document doc(parser, settings);
+
+		// yeah so closing the file is a good idea (prevents readonly states)
+		f->close();
+
+		// safety for version handling
+		if (doc.IsSafeToImport()) {
+			bool is_blender_fbx = false;
+			//const FBXDocParser::PropertyPtr app_vendor = p_document->GlobalSettingsPtr()->Props()
+			//	p_document->Creator()
+			const FBXDocParser::PropertyTable *import_props = doc.GetMetadataProperties();
+			const FBXDocParser::PropertyPtr app_name = import_props->Get("Original|ApplicationName");
+			const FBXDocParser::PropertyPtr app_vendor = import_props->Get("Original|ApplicationVendor");
+			const FBXDocParser::PropertyPtr app_version = import_props->Get("Original|ApplicationVersion");
+			//
+			if (app_name) {
+				const FBXDocParser::TypedProperty<std::string> *app_name_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_name);
+				if (app_name_string) {
+					print_verbose("FBX App Name: " + String(app_name_string->Value().c_str()));
+				}
+			}
+
+			if (app_vendor) {
+				const FBXDocParser::TypedProperty<std::string> *app_vendor_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_vendor);
+				if (app_vendor_string) {
+					print_verbose("FBX App Vendor: " + String(app_vendor_string->Value().c_str()));
+					is_blender_fbx = app_vendor_string->Value().find("Blender") != std::string::npos;
+				}
+			}
+
+			if (app_version) {
+				const FBXDocParser::TypedProperty<std::string> *app_version_string = dynamic_cast<const FBXDocParser::TypedProperty<std::string> *>(app_version);
+				if (app_version_string) {
+					print_verbose("FBX App Version: " + String(app_version_string->Value().c_str()));
+				}
+			}
+
+			if (is_blender_fbx) {
+				WARN_PRINT("Blender FBX files will not work properly with keyframes or skeletons until we make fixes stand by.");
+			}
+
+			Node3D *spatial = _generate_scene(p_path, &doc, p_flags, p_bake_fps, 8);
+			// todo: move to document shutdown (will need to be validated after moving; this code has been validated already)
+			for (FBXDocParser::TokenPtr token : tokens) {
+				if (token) {
+					delete token;
+					token = nullptr;
+				}
+			}
+
+			return spatial;
+
+		} else {
+			print_error("Cannot import file: " + p_path + " version of file is unsupported, please re-export in your modelling package file version is: " + itos(doc.FBXVersion()));
+		}
+	}
+
+	return memnew(Node3D);
+}
+
+template <class T>
+struct EditorSceneImporterAssetImportInterpolate {
+	T lerp(const T &a, const T &b, float c) const {
+		return a + (b - a) * c;
+	}
+
+	T catmull_rom(const T &p0, const T &p1, const T &p2, const T &p3, float t) {
+		const float t2 = t * t;
+		const float t3 = t2 * t;
+
+		return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4.0f * p2 - p3) * t2 + (-p0 + 3.0f * p1 - 3.0f * p2 + p3) * t3);
+	}
+
+	T bezier(T start, T control_1, T control_2, T end, float t) {
+		/* Formula from Wikipedia article on Bezier curves. */
+		const real_t omt = (1.0 - t);
+		const real_t omt2 = omt * omt;
+		const real_t omt3 = omt2 * omt;
+		const real_t t2 = t * t;
+		const real_t t3 = t2 * t;
+
+		return start * omt3 + control_1 * omt2 * t * 3.0 + control_2 * omt * t2 * 3.0 + end * t3;
+	}
+};
+
+//thank you for existing, partial specialization
+template <>
+struct EditorSceneImporterAssetImportInterpolate<Quat> {
+	Quat lerp(const Quat &a, const Quat &b, float c) const {
+		ERR_FAIL_COND_V(!a.is_normalized(), Quat());
+		ERR_FAIL_COND_V(!b.is_normalized(), Quat());
+
+		return a.slerp(b, c).normalized();
+	}
+
+	Quat catmull_rom(const Quat &p0, const Quat &p1, const Quat &p2, const Quat &p3, float c) {
+		ERR_FAIL_COND_V(!p1.is_normalized(), Quat());
+		ERR_FAIL_COND_V(!p2.is_normalized(), Quat());
+
+		return p1.slerp(p2, c).normalized();
+	}
+
+	Quat bezier(Quat start, Quat control_1, Quat control_2, Quat end, float t) {
+		ERR_FAIL_COND_V(!start.is_normalized(), Quat());
+		ERR_FAIL_COND_V(!end.is_normalized(), Quat());
+
+		return start.slerp(end, t).normalized();
+	}
+};
+
+template <class T>
+T EditorSceneImporterFBX::_interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time,
+		AssetImportAnimation::Interpolation p_interp) {
+	//could use binary search, worth it?
+	int idx = -1;
+	for (int i = 0; i < p_times.size(); i++) {
+		if (p_times[i] > p_time)
+			break;
+		idx++;
+	}
+
+	EditorSceneImporterAssetImportInterpolate<T> interp;
+
+	switch (p_interp) {
+		case AssetImportAnimation::INTERP_LINEAR: {
+			if (idx == -1) {
+				return p_values[0];
+			} else if (idx >= p_times.size() - 1) {
+				return p_values[p_times.size() - 1];
+			}
+
+			float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]);
+
+			return interp.lerp(p_values[idx], p_values[idx + 1], c);
+
+		} break;
+		case AssetImportAnimation::INTERP_STEP: {
+			if (idx == -1) {
+				return p_values[0];
+			} else if (idx >= p_times.size() - 1) {
+				return p_values[p_times.size() - 1];
+			}
+
+			return p_values[idx];
+
+		} break;
+		case AssetImportAnimation::INTERP_CATMULLROMSPLINE: {
+			if (idx == -1) {
+				return p_values[1];
+			} else if (idx >= p_times.size() - 1) {
+				return p_values[1 + p_times.size() - 1];
+			}
+
+			float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]);
+
+			return interp.catmull_rom(p_values[idx - 1], p_values[idx], p_values[idx + 1], p_values[idx + 3], c);
+
+		} break;
+		case AssetImportAnimation::INTERP_CUBIC_SPLINE: {
+			if (idx == -1) {
+				return p_values[1];
+			} else if (idx >= p_times.size() - 1) {
+				return p_values[(p_times.size() - 1) * 3 + 1];
+			}
+
+			float c = (p_time - p_times[idx]) / (p_times[idx + 1] - p_times[idx]);
+
+			T from = p_values[idx * 3 + 1];
+			T c1 = from + p_values[idx * 3 + 2];
+			T to = p_values[idx * 3 + 4];
+			T c2 = to + p_values[idx * 3 + 3];
+
+			return interp.bezier(from, c1, c2, to, c);
+
+		} break;
+	}
+
+	ERR_FAIL_V(p_values[0]);
+}
+
+Node3D *EditorSceneImporterFBX::_generate_scene(
+		const String &p_path,
+		const FBXDocParser::Document *p_document,
+		const uint32_t p_flags,
+		int p_bake_fps,
+		const int32_t p_max_bone_weights) {
+	ImportState state;
+	state.path = p_path;
+	state.animation_player = NULL;
+
+	// create new root node for scene
+	Node3D *scene_root = memnew(Node3D);
+	state.root = memnew(Node3D);
+	state.root_owner = scene_root; // the real scene root... sorry compatibility code is painful...
+
+	state.root->set_name("RootNode");
+	scene_root->add_child(state.root);
+	state.root->set_owner(scene_root);
+
+	state.fbx_root_node.instance();
+	state.fbx_root_node->godot_node = state.root;
+
+	// Size relative to cm.
+	const real_t fbx_unit_scale = p_document->GlobalSettingsPtr()->UnitScaleFactor();
+
+	print_verbose("FBX unit scale import value: " + rtos(fbx_unit_scale));
+	// Set FBX file scale is relative to CM must be converted to M
+	state.scale = fbx_unit_scale / 100.0;
+	print_verbose("FBX unit scale is: " + rtos(state.scale));
+
+	// Enabled by default.
+	state.enable_material_import = true;
+	// Enabled by default.
+	state.enable_animation_import = true;
+	Ref<FBXNode> root_node;
+	root_node.instance();
+
+	// make sure fake noFBXDocParser::PropertyPtr ptrde always has a transform too ;)
+	Ref<PivotTransform> pivot_transform;
+	pivot_transform.instance();
+	root_node->pivot_transform = pivot_transform;
+	root_node->node_name = "root node";
+	root_node->current_node_id = 0;
+	root_node->godot_node = state.root;
+
+	// cache this node onto the fbx_target map.
+	state.fbx_target_map.insert(0, root_node);
+
+	// cache basic node information from FBX document
+	// grabs all FBX bones
+	BuildDocumentBones(Ref<FBXBone>(), state, p_document, 0L);
+	BuildDocumentNodes(Ref<PivotTransform>(), state, p_document, 0L, nullptr);
+
+	// Build document skinning information
+
+	// Algorithm is this:
+	// Get Deformer: object with "Skin" class.
+	// Deformer:: has link to Geometry:: (correct mesh for skin)
+	// Deformer:: has Source which is the SubDeformer:: (e.g. the Cluster)
+	// Notes at the end it configures the vertex weight mapping.
+
+	for (uint64_t skin_id : p_document->GetSkinIDs()) {
+		// Validate the parser
+		FBXDocParser::LazyObject *lazy_skin = p_document->GetObject(skin_id);
+		ERR_CONTINUE_MSG(lazy_skin == nullptr, "invalid lazy object [serious parser bug]");
+
+		// Validate the parser
+		const FBXDocParser::Skin *skin = lazy_skin->Get<FBXDocParser::Skin>();
+		ERR_CONTINUE_MSG(skin == nullptr, "invalid skin added to skin list [parser bug]");
+
+		const std::vector<const FBXDocParser::Connection *> source_to_destination = p_document->GetConnectionsBySourceSequenced(skin_id);
+		FBXDocParser::MeshGeometry *mesh = nullptr;
+		uint64_t mesh_id = 0;
+
+		// Most likely only contains the mesh link for the skin
+		// The mesh geometry.
+		for (const FBXDocParser::Connection *con : source_to_destination) {
+			// do something
+			print_verbose("src: " + itos(con->src));
+			FBXDocParser::Object *ob = con->DestinationObject();
+			mesh = dynamic_cast<FBXDocParser::MeshGeometry *>(ob);
+
+			if (mesh) {
+				mesh_id = mesh->ID();
+				break;
+			}
+		}
+
+		// Validate the mesh exists and was retrieved
+		ERR_CONTINUE_MSG(mesh_id == 0, "mesh id is invalid");
+		const std::vector<const FBXDocParser::Cluster *> clusters = skin->Clusters();
+
+		// NOTE: this will ONLY work on skinned bones (it is by design.)
+		// A cluster is a skinned bone so SKINS won't contain unskinned bones so we need to pre-add all bones and parent them in a step beforehand.
+		for (const FBXDocParser::Cluster *cluster : clusters) {
+			ERR_CONTINUE_MSG(cluster == nullptr, "invalid bone cluster");
+			const uint64_t deformer_id = cluster->ID();
+			std::vector<const FBXDocParser::Connection *> connections = p_document->GetConnectionsByDestinationSequenced(deformer_id);
+
+			// Weight data always has a node in the scene lets grab the limb's node in the scene :) (reverse set to true since it's the opposite way around)
+			const FBXDocParser::ModelLimbNode *limb_node = ProcessDOMConnection<FBXDocParser::ModelLimbNode>(p_document, deformer_id, true);
+
+			ERR_CONTINUE_MSG(limb_node == nullptr, "unable to resolve model for skinned bone");
+
+			const uint64_t model_id = limb_node->ID();
+
+			// This will never happen, so if it does you know you fucked up.
+			ERR_CONTINUE_MSG(!state.fbx_bone_map.has(model_id), "missing LimbNode detected");
+
+			// new bone instance
+			Ref<FBXBone> bone_element = state.fbx_bone_map[model_id];
+
+			//
+			// Bone Weight Information Configuration
+			//
+
+			// Cache Weight Information into bone for later usage if you want the raw data.
+			const std::vector<unsigned int> &indexes = cluster->GetIndices();
+			const std::vector<float> &weights = cluster->GetWeights();
+			Ref<FBXMeshData> mesh_vertex_data;
+
+			// this data will pre-exist if vertex weight information is found
+			if (state.renderer_mesh_data.has(mesh_id)) {
+				mesh_vertex_data = state.renderer_mesh_data[mesh_id];
+			} else {
+				mesh_vertex_data.instance();
+				state.renderer_mesh_data.insert(mesh_id, mesh_vertex_data);
+			}
+
+			mesh_vertex_data->armature_id = bone_element->armature_id;
+			mesh_vertex_data->valid_armature_id = true;
+
+			//print_verbose("storing mesh vertex data for mesh to use later");
+			ERR_CONTINUE_MSG(indexes.size() != weights.size(), "[doc] error mismatch between weight info");
+
+			for (size_t idx = 0; idx < indexes.size(); idx++) {
+				const size_t vertex_index = indexes[idx];
+				const real_t influence_weight = weights[idx];
+
+				VertexWeightMapping &vm = mesh_vertex_data->vertex_weights[vertex_index];
+				vm.weights.push_back(influence_weight);
+				vm.bones.push_back(0); // bone id is pushed on here during sanitization phase
+				vm.bones_ref.push_back(bone_element);
+			}
+
+			for (const int *vertex_index = mesh_vertex_data->vertex_weights.next(nullptr);
+					vertex_index != nullptr;
+					vertex_index = mesh_vertex_data->vertex_weights.next(vertex_index)) {
+				VertexWeightMapping *vm = mesh_vertex_data->vertex_weights.getptr(*vertex_index);
+				const int influence_count = vm->weights.size();
+				if (influence_count > mesh_vertex_data->max_weight_count) {
+					mesh_vertex_data->max_weight_count = influence_count;
+					mesh_vertex_data->valid_weight_count = true;
+				}
+			}
+
+			if (mesh_vertex_data->max_weight_count > 4) {
+				if (mesh_vertex_data->max_weight_count > 8) {
+					ERR_PRINT("[doc] Serious: maximum bone influences is 8 in this branch.");
+				}
+				// Clamp to 8 bone vertex influences.
+				mesh_vertex_data->max_weight_count = 8;
+				print_verbose("[doc] Using 8 vertex bone influences configuration.");
+			} else {
+				mesh_vertex_data->max_weight_count = 4;
+				print_verbose("[doc] Using 4 vertex bone influences configuration.");
+			}
+		}
+	}
+
+	// do we globally allow for import of materials
+	// (prevents overwrite of materials; so you can handle them explicitly)
+	if (state.enable_material_import) {
+		const std::vector<uint64_t> &materials = p_document->GetMaterialIDs();
+
+		for (uint64_t material_id : materials) {
+			FBXDocParser::LazyObject *lazy_material = p_document->GetObject(material_id);
+			FBXDocParser::Material *mat = (FBXDocParser::Material *)lazy_material->Get<FBXDocParser::Material>();
+			ERR_CONTINUE_MSG(!mat, "Could not convert fbx material by id: " + itos(material_id));
+
+			Ref<FBXMaterial> material;
+			material.instance();
+			material->set_imported_material(mat);
+
+			Ref<StandardMaterial3D> godot_material = material->import_material(state);
+
+			state.cached_materials.insert(material_id, godot_material);
+		}
+	}
+
+	// build skin and skeleton information
+	print_verbose("[doc] Skeleton3D Bone count: " + itos(state.fbx_bone_map.size()));
+
+	// Importing bones using document based method from FBX directly
+	// We do not use the assimp bone format to determine this information anymore.
+	if (state.fbx_bone_map.size() > 0) {
+		// We are using a single skeleton only method here
+		// this is because we really have no concept of skeletons in FBX
+		// their are bones in a scene but they have no specific armature
+		// we can detect armatures but the issue lies in the complexity
+		// we opted to merge the entire scene onto one skeleton for now
+		// if we need to change this we have an archive of the old code.
+
+		// bind pose normally only has 1 per mesh but can have more than one
+		// this is the point of skins
+		// in FBX first bind pose is the master for the first skin
+
+		// In order to handle the FBX skeleton we must also inverse any parent transforms on the bones
+		// just to rule out any parent node transforms in the bone data
+		// this is trivial to do and allows us to use the single skeleton method and merge them
+		// this means that the nodes from maya kLocators will be preserved as bones
+		// in the same rig without having to match this across skeletons and merge by detection
+		// we can just merge and undo any parent transforms
+		for (Map<uint64_t, Ref<FBXBone>>::Element *bone_element = state.fbx_bone_map.front(); bone_element; bone_element = bone_element->next()) {
+			Ref<FBXBone> bone = bone_element->value();
+			Ref<FBXSkeleton> fbx_skeleton_inst;
+
+			uint64_t armature_id = bone->armature_id;
+			if (state.skeleton_map.has(armature_id)) {
+				fbx_skeleton_inst = state.skeleton_map[armature_id];
+			} else {
+				fbx_skeleton_inst.instance();
+				state.skeleton_map.insert(armature_id, fbx_skeleton_inst);
+			}
+
+			print_verbose("populating skeleton with bone: " + bone->bone_name);
+
+			//			// populate bone skeleton - since fbx has no DOM for the skeleton just a node.
+			//			bone->bone_skeleton = fbx_skeleton_inst;
+
+			// now populate bone on the armature node list
+			fbx_skeleton_inst->skeleton_bones.push_back(bone);
+
+			CRASH_COND_MSG(!state.fbx_target_map.has(armature_id), "invalid armature [serious]");
+
+			Ref<FBXNode> node = state.fbx_target_map[armature_id];
+
+			CRASH_COND_MSG(node.is_null(), "invalid node [serious]");
+			CRASH_COND_MSG(node->pivot_transform.is_null(), "invalid pivot transform [serious]");
+			fbx_skeleton_inst->fbx_node = node;
+
+			ERR_CONTINUE_MSG(fbx_skeleton_inst->fbx_node.is_null(), "invalid skeleton node [serious]");
+
+			// we need to have a valid armature id and the model configured for the bone to be assigned fully.
+			// happens once per skeleton
+
+			if (state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) {
+				print_verbose("allocated fbx skeleton primary / armature node for the level: " + fbx_skeleton_inst->fbx_node->node_name);
+			} else if (!state.fbx_target_map.has(armature_id) && !fbx_skeleton_inst->fbx_node->has_model()) {
+				print_error("bones are not mapped to an armature node for armature id: " + itos(armature_id) + " bone: " + bone->bone_name);
+				// this means bone will be removed and not used, which is safe actually and no skeleton will be created.
+			}
+		}
+
+		// setup skeleton instances if required :)
+		for (Map<uint64_t, Ref<FBXSkeleton>>::Element *skeleton_node = state.skeleton_map.front(); skeleton_node; skeleton_node = skeleton_node->next()) {
+			Ref<FBXSkeleton> &skeleton = skeleton_node->value();
+			skeleton->init_skeleton(state);
+
+			ERR_CONTINUE_MSG(skeleton->fbx_node.is_null(), "invalid fbx target map, missing skeleton");
+		}
+
+		// This list is not populated
+		for (Map<uint64_t, Ref<FBXNode>>::Element *skin_mesh = state.MeshNodes.front(); skin_mesh; skin_mesh = skin_mesh->next()) {
+		}
+	}
+
+	// build godot node tree
+	if (state.fbx_node_list.size() > 0) {
+		for (List<Ref<FBXNode>>::Element *node_element = state.fbx_node_list.front();
+				node_element;
+				node_element = node_element->next()) {
+			Ref<FBXNode> fbx_node = node_element->get();
+			EditorSceneImporterMeshNode *mesh_node = nullptr;
+			Ref<FBXMeshData> mesh_data_precached;
+
+			// check for valid geometry
+			if (fbx_node->fbx_model == nullptr) {
+				print_error("[doc] fundamental flaw, submit bug immediately with full import log with verbose logging on");
+			} else {
+				const std::vector<const FBXDocParser::Geometry *> &geometry = fbx_node->fbx_model->GetGeometry();
+				for (const FBXDocParser::Geometry *mesh : geometry) {
+					print_verbose("[doc] [" + itos(mesh->ID()) + "] mesh: " + fbx_node->node_name);
+
+					if (mesh == nullptr)
+						continue;
+
+					const FBXDocParser::MeshGeometry *mesh_geometry = dynamic_cast<const FBXDocParser::MeshGeometry *>(mesh);
+					if (mesh_geometry) {
+						uint64_t mesh_id = mesh_geometry->ID();
+
+						// this data will pre-exist if vertex weight information is found
+						if (state.renderer_mesh_data.has(mesh_id)) {
+							mesh_data_precached = state.renderer_mesh_data[mesh_id];
+						} else {
+							mesh_data_precached.instance();
+							state.renderer_mesh_data.insert(mesh_id, mesh_data_precached);
+						}
+
+						mesh_data_precached->mesh_node = fbx_node;
+
+						// mesh node, mesh id
+						mesh_node = mesh_data_precached->create_fbx_mesh(state, mesh_geometry, fbx_node->fbx_model, (p_flags & IMPORT_USE_COMPRESSION) != 0);
+						if (!state.MeshNodes.has(mesh_id)) {
+							state.MeshNodes.insert(mesh_id, fbx_node);
+						}
+					}
+
+					const FBXDocParser::ShapeGeometry *shape_geometry = dynamic_cast<const FBXDocParser::ShapeGeometry *>(mesh);
+					if (shape_geometry != nullptr) {
+						print_verbose("[doc] valid shape geometry converted");
+					}
+				}
+			}
+
+			Ref<FBXSkeleton> node_skeleton = fbx_node->skeleton_node;
+
+			if (node_skeleton.is_valid()) {
+				Skeleton3D *skel = node_skeleton->skeleton;
+				fbx_node->godot_node = skel;
+			} else if (mesh_node == nullptr) {
+				fbx_node->godot_node = memnew(Node3D);
+			} else {
+				fbx_node->godot_node = mesh_node;
+			}
+
+			fbx_node->godot_node->set_name(fbx_node->node_name);
+
+			// assign parent if valid
+			if (fbx_node->fbx_parent.is_valid()) {
+				fbx_node->fbx_parent->godot_node->add_child(fbx_node->godot_node);
+				fbx_node->godot_node->set_owner(state.root_owner);
+			}
+
+			// Node Transform debug, set local xform data.
+			fbx_node->godot_node->set_transform(get_unscaled_transform(fbx_node->pivot_transform->LocalTransform, state.scale));
+
+			// populate our mesh node reference
+			if (mesh_node != nullptr && mesh_data_precached.is_valid()) {
+				mesh_data_precached->godot_mesh_instance = mesh_node;
+			}
+		}
+	}
+
+	for (Map<uint64_t, Ref<FBXMeshData>>::Element *mesh_data = state.renderer_mesh_data.front(); mesh_data; mesh_data = mesh_data->next()) {
+		const uint64_t mesh_id = mesh_data->key();
+		Ref<FBXMeshData> mesh = mesh_data->value();
+
+		const FBXDocParser::MeshGeometry *mesh_geometry = p_document->GetObject(mesh_id)->Get<FBXDocParser::MeshGeometry>();
+
+		ERR_CONTINUE_MSG(mesh->mesh_node.is_null(), "invalid mesh allocation");
+
+		const FBXDocParser::Skin *mesh_skin = mesh_geometry->DeformerSkin();
+
+		if (!mesh_skin) {
+			continue; // safe to continue
+		}
+
+		//
+		// Skin bone configuration
+		//
+
+		//
+		// Get Mesh Node Xform only
+		//
+		//		ERR_CONTINUE_MSG(!state.fbx_target_map.has(mesh_id), "invalid xform for the skin pose: " + itos(mesh_id));
+		//		Ref<FBXNode> mesh_node_xform_data = state.fbx_target_map[mesh_id];
+
+		if (!mesh_skin) {
+			continue; // not a deformer.
+		}
+
+		if (mesh_skin->Clusters().size() == 0) {
+			continue; // possibly buggy mesh
+		}
+
+		// Lookup skin or create it if it's not found.
+		Ref<Skin> skin;
+		if (!state.MeshSkins.has(mesh_id)) {
+			print_verbose("Created new skin");
+			skin.instance();
+			state.MeshSkins.insert(mesh_id, skin);
+		} else {
+			print_verbose("Grabbed skin");
+			skin = state.MeshSkins[mesh_id];
+		}
+
+		for (const FBXDocParser::Cluster *cluster : mesh_skin->Clusters()) {
+			// node or bone this cluster targets (in theory will only be a bone target)
+			uint64_t skin_target_id = cluster->TargetNode()->ID();
+
+			print_verbose("adding cluster [" + itos(cluster->ID()) + "] " + String(cluster->Name().c_str()) + " for target: [" + itos(skin_target_id) + "] " + String(cluster->TargetNode()->Name().c_str()));
+			ERR_CONTINUE_MSG(!state.fbx_bone_map.has(skin_target_id), "no bone found by that ID? locator");
+
+			const Ref<FBXBone> bone = state.fbx_bone_map[skin_target_id];
+			const Ref<FBXSkeleton> skeleton = bone->fbx_skeleton;
+			const Ref<FBXNode> skeleton_node = skeleton->fbx_node;
+
+			skin->add_named_bind(
+					bone->bone_name,
+					get_unscaled_transform(
+							skeleton_node->pivot_transform->GlobalTransform.affine_inverse() * cluster->TransformLink().affine_inverse(), state.scale));
+		}
+
+		print_verbose("cluster name / id: " + String(mesh_skin->Name().c_str()) + " [" + itos(mesh_skin->ID()) + "]");
+		print_verbose("skeleton has " + itos(state.fbx_bone_map.size()) + " binds");
+		print_verbose("fbx skin has " + itos(mesh_skin->Clusters().size()) + " binds");
+	}
+
+	// mesh data iteration for populating skeleton mapping
+	for (Map<uint64_t, Ref<FBXMeshData>>::Element *mesh_data = state.renderer_mesh_data.front(); mesh_data; mesh_data = mesh_data->next()) {
+		Ref<FBXMeshData> mesh = mesh_data->value();
+		const uint64_t mesh_id = mesh_data->key();
+		EditorSceneImporterMeshNode *mesh_instance = mesh->godot_mesh_instance;
+		const int mesh_weights = mesh->max_weight_count;
+		Ref<FBXSkeleton> skeleton;
+		const bool valid_armature = mesh->valid_armature_id;
+		const uint64_t armature = mesh->armature_id;
+
+		if (mesh_weights > 0) {
+			// this is a bug, it means the weights were found but the skeleton wasn't
+			ERR_CONTINUE_MSG(!valid_armature, "[doc] fbx armature is missing");
+		} else {
+			continue; // safe to continue not a bug just a normal mesh
+		}
+
+		if (state.skeleton_map.has(armature)) {
+			skeleton = state.skeleton_map[armature];
+			print_verbose("[doc] armature mesh to skeleton mapping has been allocated");
+		} else {
+			print_error("[doc] unable to find armature mapping");
+		}
+
+		ERR_CONTINUE_MSG(!mesh_instance, "[doc] invalid mesh mapping for skeleton assignment");
+		ERR_CONTINUE_MSG(skeleton.is_null(), "[doc] unable to resolve the correct skeleton but we have weights!");
+
+		mesh_instance->set_skeleton_path(mesh_instance->get_path_to(skeleton->skeleton));
+		print_verbose("[doc] allocated skeleton to mesh " + mesh_instance->get_name());
+
+		// do we have a mesh skin for this mesh
+		ERR_CONTINUE_MSG(!state.MeshSkins.has(mesh_id), "no skin found for mesh");
+
+		Ref<Skin> mesh_skin = state.MeshSkins[mesh_id];
+
+		ERR_CONTINUE_MSG(mesh_skin.is_null(), "invalid skin stored in map");
+		print_verbose("[doc] allocated skin to mesh " + mesh_instance->get_name());
+		mesh_instance->set_skin(mesh_skin);
+	}
+
+	// build skin and skeleton information
+	print_verbose("[doc] Skeleton3D Bone count: " + itos(state.fbx_bone_map.size()));
+	const FBXDocParser::FileGlobalSettings *FBXSettings = p_document->GlobalSettingsPtr();
+
+	// Configure constraints
+	// NOTE: constraints won't be added quite yet, we don't have a real need for them *yet*. (they can be supported later on)
+	// const std::vector<uint64_t> fbx_constraints = p_document->GetConstraintStackIDs();
+
+	// get the animation FPS
+	float fps_setting = ImportUtils::get_fbx_fps(FBXSettings);
+
+	// enable animation import, only if local animation is enabled
+	if (state.enable_animation_import && (p_flags & IMPORT_ANIMATION)) {
+		// document animation stack list - get by ID so we can unload any non used animation stack
+		const std::vector<uint64_t> animation_stack = p_document->GetAnimationStackIDs();
+
+		for (uint64_t anim_id : animation_stack) {
+			FBXDocParser::LazyObject *lazyObject = p_document->GetObject(anim_id);
+			const FBXDocParser::AnimationStack *stack = lazyObject->Get<FBXDocParser::AnimationStack>();
+
+			if (stack != nullptr) {
+				String animation_name = ImportUtils::FBXNodeToName(stack->Name());
+				print_verbose("Valid animation stack has been found: " + animation_name);
+				// ReferenceTime is the same for some animations?
+				// LocalStop time is the start and end time
+				float r_start = CONVERT_FBX_TIME(stack->ReferenceStart());
+				float r_stop = CONVERT_FBX_TIME(stack->ReferenceStop());
+				float start_time = CONVERT_FBX_TIME(stack->LocalStart());
+				float end_time = CONVERT_FBX_TIME(stack->LocalStop());
+				float duration = end_time - start_time;
+
+				print_verbose("r_start " + rtos(r_start) + ", r_stop " + rtos(r_stop));
+				print_verbose("start_time" + rtos(start_time) + " end_time " + rtos(end_time));
+				print_verbose("anim duration : " + rtos(duration));
+
+				// we can safely create the animation player
+				if (state.animation_player == nullptr) {
+					print_verbose("Creating animation player");
+					state.animation_player = memnew(AnimationPlayer);
+					state.root->add_child(state.animation_player);
+					state.animation_player->set_owner(state.root_owner);
+				}
+
+				Ref<Animation> animation;
+				animation.instance();
+				animation->set_name(animation_name);
+				animation->set_length(duration);
+
+				print_verbose("Animation length: " + rtos(animation->get_length()) + " seconds");
+
+				// i think assimp was duplicating things, this lets me know to just reference or ignore this to prevent duplicate information in tracks
+				// this would mean that we would be doing three times as much work per track if my theory is correct.
+				// this was not the case but this is a good sanity check for the animation handler from the document.
+				// it also lets us know if the FBX specification massively changes the animation system, in theory such a change would make this show
+				// an fbx specification error, so best keep it in
+				// the overhead is tiny.
+				Map<uint64_t, const FBXDocParser::AnimationCurve *> CheckForDuplication;
+
+				const std::vector<const FBXDocParser::AnimationLayer *> &layers = stack->Layers();
+				print_verbose("FBX Animation layers: " + itos(layers.size()));
+				for (const FBXDocParser::AnimationLayer *layer : layers) {
+					std::vector<const FBXDocParser::AnimationCurveNode *> node_list = layer->Nodes();
+					print_verbose("Layer: " + ImportUtils::FBXNodeToName(layer->Name()) + ", " + " AnimCurveNode count " + itos(node_list.size()));
+
+					// first thing to do here is that i need to first get the animcurvenode to a Vector3
+					// we now need to put this into the track information for godot.
+					// to do this we need to know which track is what?
+
+					// target id, [ track name, [time index, vector] ]
+					// new map needs to be [ track name, keyframe_data ]
+					Map<uint64_t, Map<StringName, FBXTrack>> AnimCurveNodes;
+
+					// struct AnimTrack {
+					// 	// Animation track can be
+					// 	// visible, T, R, S
+					// 	Map<StringName, Map<uint64_t, Vector3> > animation_track;
+					// };
+
+					// Map<uint64_t, AnimTrack> AnimCurveNodes;
+
+					// so really, what does this mean to make an animtion track.
+					// we need to know what object the curves are for.
+					// we need the target ID and the target name for the track reduction.
+
+					FBXDocParser::Model::RotOrder quat_rotation_order = FBXDocParser::Model::RotOrder_EulerXYZ;
+
+					// T:: R:: S:: Visible:: Custom::
+					for (const FBXDocParser::AnimationCurveNode *curve_node : node_list) {
+						// when Curves() is called the curves are actually read, we could replace this with our own ProcessDomConnection code here if required.
+						// We may need to do this but ideally we use Curves
+						// note: when you call this there might be a delay in opening it
+						// uses mutable type to 'cache' the response until the AnimationCurveNode is cleaned up.
+						std::map<std::string, const FBXDocParser::AnimationCurve *> curves = curve_node->Curves();
+						const FBXDocParser::Object *object = curve_node->Target();
+						const FBXDocParser::Model *target = curve_node->TargetAsModel();
+						if (target == nullptr) {
+							if (object != nullptr) {
+								print_error("[doc] warning failed to find a target Model for curve: " + String(object->Name().c_str()));
+							} else {
+								//print_error("[doc] failed to resolve object");
+								continue;
+							}
+
+							continue;
+						} else {
+							//print_verbose("[doc] applied rotation order: " + itos(target->RotationOrder()));
+							quat_rotation_order = target->RotationOrder();
+						}
+
+						uint64_t target_id = target->ID();
+						String target_name = ImportUtils::FBXNodeToName(target->Name());
+
+						const FBXDocParser::PropertyTable *properties = curve_node->Props();
+						bool got_x = false, got_y = false, got_z = false;
+						float offset_x = FBXDocParser::PropertyGet<float>(properties, "d|X", got_x);
+						float offset_y = FBXDocParser::PropertyGet<float>(properties, "d|Y", got_y);
+						float offset_z = FBXDocParser::PropertyGet<float>(properties, "d|Z", got_z);
+
+						String curve_node_name = ImportUtils::FBXNodeToName(curve_node->Name());
+
+						// Reduce all curves for this node into a single container
+						// T, R, S is what we expect, although other tracks are possible
+						// like for example visibility tracks.
+
+						// We are not ordered here, we don't care about ordering, this happens automagically by godot when we insert with the
+						// key time :), so order is unimportant because the insertion will happen at a time index
+						// good to know: we do not need a list of these in another format :)
+						//Map<String, Vector<const Assimp::FBX::AnimationCurve *> > unordered_track;
+
+						// T
+						// R
+						// S
+						// Map[String, List<VECTOR>]
+
+						// So this is a reduction of the animation curve nodes
+						// We build this as a lookup, this is essentially our 'animation track'
+						//AnimCurveNodes.insert(curve_node_name, Map<uint64_t, Vector3>());
+
+						// create the animation curve information with the target id
+						// so the point of this makes a track with the name "T" for example
+						// the target ID is also set here, this means we don't need to do anything extra when we are in the 'create all animation tracks' step
+						FBXTrack &keyframe_map = AnimCurveNodes[target_id][StringName(curve_node_name)];
+
+						if (got_x && got_y && got_z) {
+							Vector3 default_value = Vector3(offset_x, offset_y, offset_z);
+							keyframe_map.default_value = default_value;
+							keyframe_map.has_default = true;
+							//print_verbose("track name: " + curve_node_name);
+							//print_verbose("xyz default: " + default_value);
+						}
+						// target id, [ track name, [time index, vector] ]
+						// Map<uint64_t, Map<StringName, Map<uint64_t, Vector3> > > AnimCurveNodes;
+
+						// we probably need the target id here.
+						// so map[uint64_t map]...
+						// Map<uint64_t, Vector3D> translation_keys, rotation_keys, scale_keys;
+
+						// extra const required by C++11 colon/Range operator
+						// note: do not use C++17 syntax here for dicts.
+						// this is banned in Godot.
+						for (std::pair<const std::string, const FBXDocParser::AnimationCurve *> &kvp : curves) {
+							const String curve_element = ImportUtils::FBXNodeToName(kvp.first);
+							const FBXDocParser::AnimationCurve *curve = kvp.second;
+							String curve_name = ImportUtils::FBXNodeToName(curve->Name());
+							uint64_t curve_id = curve->ID();
+
+							if (CheckForDuplication.has(curve_id)) {
+								print_error("(FBX spec changed?) We found a duplicate curve being used for an alternative node - report to godot issue tracker");
+							} else {
+								CheckForDuplication.insert(curve_id, curve);
+							}
+
+							// FBX has no name for AnimCurveNode::, most of the time, not seen any with valid name here.
+							const std::map<int64_t, float> &track_time = curve->GetValueTimeTrack();
+
+							if (track_time.size() > 0) {
+								for (std::pair<int64_t, float> keyframe : track_time) {
+									if (curve_element == "d|X") {
+										keyframe_map.keyframes[keyframe.first].x = keyframe.second;
+									} else if (curve_element == "d|Y") {
+										keyframe_map.keyframes[keyframe.first].y = keyframe.second;
+									} else if (curve_element == "d|Z") {
+										keyframe_map.keyframes[keyframe.first].z = keyframe.second;
+									} else {
+										//print_error("FBX Unsupported element: " + curve_element);
+									}
+
+									//print_verbose("[" + itos(target_id) + "] Keyframe added:  " + itos(keyframe_map.size()));
+
+									//print_verbose("Keyframe t:" + rtos(animation_track_time) + " v: " + rtos(keyframe.second));
+								}
+							}
+						}
+					}
+
+					// Map<uint64_t, Map<StringName, Map<uint64_t, Vector3> > > AnimCurveNodes;
+					// add this animation track here
+
+					// target id, [ track name, [time index, vector] ]
+					//std::map<uint64_t, std::map<StringName, FBXTrack > > AnimCurveNodes;
+					for (Map<uint64_t, Map<StringName, FBXTrack>>::Element *track = AnimCurveNodes.front(); track; track = track->next()) {
+						// 5 tracks
+						// current track index
+						// track count is 5
+						// track count is 5.
+						// next track id is 5.
+						const uint64_t target_id = track->key();
+						int track_idx = animation->add_track(Animation::TYPE_TRANSFORM);
+
+						// animation->track_set_path(track_idx, node_path);
+						// animation->track_set_path(track_idx, node_path);
+						Ref<FBXBone> bone;
+
+						// note we must not run the below code if the entry doesn't exist, it will create dummy entries which is very bad.
+						// remember that state.fbx_bone_map[target_id] will create a new entry EVEN if you only read.
+						// this would break node animation targets, so if you change this be warned. :)
+						if (state.fbx_bone_map.has(target_id)) {
+							bone = state.fbx_bone_map[target_id];
+						}
+
+						Transform target_transform;
+
+						if (state.fbx_target_map.has(target_id)) {
+							Ref<FBXNode> node_ref = state.fbx_target_map[target_id];
+							target_transform = node_ref->pivot_transform->GlobalTransform;
+							//print_verbose("[doc] allocated animation node transform");
+						}
+
+						//int size_targets = state.fbx_target_map.size();
+						//print_verbose("Target ID map: " + itos(size_targets));
+						//print_verbose("[doc] debug bone map size: " + itos(state.fbx_bone_map.size()));
+
+						// if this is a skeleton mapped track we can just set the path for the track.
+						// todo: implement node paths here at some
+						if (state.fbx_bone_map.size() > 0 && state.fbx_bone_map.has(target_id)) {
+							if (bone->fbx_skeleton.is_valid() && bone.is_valid()) {
+								Ref<FBXSkeleton> fbx_skeleton = bone->fbx_skeleton;
+								String bone_path = state.root->get_path_to(fbx_skeleton->skeleton);
+								bone_path += ":" + fbx_skeleton->skeleton->get_bone_name(bone->godot_bone_id);
+								print_verbose("[doc] track bone path: " + bone_path);
+								NodePath path = bone_path;
+								animation->track_set_path(track_idx, path);
+							}
+						} else if (state.fbx_target_map.has(target_id)) {
+							//print_verbose("[doc] we have a valid target for a node animation");
+							Ref<FBXNode> target_node = state.fbx_target_map[target_id];
+							if (target_node.is_valid() && target_node->godot_node != nullptr) {
+								String node_path = state.root->get_path_to(target_node->godot_node);
+								NodePath path = node_path;
+								animation->track_set_path(track_idx, path);
+								//print_verbose("[doc] node animation path: " + node_path);
+							}
+						} else {
+							// note: this could actually be unsafe this means we should be careful about continuing here, if we see bizzare effects later we should disable this.
+							// I am not sure if this is unsafe or not, testing will tell us this.
+							print_error("[doc] invalid fbx target detected for this track");
+							continue;
+						}
+
+						// everything in FBX and Maya is a node therefore if this happens something is seriously broken.
+						if (!state.fbx_target_map.has(target_id)) {
+							print_error("unable to resolve this to an FBX object.");
+							continue;
+						}
+
+						Ref<FBXNode> target_node = state.fbx_target_map[target_id];
+						const FBXDocParser::Model *model = target_node->fbx_model;
+						const FBXDocParser::PropertyTable *props = model->Props();
+
+						Map<StringName, FBXTrack> &track_data = track->value();
+						FBXTrack &translation_keys = track_data[StringName("T")];
+						FBXTrack &rotation_keys = track_data[StringName("R")];
+						FBXTrack &scale_keys = track_data[StringName("S")];
+
+						double increment = 1.0f / fps_setting;
+						double time = 0.0f;
+
+						bool last = false;
+
+						Vector<Vector3> pos_values;
+						Vector<float> pos_times;
+						Vector<Vector3> scale_values;
+						Vector<float> scale_times;
+						Vector<Quat> rot_values;
+						Vector<float> rot_times;
+
+						double max_duration = 0;
+						double anim_length = animation->get_length();
+
+						for (std::pair<int64_t, Vector3> position_key : translation_keys.keyframes) {
+							pos_values.push_back(position_key.second * state.scale);
+							double animation_track_time = CONVERT_FBX_TIME(position_key.first);
+
+							if (animation_track_time > max_duration) {
+								max_duration = animation_track_time;
+							}
+
+							//print_verbose("pos keyframe: t:" + rtos(animation_track_time) + " value " + position_key.second);
+							pos_times.push_back(animation_track_time);
+						}
+
+						for (std::pair<int64_t, Vector3> scale_key : scale_keys.keyframes) {
+							scale_values.push_back(scale_key.second);
+							double animation_track_time = CONVERT_FBX_TIME(scale_key.first);
+
+							if (animation_track_time > max_duration) {
+								max_duration = animation_track_time;
+							}
+							//print_verbose("scale keyframe t:" + rtos(animation_track_time));
+							scale_times.push_back(animation_track_time);
+						}
+
+						//
+						// Pre and Post keyframe rotation handler
+						// -- Required because Maya and Autodesk <3 the pain when it comes to implementing animation code! enjoy <3
+
+						bool got_pre = false;
+						bool got_post = false;
+
+						Quat post_rotation;
+						Quat pre_rotation;
+
+						// Rotation matrix
+						const Vector3 &PreRotation = FBXDocParser::PropertyGet<Vector3>(props, "PreRotation", got_pre);
+						const Vector3 &PostRotation = FBXDocParser::PropertyGet<Vector3>(props, "PostRotation", got_post);
+
+						FBXDocParser::Model::RotOrder rot_order = model->RotationOrder();
+						if (got_pre) {
+							pre_rotation = ImportUtils::EulerToQuaternion(rot_order, ImportUtils::deg2rad(PreRotation));
+						}
+						if (got_post) {
+							post_rotation = ImportUtils::EulerToQuaternion(rot_order, ImportUtils::deg2rad(PostRotation));
+						}
+
+						Quat lastQuat = Quat();
+
+						for (std::pair<int64_t, Vector3> rotation_key : rotation_keys.keyframes) {
+							double animation_track_time = CONVERT_FBX_TIME(rotation_key.first);
+
+							//print_verbose("euler rotation key: " + rotation_key.second);
+							Quat rot_key_value = ImportUtils::EulerToQuaternion(quat_rotation_order, ImportUtils::deg2rad(rotation_key.second));
+
+							if (lastQuat != Quat() && rot_key_value.dot(lastQuat) < 0) {
+								rot_key_value.x = -rot_key_value.x;
+								rot_key_value.y = -rot_key_value.y;
+								rot_key_value.z = -rot_key_value.z;
+								rot_key_value.w = -rot_key_value.w;
+							}
+							// pre_post rotation possibly could fix orientation
+							Quat final_rotation = pre_rotation * rot_key_value * post_rotation;
+
+							lastQuat = final_rotation;
+
+							if (animation_track_time > max_duration) {
+								max_duration = animation_track_time;
+							}
+
+							rot_values.push_back(final_rotation);
+							rot_times.push_back(animation_track_time);
+						}
+
+						bool valid_rest = false;
+						Transform bone_rest;
+						int skeleton_bone = -1;
+						if (state.fbx_bone_map.has(target_id)) {
+							if (bone.is_valid() && bone->fbx_skeleton.is_valid()) {
+								skeleton_bone = bone->godot_bone_id;
+								if (skeleton_bone >= 0) {
+									bone_rest = bone->fbx_skeleton->skeleton->get_bone_rest(skeleton_bone);
+									valid_rest = true;
+								}
+							}
+
+							if (!valid_rest) {
+								print_verbose("invalid rest!");
+							}
+						}
+
+						const Vector3 def_pos = translation_keys.has_default ? (translation_keys.default_value * state.scale) : bone_rest.origin;
+						const Quat def_rot = rotation_keys.has_default ? ImportUtils::EulerToQuaternion(quat_rotation_order, ImportUtils::deg2rad(rotation_keys.default_value)) : bone_rest.basis.get_rotation_quat();
+						const Vector3 def_scale = scale_keys.has_default ? scale_keys.default_value : bone_rest.basis.get_scale();
+						print_verbose("track defaults: p(" + def_pos + ") s(" + def_scale + ") r(" + def_rot + ")");
+
+						while (true) {
+							Vector3 pos = def_pos;
+							Quat rot = def_rot;
+							Vector3 scale = def_scale;
+
+							if (pos_values.size()) {
+								pos = _interpolate_track<Vector3>(pos_times, pos_values, time,
+										AssetImportAnimation::INTERP_LINEAR);
+							}
+
+							if (rot_values.size()) {
+								rot = _interpolate_track<Quat>(rot_times, rot_values, time,
+										AssetImportAnimation::INTERP_LINEAR);
+							}
+
+							if (scale_values.size()) {
+								scale = _interpolate_track<Vector3>(scale_times, scale_values, time,
+										AssetImportAnimation::INTERP_LINEAR);
+							}
+
+							// node animations must also include pivots
+							if (skeleton_bone >= 0) {
+								Transform xform = Transform();
+								xform.basis.set_quat_scale(rot, scale);
+								xform.origin = pos;
+								const Transform t = bone_rest.affine_inverse() * xform;
+
+								// populate	this again
+								rot = t.basis.get_rotation_quat();
+								rot.normalize();
+								scale = t.basis.get_scale();
+								pos = t.origin;
+							}
+
+							animation->transform_track_insert_key(track_idx, time, pos, rot, scale);
+
+							if (last) {
+								break;
+							}
+
+							time += increment;
+							if (time > anim_length) {
+								last = true;
+								time = anim_length;
+								break;
+							}
+						}
+					}
+				}
+				state.animation_player->add_animation(animation_name, animation);
+			}
+		}
+
+		// AnimStack elements contain start stop time and name of animation
+		// AnimLayer is the current active layer of the animation (multiple layers can be active we only support 1)
+		// AnimCurveNode has a OP link back to the model which is the real node.
+		// AnimCurveNode has a direct link to AnimationCurve (of which it may have more than one)
+
+		// Store animation stack in list
+		// iterate over all AnimStacks like the cache node algorithm recursively
+		// this can then be used with ProcessDomConnection<> to link from
+		// AnimStack:: <-- (OO) --> AnimLayer:: <-- (OO) --> AnimCurveNode:: (which can OP resolve) to Model::
+	}
+
+	//
+	// Cleanup operations - explicit to prevent errors on shutdown - found that ref to ref does behave badly sometimes.
+	//
+
+	state.renderer_mesh_data.clear();
+	state.MeshSkins.clear();
+	state.fbx_target_map.clear();
+	state.fbx_node_list.clear();
+
+	for (Map<uint64_t, Ref<FBXBone>>::Element *element = state.fbx_bone_map.front(); element; element = element->next()) {
+		Ref<FBXBone> bone = element->value();
+		bone->parent_bone.unref();
+		bone->node.unref();
+		bone->fbx_skeleton.unref();
+	}
+
+	for (Map<uint64_t, Ref<FBXSkeleton>>::Element *element = state.skeleton_map.front(); element; element = element->next()) {
+		Ref<FBXSkeleton> skel = element->value();
+		skel->fbx_node.unref();
+		skel->skeleton_bones.clear();
+	}
+
+	state.fbx_bone_map.clear();
+	state.skeleton_map.clear();
+	state.fbx_root_node.unref();
+
+	return scene_root;
+}
+
+void EditorSceneImporterFBX::BuildDocumentBones(Ref<FBXBone> p_parent_bone,
+		ImportState &state, const FBXDocParser::Document *p_doc,
+		uint64_t p_id) {
+	const std::vector<const FBXDocParser::Connection *> &conns = p_doc->GetConnectionsByDestinationSequenced(p_id, "Model");
+	// FBX can do an join like this
+	// Model -> SubDeformer (bone) -> Deformer (skin pose)
+	// This is important because we need to somehow link skin back to bone id in skeleton :)
+	// The rules are:
+	// A subdeformer will exist if 'limbnode' class tag present
+	// The subdeformer will not necessarily have a deformer as joints do not have one
+	for (const FBXDocParser::Connection *con : conns) {
+		// goto: bone creation
+		//print_verbose("con: " + String(con->PropertyName().c_str()));
+
+		// ignore object-property links we want the object to object links nothing else
+		if (con->PropertyName().length()) {
+			continue;
+		}
+
+		// convert connection source object into Object base class
+		const FBXDocParser::Object *const object = con->SourceObject();
+
+		if (nullptr == object) {
+			print_verbose("failed to convert source object for Model link");
+			continue;
+		}
+
+		// FBX Model::Cube, Model::Bone001, etc elements
+		// This detects if we can cast the object into this model structure.
+		const FBXDocParser::Model *const model = dynamic_cast<const FBXDocParser::Model *>(object);
+
+		// declare our bone element reference (invalid, unless we create a bone in this step)
+		// this lets us pass valid armature information into children objects and this is why we moved this up here
+		// previously this was created .instanced() on the same line.
+		Ref<FBXBone> bone_element;
+
+		if (model != nullptr) {
+			// model marked with limb node / casted.
+			const FBXDocParser::ModelLimbNode *const limb_node = dynamic_cast<const FBXDocParser::ModelLimbNode *>(model);
+			if (limb_node != nullptr) {
+				// Write bone into bone list for FBX
+
+				ERR_FAIL_COND_MSG(state.fbx_bone_map.has(limb_node->ID()), "[serious] duplicate LimbNode detected");
+
+				bool parent_is_bone = state.fbx_bone_map.find(p_id);
+				bone_element.instance();
+
+				// used to build the bone hierarchy in the skeleton
+				bone_element->parent_bone_id = parent_is_bone ? p_id : 0;
+				bone_element->valid_parent = parent_is_bone;
+				bone_element->limb_node = limb_node;
+
+				// parent is a node and this is the first bone
+				if (!parent_is_bone) {
+					uint64_t armature_id = p_id;
+					bone_element->valid_armature_id = true;
+					bone_element->armature_id = armature_id;
+					print_verbose("[doc] valid armature has been configured for first child: " + itos(armature_id));
+				} else if (p_parent_bone.is_valid()) {
+					if (p_parent_bone->valid_armature_id) {
+						bone_element->valid_armature_id = true;
+						bone_element->armature_id = p_parent_bone->armature_id;
+						print_verbose("[doc] bone has valid armature id:" + itos(bone_element->armature_id));
+					} else {
+						print_error("[doc] unassigned armature id: " + String(limb_node->Name().c_str()));
+					}
+				} else {
+					print_error("[doc] error is this a bone? " + String(limb_node->Name().c_str()));
+				}
+
+				if (!parent_is_bone) {
+					print_verbose("[doc] Root bone: " + bone_element->bone_name);
+				}
+
+				uint64_t limb_id = limb_node->ID();
+				bone_element->bone_id = limb_id;
+				bone_element->bone_name = ImportUtils::FBXNodeToName(model->Name());
+				bone_element->parent_bone = p_parent_bone;
+
+				// insert limb by ID into list.
+				state.fbx_bone_map.insert(limb_node->ID(), bone_element);
+			}
+
+			// recursion call - child nodes
+			BuildDocumentBones(bone_element, state, p_doc, model->ID());
+		}
+	}
+}
+
+void EditorSceneImporterFBX::BuildDocumentNodes(
+		Ref<PivotTransform> parent_transform,
+		ImportState &state,
+		const FBXDocParser::Document *p_doc,
+		uint64_t id,
+		Ref<FBXNode> parent_node) {
+	// tree
+	// here we get the node 0 on the root by default
+	const std::vector<const FBXDocParser::Connection *> &conns = p_doc->GetConnectionsByDestinationSequenced(id, "Model");
+
+	// branch
+	for (const FBXDocParser::Connection *con : conns) {
+		// ignore object-property links
+		if (con->PropertyName().length()) {
+			// really important we document why this is ignored.
+			print_verbose("ignoring property link - no docs on why this is ignored");
+			continue;
+		}
+
+		// convert connection source object into Object base class
+		// Source objects can exist with 'null connections' this means that we only for sure know the source exists.
+		const FBXDocParser::Object *const source_object = con->SourceObject();
+
+		if (nullptr == source_object) {
+			print_verbose("failed to convert source object for Model link");
+			continue;
+		}
+
+		// FBX Model::Cube, Model::Bone001, etc elements
+		// This detects if we can cast the object into this model structure.
+		const FBXDocParser::Model *const model = dynamic_cast<const FBXDocParser::Model *>(source_object);
+		// model is the current node
+		if (nullptr != model) {
+			uint64_t current_node_id = model->ID();
+
+			Ref<FBXNode> new_node;
+			new_node.instance();
+			new_node->current_node_id = current_node_id;
+			new_node->node_name = ImportUtils::FBXNodeToName(model->Name());
+
+			Ref<PivotTransform> fbx_transform;
+			fbx_transform.instance();
+			fbx_transform->set_parent(parent_transform);
+			fbx_transform->set_model(model);
+			fbx_transform->debug_pivot_xform("name: " + new_node->node_name);
+			fbx_transform->Execute();
+
+			new_node->set_pivot_transform(fbx_transform);
+
+			// check if this node is a bone
+			if (state.fbx_bone_map.has(current_node_id)) {
+				Ref<FBXBone> bone = state.fbx_bone_map[current_node_id];
+				if (bone.is_valid()) {
+					bone->set_node(new_node);
+					print_verbose("allocated bone data: " + bone->bone_name);
+				}
+			}
+
+			// set the model, we can't just assign this safely
+			new_node->set_model(model);
+
+			if (parent_node.is_valid()) {
+				new_node->set_parent(parent_node);
+			} else {
+				new_node->set_parent(state.fbx_root_node);
+			}
+
+			CRASH_COND_MSG(new_node->pivot_transform.is_null(), "invalid fbx target map pivot transform [serious]");
+
+			// populate lookup tables with references
+			// [fbx_node_id, fbx_node]
+
+			state.fbx_node_list.push_back(new_node);
+			if (!state.fbx_target_map.has(new_node->current_node_id)) {
+				state.fbx_target_map[new_node->current_node_id] = new_node;
+			}
+
+			// print node name
+			print_verbose("[doc] new node " + new_node->node_name);
+
+			// sub branches
+			BuildDocumentNodes(new_node->pivot_transform, state, p_doc, current_node_id, new_node);
+		}
+	}
+}

+ 133 - 0
modules/fbx/editor_scene_importer_fbx.h

@@ -0,0 +1,133 @@
+/*************************************************************************/
+/*  editor_scene_importer_fbx.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 EDITOR_SCENE_IMPORTER_FBX_H
+#define EDITOR_SCENE_IMPORTER_FBX_H
+
+#ifdef TOOLS_ENABLED
+
+#include "data/import_state.h"
+#include "tools/import_utils.h"
+
+#include "core/core_bind.h"
+#include "core/io/resource_importer.h"
+#include "core/string/ustring.h"
+#include "core/templates/local_vector.h"
+#include "core/templates/vector.h"
+#include "core/variant/dictionary.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 "fbx_parser/FBXDocument.h"
+#include "fbx_parser/FBXImportSettings.h"
+#include "fbx_parser/FBXMeshGeometry.h"
+#include "fbx_parser/FBXUtil.h"
+
+#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL
+
+class EditorSceneImporterFBX : public EditorSceneImporter {
+private:
+	GDCLASS(EditorSceneImporterFBX, EditorSceneImporter);
+
+	struct AssetImportAnimation {
+		enum Interpolation {
+			INTERP_LINEAR,
+			INTERP_STEP,
+			INTERP_CATMULLROMSPLINE,
+			INTERP_CUBIC_SPLINE
+		};
+	};
+
+	// ------------------------------------------------------------------------------------------------
+	template <typename T>
+	const T *ProcessDOMConnection(
+			const FBXDocParser::Document *doc,
+			uint64_t current_element,
+			bool reverse_lookup = false) {
+		const std::vector<const FBXDocParser::Connection *> &conns = reverse_lookup ? doc->GetConnectionsByDestinationSequenced(current_element) : doc->GetConnectionsBySourceSequenced(current_element);
+		//print_verbose("[doc] looking for " + String(element_to_find));
+		// using the temp pattern here so we can debug before it returns
+		// in some cases we return too early, with 'deformer object base class' in wrong place
+		// in assimp this means we can accidentally return too early...
+		const T *return_obj = nullptr;
+
+		for (const FBXDocParser::Connection *con : conns) {
+			const FBXDocParser::Object *source_object = con->SourceObject();
+			const FBXDocParser::Object *dest_object = con->DestinationObject();
+			if (source_object && dest_object != nullptr) {
+				//print_verbose("[doc] connection name: " + String(source_object->Name().c_str()) + ", dest: " + String(dest_object->Name().c_str()));
+				const T *temp = dynamic_cast<const T *>(reverse_lookup ? source_object : dest_object);
+				if (temp) {
+					return_obj = temp;
+				}
+			}
+		}
+
+		if (return_obj != nullptr) {
+			//print_verbose("[doc] returned valid element");
+			//print_verbose("Found object for bone");
+			return return_obj;
+		}
+
+		// safe to return nothing, need to use nullptr here as nullptr is used internally for FBX document.
+		return nullptr;
+	}
+
+	void BuildDocumentBones(Ref<FBXBone> p_parent_bone,
+			ImportState &state, const FBXDocParser::Document *p_doc,
+			uint64_t p_id);
+
+	void BuildDocumentNodes(Ref<PivotTransform> parent_transform, ImportState &state, const FBXDocParser::Document *doc, uint64_t id, Ref<FBXNode> fbx_parent);
+
+	Node3D *_generate_scene(const String &p_path, const FBXDocParser::Document *p_document,
+			const uint32_t p_flags,
+			int p_bake_fps, const int32_t p_max_bone_weights);
+
+	template <class T>
+	T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp);
+	void _register_project_setting_import(const String generic, const String import_setting_string, const Vector<String> &exts, List<String> *r_extensions, const bool p_enabled) const;
+
+public:
+	EditorSceneImporterFBX() {}
+	~EditorSceneImporterFBX() {}
+
+	virtual void get_extensions(List<String> *r_extensions) const override;
+	virtual uint32_t get_import_flags() const override;
+	virtual Node3D *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = NULL) override;
+};
+
+#endif // TOOLS_ENABLED
+#endif // EDITOR_SCENE_IMPORTER_FBX_H

+ 282 - 0
modules/fbx/fbx_parser/ByteSwapper.h

@@ -0,0 +1,282 @@
+/*************************************************************************/
+/*  ByteSwapper.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file Helper class tp perform various byte oder swappings
+   (e.g. little to big endian) */
+#ifndef BYTE_SWAPPER_H
+#define BYTE_SWAPPER_H
+
+#include <stdint.h>
+#include <algorithm>
+#include <locale>
+
+namespace FBXDocParser {
+// --------------------------------------------------------------------------------------
+/** Defines some useful byte order swap routines.
+ *
+ * This is required to read big-endian model formats on little-endian machines,
+ * and vice versa. Direct use of this class is DEPRECATED. Use #StreamReader instead. */
+// --------------------------------------------------------------------------------------
+class ByteSwap {
+	ByteSwap() {}
+
+public:
+	// ----------------------------------------------------------------------
+	/** Swap two bytes of data
+	 *  @param[inout] _szOut A void* to save the reintcasts for the caller. */
+	static inline void Swap2(void *_szOut) {
+		uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut);
+		std::swap(szOut[0], szOut[1]);
+	}
+
+	// ----------------------------------------------------------------------
+	/** Swap four bytes of data
+	 *  @param[inout] _szOut A void* to save the reintcasts for the caller. */
+	static inline void Swap4(void *_szOut) {
+		uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut);
+		std::swap(szOut[0], szOut[3]);
+		std::swap(szOut[1], szOut[2]);
+	}
+
+	// ----------------------------------------------------------------------
+	/** Swap eight bytes of data
+	 *  @param[inout] _szOut A void* to save the reintcasts for the caller. */
+	static inline void Swap8(void *_szOut) {
+		uint8_t *const szOut = reinterpret_cast<uint8_t *>(_szOut);
+		std::swap(szOut[0], szOut[7]);
+		std::swap(szOut[1], szOut[6]);
+		std::swap(szOut[2], szOut[5]);
+		std::swap(szOut[3], szOut[4]);
+	}
+
+	// ----------------------------------------------------------------------
+	/** ByteSwap a float. Not a joke.
+	 *  @param[inout] fOut ehm. .. */
+	static inline void Swap(float *fOut) {
+		Swap4(fOut);
+	}
+
+	// ----------------------------------------------------------------------
+	/** ByteSwap a double. Not a joke.
+	 *  @param[inout] fOut ehm. .. */
+	static inline void Swap(double *fOut) {
+		Swap8(fOut);
+	}
+
+	// ----------------------------------------------------------------------
+	/** ByteSwap an int16t. Not a joke.
+	 *  @param[inout] fOut ehm. .. */
+	static inline void Swap(int16_t *fOut) {
+		Swap2(fOut);
+	}
+
+	static inline void Swap(uint16_t *fOut) {
+		Swap2(fOut);
+	}
+
+	// ----------------------------------------------------------------------
+	/** ByteSwap an int32t. Not a joke.
+	 *  @param[inout] fOut ehm. .. */
+	static inline void Swap(int32_t *fOut) {
+		Swap4(fOut);
+	}
+
+	static inline void Swap(uint32_t *fOut) {
+		Swap4(fOut);
+	}
+
+	// ----------------------------------------------------------------------
+	/** ByteSwap an int64t. Not a joke.
+	 *  @param[inout] fOut ehm. .. */
+	static inline void Swap(int64_t *fOut) {
+		Swap8(fOut);
+	}
+
+	static inline void Swap(uint64_t *fOut) {
+		Swap8(fOut);
+	}
+
+	// ----------------------------------------------------------------------
+	//! Templatized ByteSwap
+	//! \returns param tOut as swapped
+	template <typename Type>
+	static inline Type Swapped(Type tOut) {
+		return _swapper<Type, sizeof(Type)>()(tOut);
+	}
+
+private:
+	template <typename T, size_t size>
+	struct _swapper;
+};
+
+template <typename T>
+struct ByteSwap::_swapper<T, 2> {
+	T operator()(T tOut) {
+		Swap2(&tOut);
+		return tOut;
+	}
+};
+
+template <typename T>
+struct ByteSwap::_swapper<T, 4> {
+	T operator()(T tOut) {
+		Swap4(&tOut);
+		return tOut;
+	}
+};
+
+template <typename T>
+struct ByteSwap::_swapper<T, 8> {
+	T operator()(T tOut) {
+		Swap8(&tOut);
+		return tOut;
+	}
+};
+
+// --------------------------------------------------------------------------------------
+// ByteSwap macros for BigEndian/LittleEndian support
+// --------------------------------------------------------------------------------------
+#if (defined AI_BUILD_BIG_ENDIAN)
+#define AI_LE(t) (t)
+#define AI_BE(t) ByteSwap::Swapped(t)
+#define AI_LSWAP2(p)
+#define AI_LSWAP4(p)
+#define AI_LSWAP8(p)
+#define AI_LSWAP2P(p)
+#define AI_LSWAP4P(p)
+#define AI_LSWAP8P(p)
+#define LE_NCONST const
+#define AI_SWAP2(p) ByteSwap::Swap2(&(p))
+#define AI_SWAP4(p) ByteSwap::Swap4(&(p))
+#define AI_SWAP8(p) ByteSwap::Swap8(&(p))
+#define AI_SWAP2P(p) ByteSwap::Swap2((p))
+#define AI_SWAP4P(p) ByteSwap::Swap4((p))
+#define AI_SWAP8P(p) ByteSwap::Swap8((p))
+#define BE_NCONST
+#else
+#define AI_BE(t) (t)
+#define AI_LE(t) ByteSwap::Swapped(t)
+#define AI_SWAP2(p)
+#define AI_SWAP4(p)
+#define AI_SWAP8(p)
+#define AI_SWAP2P(p)
+#define AI_SWAP4P(p)
+#define AI_SWAP8P(p)
+#define BE_NCONST const
+#define AI_LSWAP2(p) ByteSwap::Swap2(&(p))
+#define AI_LSWAP4(p) ByteSwap::Swap4(&(p))
+#define AI_LSWAP8(p) ByteSwap::Swap8(&(p))
+#define AI_LSWAP2P(p) ByteSwap::Swap2((p))
+#define AI_LSWAP4P(p) ByteSwap::Swap4((p))
+#define AI_LSWAP8P(p) ByteSwap::Swap8((p))
+#define LE_NCONST
+#endif
+
+namespace Intern {
+
+// --------------------------------------------------------------------------------------------
+template <typename T, bool doit>
+struct ByteSwapper {
+	void operator()(T *inout) {
+		ByteSwap::Swap(inout);
+	}
+};
+
+template <typename T>
+struct ByteSwapper<T, false> {
+	void operator()(T *) {
+	}
+};
+
+// --------------------------------------------------------------------------------------------
+template <bool SwapEndianess, typename T, bool RuntimeSwitch>
+struct Getter {
+	void operator()(T *inout, bool le) {
+		le = !le;
+		if (le) {
+			ByteSwapper<T, (sizeof(T) > 1 ? true : false)>()(inout);
+		} else
+			ByteSwapper<T, false>()(inout);
+	}
+};
+
+template <bool SwapEndianess, typename T>
+struct Getter<SwapEndianess, T, false> {
+	void operator()(T *inout, bool /*le*/) {
+		// static branch
+		ByteSwapper<T, (SwapEndianess && sizeof(T) > 1)>()(inout);
+	}
+};
+} // namespace Intern
+} // namespace FBXDocParser
+
+#endif // BYTE_SWAPPER_H

+ 183 - 0
modules/fbx/fbx_parser/CREDITS

@@ -0,0 +1,183 @@
+===============================================================
+Open Asset Import Library (Assimp)
+Developers and Contributors
+===============================================================
+
+The following is a non-exhaustive list of all constributors over the years.
+If you think your name should be listed here, drop us a line and we'll add you.
+
+- Alexander Gessler,
+3DS-, BLEND-, ASE-, DXF-, HMP-, MDL-, MD2-, MD3-, MD5-, MDC-, NFF-, PLY-, STL-, RAW-, OFF-, MS3D-, Q3D- and LWO-Loader, Assimp-Viewer, assimp-cmd, -noboost, Website (Design).
+
+- Thomas Schulze,
+X-, Collada-, BVH-Loader, Postprocessing framework. Data structure & Interface design, documentation.
+
+- Kim Kulling,
+Obj-, Q3BSD-, OpenGEX-Loader, Logging system, CMake-build-environment, Linux-build, Website ( Admin ), Coverity ( Admin ), Glitter ( Admin ).
+
+- R.Schmidt,
+Linux build, eclipse support.
+
+- Matthias Gubisch,
+Assimp.net
+Visual Studio 9 support, bugfixes.
+
+- Mark Sibly
+B3D-Loader, Assimp testing
+
+- Jonathan Klein
+Ogre Loader, VC2010 fixes and CMake fixes.
+
+- Sebastian Hempel,
+PyAssimp (first version)
+Compile-Bugfixes for mingw, add environment for static library support in make.
+
+- Jonathan Pokrass
+Supplied a bugfix concerning the scaling in the md3 loader.
+
+- Andrew Galante,
+Submitted patches to make Assimp compile with GCC-4, a makefile and the xcode3 workspace.
+
+- Andreas Nagel
+First Assimp testing & verification under Windows Vista 64 Bit.
+
+- Marius Schr�der
+Allowed us to use many of his models for screenshots and testing.
+
+- Christian Schubert
+Supplied various XFiles for testing purposes.
+
+- Tizian Wieland
+Searched the web for hundreds of test models for internal use
+
+- John Connors
+Supplied patches for linux and SCons.
+
+- T. R.
+The GUY who performed some of the CSM mocaps.
+
+- Andy Maloney
+Contributed fixes for the documentation and the doxygen markup
+
+- Zhao Lei
+Contributed several bugfixes fixing memory leaks and improving float parsing
+
+- sueastside
+Updated PyAssimp to the latest Assimp data structures and provided a script to keep the Python binding up-to-date.
+
+- Tobias Rittig
+Collada testing with Cinema 4D
+
+- Brad Grantham
+Improvements in OpenGL-Sample.
+
+- Robert Ramirez
+Add group loading feature to Obj-Loader.
+
+- Chris Maiwald
+Many bugreports, improving Assimp's portability, regular testing & feedback.
+
+- Stepan Hrbek
+Bugreport and fix for a obj-materialloader crash.
+
+- David Nadlinger
+D bindings, CMake install support.
+
+- Dario Accornero
+Contributed several patches regarding Mac OS/XCode targets, bug reports.
+
+- Martin Walser (Samhayne)
+Contributed the 'SimpleTexturedOpenGl' sample.
+
+- Matthias Fauconneau
+Contributed a fix for the Q3-BSP loader.
+
+- Jørgen P. Tjernø
+Contributed updated and improved xcode workspaces
+
+- drparallax
+Contributed the /samples/SimpleAssimpViewX sample
+
+- Carsten Fuchs
+Contributed a fix for the Normalize method in aiQuaternion.
+
+- dbburgess
+Contributes a Android-specific build issue: log the hardware architecture for ARM.
+
+- alfiereinre7
+Contributes a obj-fileparser fix: missing tokens in the obj-token list.
+
+- Roman Kharitonov
+Contributes a fix for the configure script environment.
+
+- Ed Diana
+Contributed AssimpDelphi (/port/AssimpDelphi).
+
+- rdb
+Contributes a bundle of fixes and improvements for the bsp-importer.
+
+- Mick P
+For contributing the De-bone postprocessing step and filing various bug reports.
+
+- Rosen Diankov
+Contributed patches to build assimp debian packages using cmake.
+
+- Mark Page
+Contributed a patch to fix the VertexTriangleAdjacency postprocessing step.
+
+- IOhannes
+Contributed the Debian build fixes ( architecture macro ).
+
+- gellule
+Several LWO and LWS fixes (pivoting).
+
+- Marcel Metz
+GCC/Linux fixes for the SimpleOpenGL sample.
+
+- Brian Miller
+Bugfix for a compiler fix for iOS on arm.
+
+- Séverin Lemaignan
+Rewrite of PyAssimp, distutils and Python3 support
+
+- albert-wang
+Bugfixes for the collada parser
+
+- Ya ping Jin
+Bugfixes for uv-tanget calculation.
+
+- Jonne Nauha
+Ogre Binary format support
+
+- Filip Wasil, Tieto Poland Sp. z o.o.
+Android JNI asset extraction support
+
+- Richard Steffen
+Contributed ExportProperties interface
+Contributed X File exporter
+Contributed Step (stp) exporter
+
+- Thomas Iorns (mesilliac)
+Initial FBX Export support
+
+For a more detailed list just check: https://github.com/assimp/assimp/network/members
+
+
+========
+Patreons
+========
+
+Huge thanks to our Patreons!
+
+- migenius
+- Marcus
+- Cort
+- elect
+- Steffen
+
+
+===================
+Commercial Sponsors
+===================
+
+- MyDidimo (mydidimo.com): Sponsored development of FBX Export support

+ 290 - 0
modules/fbx/fbx_parser/FBXAnimation.cpp

@@ -0,0 +1,290 @@
+/*************************************************************************/
+/*  FBXAnimation.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXAnimation.cpp
+ *  @brief Assimp::FBX::AnimationCurve, Assimp::FBX::AnimationCurveNode,
+ *         Assimp::FBX::AnimationLayer, Assimp::FBX::AnimationStack
+ */
+
+#include "FBXCommon.h"
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXParser.h"
+
+namespace FBXDocParser {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurve::AnimationCurve(uint64_t id, const ElementPtr element, const std::string &name, const Document & /*doc*/) :
+		Object(id, element, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+	const ElementPtr KeyTime = GetRequiredElement(sc, "KeyTime");
+	const ElementPtr KeyValueFloat = GetRequiredElement(sc, "KeyValueFloat");
+
+	// note preserved keys and values for legacy FBXConverter.cpp
+	// we can remove this once the animation system is written
+	// and clean up this code so we do not have copies everywhere.
+	ParseVectorDataArray(keys, KeyTime);
+	ParseVectorDataArray(values, KeyValueFloat);
+
+	if (keys.size() != values.size()) {
+		DOMError("the number of key times does not match the number of keyframe values", KeyTime);
+	}
+
+	// put the two lists into the map, underlying container is really just a dictionary
+	// these will always match, if not an error will throw and the file will not import
+	// this is useful because we then can report something and fix this later if it becomes an issue
+	// at this point we do not need a different count of these elements so this makes the
+	// most sense to do.
+	for (size_t x = 0; x < keys.size(); x++) {
+		keyvalues[keys[x]] = values[x];
+	}
+
+	const ElementPtr KeyAttrDataFloat = sc->GetElement("KeyAttrDataFloat");
+	if (KeyAttrDataFloat) {
+		ParseVectorDataArray(attributes, KeyAttrDataFloat);
+	}
+
+	const ElementPtr KeyAttrFlags = sc->GetElement("KeyAttrFlags");
+	if (KeyAttrFlags) {
+		ParseVectorDataArray(flags, KeyAttrFlags);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurve::~AnimationCurve() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurveNode::AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name,
+		const Document &doc, const char *const *target_prop_whitelist /*= NULL*/,
+		size_t whitelist_size /*= 0*/) :
+		Object(id, element, name), target(), doc(doc) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	// find target node
+	const char *whitelist[] = { "Model", "NodeAttribute", "Deformer" };
+	const std::vector<const Connection *> &conns = doc.GetConnectionsBySourceSequenced(ID(), whitelist, 3);
+
+	for (const Connection *con : conns) {
+		// link should go for a property
+		if (!con->PropertyName().length()) {
+			continue;
+		}
+
+		Object *object = con->DestinationObject();
+
+		if (!object) {
+			DOMWarning("failed to read destination object for AnimationCurveNode->Model link, ignoring", element);
+			continue;
+		}
+
+		target = object;
+		prop = con->PropertyName();
+		break;
+	}
+
+	props = GetPropertyTable(doc, "AnimationCurveNode.FbxAnimCurveNode", element, sc, false);
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurveNode::~AnimationCurveNode() {
+	curves.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+const AnimationMap &AnimationCurveNode::Curves() const {
+	/* Lazy loaded animation curves, will only load if required */
+	if (curves.empty()) {
+		// resolve attached animation curves
+		const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurve");
+
+		for (const Connection *con : conns) {
+			// So the advantage of having this STL boilerplate is that it's dead simple once you get it.
+			// The other advantage is casting is guaranteed to be safe and nullptr will be returned in the last step if it fails.
+			Object *ob = con->SourceObject();
+			AnimationCurve *anim_curve = dynamic_cast<AnimationCurve *>(ob);
+			ERR_CONTINUE_MSG(!anim_curve, "Failed to convert animation curve from object");
+
+			curves.insert(std::make_pair(con->PropertyName(), anim_curve));
+		}
+	}
+
+	return curves;
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationLayer::AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
+		Object(id, element, name), doc(doc) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	// note: the props table here bears little importance and is usually absent
+	props = GetPropertyTable(doc, "AnimationLayer.FbxAnimLayer", element, sc, true);
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationLayer::~AnimationLayer() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+const AnimationCurveNodeList AnimationLayer::Nodes(const char *const *target_prop_whitelist,
+		size_t whitelist_size /*= 0*/) const {
+	AnimationCurveNodeList nodes;
+
+	// resolve attached animation nodes
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationCurveNode");
+	nodes.reserve(conns.size());
+
+	for (const Connection *con : conns) {
+		// link should not go to a property
+		if (con->PropertyName().length()) {
+			continue;
+		}
+
+		Object *ob = con->SourceObject();
+
+		if (!ob) {
+			DOMWarning("failed to read source object for AnimationCurveNode->AnimationLayer link, ignoring", element);
+			continue;
+		}
+
+		const AnimationCurveNode *anim = dynamic_cast<AnimationCurveNode *>(ob);
+		if (!anim) {
+			DOMWarning("source object for ->AnimationLayer link is not an AnimationCurveNode", element);
+			continue;
+		}
+
+		if (target_prop_whitelist) {
+			const char *s = anim->TargetProperty().c_str();
+			bool ok = false;
+			for (size_t i = 0; i < whitelist_size; ++i) {
+				if (!strcmp(s, target_prop_whitelist[i])) {
+					ok = true;
+					break;
+				}
+			}
+			if (!ok) {
+				continue;
+			}
+		}
+		nodes.push_back(anim);
+	}
+
+	return nodes;
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationStack::AnimationStack(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
+		Object(id, element, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	// note: we don't currently use any of these properties so we shouldn't bother if it is missing
+	props = GetPropertyTable(doc, "AnimationStack.FbxAnimStack", element, sc, true);
+
+	// resolve attached animation layers
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "AnimationLayer");
+	layers.reserve(conns.size());
+
+	for (const Connection *con : conns) {
+		// link should not go to a property
+		if (con->PropertyName().length()) {
+			continue;
+		}
+
+		Object *ob = con->SourceObject();
+		if (!ob) {
+			DOMWarning("failed to read source object for AnimationLayer->AnimationStack link, ignoring", element);
+			continue;
+		}
+
+		const AnimationLayer *anim = dynamic_cast<const AnimationLayer *>(ob);
+
+		if (!anim) {
+			DOMWarning("source object for ->AnimationStack link is not an AnimationLayer", element);
+			continue;
+		}
+
+		layers.push_back(anim);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationStack::~AnimationStack() {
+	if (props != nullptr) {
+		delete props;
+		props = nullptr;
+	}
+}
+} // namespace FBXDocParser

+ 467 - 0
modules/fbx/fbx_parser/FBXBinaryTokenizer.cpp

@@ -0,0 +1,467 @@
+/*************************************************************************/
+/*  FBXBinaryTokenizer.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+/** @file  FBXBinaryTokenizer.cpp
+ *  @brief Implementation of a fake lexer for binary fbx files -
+ *    we emit tokens so the parser needs almost no special handling
+ *    for binary files.
+ */
+
+#include "ByteSwapper.h"
+#include "FBXTokenizer.h"
+#include "core/string/print_string.h"
+
+#include <stdint.h>
+
+namespace FBXDocParser {
+//enum Flag
+//{
+//   e_unknown_0 = 1 << 0,
+//   e_unknown_1 = 1 << 1,
+//   e_unknown_2 = 1 << 2,
+//   e_unknown_3 = 1 << 3,
+//   e_unknown_4 = 1 << 4,
+//   e_unknown_5 = 1 << 5,
+//   e_unknown_6 = 1 << 6,
+//   e_unknown_7 = 1 << 7,
+//   e_unknown_8 = 1 << 8,
+//   e_unknown_9 = 1 << 9,
+//   e_unknown_10 = 1 << 10,
+//   e_unknown_11 = 1 << 11,
+//   e_unknown_12 = 1 << 12,
+//   e_unknown_13 = 1 << 13,
+//   e_unknown_14 = 1 << 14,
+//   e_unknown_15 = 1 << 15,
+//   e_unknown_16 = 1 << 16,
+//   e_unknown_17 = 1 << 17,
+//   e_unknown_18 = 1 << 18,
+//   e_unknown_19 = 1 << 19,
+//   e_unknown_20 = 1 << 20,
+//   e_unknown_21 = 1 << 21,
+//   e_unknown_22 = 1 << 22,
+//   e_unknown_23 = 1 << 23,
+//   e_flag_field_size_64_bit = 1 << 24, // Not sure what is
+//   e_unknown_25 = 1 << 25,
+//   e_unknown_26 = 1 << 26,
+//   e_unknown_27 = 1 << 27,
+//   e_unknown_28 = 1 << 28,
+//   e_unknown_29 = 1 << 29,
+//   e_unknown_30 = 1 << 30,
+//   e_unknown_31 = 1 << 31
+//};
+//
+//bool check_flag(uint32_t flags, Flag to_check)
+//{
+//	return (flags & to_check) != 0;
+//}
+// ------------------------------------------------------------------------------------------------
+Token::Token(const char *sbegin, const char *send, TokenType type, size_t offset) :
+		sbegin(sbegin),
+		send(send),
+		type(type),
+		line(offset),
+		column(BINARY_MARKER) {
+#ifdef DEBUG_ENABLED
+	contents = std::string(sbegin, static_cast<size_t>(send - sbegin));
+#endif
+	// calc length
+	// measure from sBegin to sEnd and validate?
+}
+
+namespace {
+
+// ------------------------------------------------------------------------------------------------
+// signal tokenization error
+void TokenizeError(const std::string &message, size_t offset) {
+	print_error("[FBX-Tokenize] " + String(message.c_str()) + ", offset " + itos(offset));
+}
+
+// ------------------------------------------------------------------------------------------------
+size_t Offset(const char *begin, const char *cursor) {
+	//ai_assert(begin <= cursor);
+
+	return cursor - begin;
+}
+
+// ------------------------------------------------------------------------------------------------
+void TokenizeError(const std::string &message, const char *begin, const char *cursor) {
+	TokenizeError(message, Offset(begin, cursor));
+}
+
+// ------------------------------------------------------------------------------------------------
+uint32_t ReadWord(const char *input, const char *&cursor, const char *end) {
+	const size_t k_to_read = sizeof(uint32_t);
+	if (Offset(cursor, end) < k_to_read) {
+		TokenizeError("cannot ReadWord, out of bounds", input, cursor);
+	}
+
+	uint32_t word;
+	::memcpy(&word, cursor, 4);
+	AI_SWAP4(word);
+
+	cursor += k_to_read;
+
+	return word;
+}
+
+// ------------------------------------------------------------------------------------------------
+uint64_t ReadDoubleWord(const char *input, const char *&cursor, const char *end) {
+	const size_t k_to_read = sizeof(uint64_t);
+	if (Offset(cursor, end) < k_to_read) {
+		TokenizeError("cannot ReadDoubleWord, out of bounds", input, cursor);
+	}
+
+	uint64_t dword /*= *reinterpret_cast<const uint64_t*>(cursor)*/;
+	::memcpy(&dword, cursor, sizeof(uint64_t));
+	AI_SWAP8(dword);
+
+	cursor += k_to_read;
+
+	return dword;
+}
+
+// ------------------------------------------------------------------------------------------------
+uint8_t ReadByte(const char *input, const char *&cursor, const char *end) {
+	if (Offset(cursor, end) < sizeof(uint8_t)) {
+		TokenizeError("cannot ReadByte, out of bounds", input, cursor);
+	}
+
+	uint8_t word; /* = *reinterpret_cast< const uint8_t* >( cursor )*/
+	::memcpy(&word, cursor, sizeof(uint8_t));
+	++cursor;
+
+	return word;
+}
+
+// ------------------------------------------------------------------------------------------------
+unsigned int ReadString(const char *&sbegin_out, const char *&send_out, const char *input,
+		const char *&cursor, const char *end, bool long_length = false, bool allow_null = false) {
+	const uint32_t len_len = long_length ? 4 : 1;
+	if (Offset(cursor, end) < len_len) {
+		TokenizeError("cannot ReadString, out of bounds reading length", input, cursor);
+	}
+
+	const uint32_t length = long_length ? ReadWord(input, cursor, end) : ReadByte(input, cursor, end);
+
+	if (Offset(cursor, end) < length) {
+		TokenizeError("cannot ReadString, length is out of bounds", input, cursor);
+	}
+
+	sbegin_out = cursor;
+	cursor += length;
+
+	send_out = cursor;
+
+	if (!allow_null) {
+		for (unsigned int i = 0; i < length; ++i) {
+			if (sbegin_out[i] == '\0') {
+				TokenizeError("failed ReadString, unexpected NUL character in string", input, cursor);
+			}
+		}
+	}
+
+	return length;
+}
+
+// ------------------------------------------------------------------------------------------------
+void ReadData(const char *&sbegin_out, const char *&send_out, const char *input, const char *&cursor, const char *end) {
+	if (Offset(cursor, end) < 1) {
+		TokenizeError("cannot ReadData, out of bounds reading length", input, cursor);
+	}
+
+	const char type = *cursor;
+	sbegin_out = cursor++;
+
+	switch (type) {
+		// 16 bit int
+		case 'Y':
+			cursor += 2;
+			break;
+
+			// 1 bit bool flag (yes/no)
+		case 'C':
+			cursor += 1;
+			break;
+
+			// 32 bit int
+		case 'I':
+			// <- fall through
+
+			// float
+		case 'F':
+			cursor += 4;
+			break;
+
+			// double
+		case 'D':
+			cursor += 8;
+			break;
+
+			// 64 bit int
+		case 'L':
+			cursor += 8;
+			break;
+
+			// note: do not write cursor += ReadWord(...cursor) as this would be UB
+
+			// raw binary data
+		case 'R': {
+			const uint32_t length = ReadWord(input, cursor, end);
+			cursor += length;
+			break;
+		}
+
+		case 'b':
+			// TODO: what is the 'b' type code? Right now we just skip over it /
+			// take the full range we could get
+			cursor = end;
+			break;
+
+			// array of *
+		case 'f':
+		case 'd':
+		case 'l':
+		case 'i':
+		case 'c': {
+			const uint32_t length = ReadWord(input, cursor, end);
+			const uint32_t encoding = ReadWord(input, cursor, end);
+
+			const uint32_t comp_len = ReadWord(input, cursor, end);
+
+			// compute length based on type and check against the stored value
+			if (encoding == 0) {
+				uint32_t stride = 0;
+				switch (type) {
+					case 'f':
+					case 'i':
+						stride = 4;
+						break;
+
+					case 'd':
+					case 'l':
+						stride = 8;
+						break;
+
+					case 'c':
+						stride = 1;
+						break;
+
+					default:
+						break;
+				};
+				//ai_assert(stride > 0);
+				if (length * stride != comp_len) {
+					TokenizeError("cannot ReadData, calculated data stride differs from what the file claims", input, cursor);
+				}
+			}
+			// zip/deflate algorithm (encoding==1)? take given length. anything else? die
+			else if (encoding != 1) {
+				TokenizeError("cannot ReadData, unknown encoding", input, cursor);
+			}
+			cursor += comp_len;
+			break;
+		}
+
+			// string
+		case 'S': {
+			const char *sb, *se;
+			// 0 characters can legally happen in such strings
+			ReadString(sb, se, input, cursor, end, true, true);
+			break;
+		}
+		default:
+			TokenizeError("cannot ReadData, unexpected type code: " + std::string(&type, 1), input, cursor);
+	}
+
+	if (cursor > end) {
+		TokenizeError("cannot ReadData, the remaining size is too small for the data type: " + std::string(&type, 1), input, cursor);
+	}
+
+	// the type code is contained in the returned range
+	send_out = cursor;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool ReadScope(TokenList &output_tokens, const char *input, const char *&cursor, const char *end, bool const is64bits) {
+	// the first word contains the offset at which this block ends
+	const uint64_t end_offset = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
+
+	// we may get 0 if reading reached the end of the file -
+	// fbx files have a mysterious extra footer which I don't know
+	// how to extract any information from, but at least it always
+	// starts with a 0.
+	if (!end_offset) {
+		return false;
+	}
+
+	if (end_offset > Offset(input, end)) {
+		TokenizeError("block offset is out of range", input, cursor);
+	} else if (end_offset < Offset(input, cursor)) {
+		TokenizeError("block offset is negative out of range", input, cursor);
+	}
+
+	// the second data word contains the number of properties in the scope
+	const uint64_t prop_count = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
+
+	// the third data word contains the length of the property list
+	const uint64_t prop_length = is64bits ? ReadDoubleWord(input, cursor, end) : ReadWord(input, cursor, end);
+
+	// now comes the name of the scope/key
+	const char *sbeg, *send;
+	ReadString(sbeg, send, input, cursor, end);
+
+	output_tokens.push_back(new_Token(sbeg, send, TokenType_KEY, Offset(input, cursor)));
+
+	// now come the individual properties
+	const char *begin_cursor = cursor;
+	for (unsigned int i = 0; i < prop_count; ++i) {
+		ReadData(sbeg, send, input, cursor, begin_cursor + prop_length);
+
+		output_tokens.push_back(new_Token(sbeg, send, TokenType_DATA, Offset(input, cursor)));
+
+		if (i != prop_count - 1) {
+			output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_COMMA, Offset(input, cursor)));
+		}
+	}
+
+	if (Offset(begin_cursor, cursor) != prop_length) {
+		TokenizeError("property length not reached, something is wrong", input, cursor);
+	}
+
+	// at the end of each nested block, there is a NUL record to indicate
+	// that the sub-scope exists (i.e. to distinguish between P: and P : {})
+	// this NUL record is 13 bytes long on 32 bit version and 25 bytes long on 64 bit.
+	const size_t sentinel_block_length = is64bits ? (sizeof(uint64_t) * 3 + 1) : (sizeof(uint32_t) * 3 + 1);
+
+	if (Offset(input, cursor) < end_offset) {
+		if (end_offset - Offset(input, cursor) < sentinel_block_length) {
+			TokenizeError("insufficient padding bytes at block end", input, cursor);
+		}
+
+		output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_OPEN_BRACKET, Offset(input, cursor)));
+
+		// XXX this is vulnerable to stack overflowing ..
+		while (Offset(input, cursor) < end_offset - sentinel_block_length) {
+			ReadScope(output_tokens, input, cursor, input + end_offset - sentinel_block_length, is64bits);
+		}
+		output_tokens.push_back(new_Token(cursor, cursor + 1, TokenType_CLOSE_BRACKET, Offset(input, cursor)));
+
+		for (unsigned int i = 0; i < sentinel_block_length; ++i) {
+			if (cursor[i] != '\0') {
+				TokenizeError("failed to read nested block sentinel, expected all bytes to be 0", input, cursor);
+			}
+		}
+		cursor += sentinel_block_length;
+	}
+
+	if (Offset(input, cursor) != end_offset) {
+		TokenizeError("scope length not reached, something is wrong", input, cursor);
+	}
+
+	return true;
+}
+} // anonymous namespace
+
+// ------------------------------------------------------------------------------------------------
+// TODO: Test FBX Binary files newer than the 7500 version to check if the 64 bits address behaviour is consistent
+void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length) {
+	if (length < 0x1b) {
+		//TokenizeError("file is too short",0);
+	}
+
+	//uint32_t offset = 0x15;
+	/*    const char* cursor = input + 0x15;
+    const uint32_t flags = ReadWord(input, cursor, input + length);
+    const uint8_t padding_0 = ReadByte(input, cursor, input + length); // unused
+    const uint8_t padding_1 = ReadByte(input, cursor, input + length); // unused*/
+
+	if (strncmp(input, "Kaydara FBX Binary", 18)) {
+		TokenizeError("magic bytes not found", 0);
+	}
+
+	const char *cursor = input + 18;
+	/*Result ignored*/ ReadByte(input, cursor, input + length);
+	/*Result ignored*/ ReadByte(input, cursor, input + length);
+	/*Result ignored*/ ReadByte(input, cursor, input + length);
+	/*Result ignored*/ ReadByte(input, cursor, input + length);
+	/*Result ignored*/ ReadByte(input, cursor, input + length);
+	const uint32_t version = ReadWord(input, cursor, input + length);
+	print_verbose("FBX Version: " + itos(version));
+	//ASSIMP_LOG_DEBUG_F("FBX version: ", version);
+	const bool is64bits = version >= 7500;
+	const char *end = input + length;
+	while (cursor < end) {
+		if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) {
+			break;
+		}
+	}
+}
+} // namespace FBXDocParser

+ 110 - 0
modules/fbx/fbx_parser/FBXCommon.h

@@ -0,0 +1,110 @@
+/*************************************************************************/
+/*  FBXCommon.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file FBXCommon.h
+* Some useful constants and enums for dealing with FBX files.
+*/
+#ifndef FBX_COMMON_H
+#define FBX_COMMON_H
+
+#include <string>
+
+namespace FBXDocParser {
+const std::string NULL_RECORD = { // 13 null bytes
+	'\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0', '\0'
+}; // who knows why
+const std::string SEPARATOR = { '\x00', '\x01' }; // for use inside strings
+const std::string MAGIC_NODE_TAG = "_$AssimpFbx$"; // from import
+const int64_t SECOND = 46186158000; // FBX's kTime unit
+
+// rotation order. We'll probably use EulerXYZ for everything
+enum RotOrder {
+	RotOrder_EulerXYZ = 0,
+	RotOrder_EulerXZY,
+	RotOrder_EulerYZX,
+	RotOrder_EulerYXZ,
+	RotOrder_EulerZXY,
+	RotOrder_EulerZYX,
+
+	RotOrder_SphericXYZ,
+
+	RotOrder_MAX // end-of-enum sentinel
+};
+
+enum TransformInheritance {
+	Transform_RrSs = 0,
+	Transform_RSrs = 1,
+	Transform_Rrs = 2,
+	TransformInheritance_MAX // end-of-enum sentinel
+};
+} // namespace FBXDocParser
+
+#endif // FBX_COMMON_H

+ 279 - 0
modules/fbx/fbx_parser/FBXDeformer.cpp

@@ -0,0 +1,279 @@
+/*************************************************************************/
+/*  FBXDeformer.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXNoteAttribute.cpp
+ *  @brief Assimp::FBX::NodeAttribute (and subclasses) implementation
+ */
+
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXMeshGeometry.h"
+#include "FBXParser.h"
+#include "core/math/math_funcs.h"
+#include "core/math/transform.h"
+
+#include <iostream>
+
+namespace FBXDocParser {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Deformer::Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2));
+	props = GetPropertyTable(doc, "Deformer.Fbx" + classname, element, sc, true);
+}
+
+// ------------------------------------------------------------------------------------------------
+Deformer::~Deformer() {
+}
+
+Constraint::Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+	const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2));
+	// used something.fbx as this is a cache name.
+	props = GetPropertyTable(doc, "Something.Fbx" + classname, element, sc, true);
+}
+
+Constraint::~Constraint() {
+}
+
+// ------------------------------------------------------------------------------------------------
+Cluster::Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Deformer(id, element, doc, name), valid_transformAssociateModel(false) {
+	const ScopePtr sc = GetRequiredScope(element);
+	//    for( auto element : sc.Elements())
+	//    {
+	//        std::cout << "cluster element: " << element.first << std::endl;
+	//    }
+	//
+	//    element: Indexes
+	//    element: Transform
+	//    element: TransformAssociateModel
+	//    element: TransformLink
+	//    element: UserData
+	//    element: Version
+	//    element: Weights
+
+	const ElementPtr Indexes = sc->GetElement("Indexes");
+	const ElementPtr Weights = sc->GetElement("Weights");
+
+	const ElementPtr TransformAssociateModel = sc->GetElement("TransformAssociateModel");
+	if (TransformAssociateModel != nullptr) {
+		//Transform t = ReadMatrix(*TransformAssociateModel);
+		link_mode = SkinLinkMode_Additive;
+		valid_transformAssociateModel = true;
+	} else {
+		link_mode = SkinLinkMode_Normalized;
+		valid_transformAssociateModel = false;
+	}
+
+	const ElementPtr Transform = GetRequiredElement(sc, "Transform", element);
+	const ElementPtr TransformLink = GetRequiredElement(sc, "TransformLink", element);
+
+	// todo: check if we need this
+	//const Element& TransformAssociateModel = GetRequiredElement(sc, "TransformAssociateModel", &element);
+
+	transform = ReadMatrix(Transform);
+	transformLink = ReadMatrix(TransformLink);
+
+	// it is actually possible that there be Deformer's with no weights
+	if (!!Indexes != !!Weights) {
+		DOMError("either Indexes or Weights are missing from Cluster", element);
+	}
+
+	if (Indexes) {
+		ParseVectorDataArray(indices, Indexes);
+		ParseVectorDataArray(weights, Weights);
+	}
+
+	if (indices.size() != weights.size()) {
+		DOMError("sizes of index and weight array don't match up", element);
+	}
+
+	// read assigned node
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Model");
+	for (const Connection *con : conns) {
+		const Model *mod = ProcessSimpleConnection<Model>(*con, false, "Model -> Cluster", element);
+		if (mod) {
+			node = mod;
+			break;
+		}
+	}
+
+	if (!node) {
+		DOMError("failed to read target Node for Cluster", element);
+		node = nullptr;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Cluster::~Cluster() {
+}
+
+// ------------------------------------------------------------------------------------------------
+Skin::Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Deformer(id, element, doc, name), accuracy(0.0f) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	// keep this it is used for debugging and any FBX format changes
+	// for (auto element : sc.Elements()) {
+	// 	std::cout << "skin element: " << element.first << std::endl;
+	// }
+
+	const ElementPtr Link_DeformAcuracy = sc->GetElement("Link_DeformAcuracy");
+	if (Link_DeformAcuracy) {
+		accuracy = ParseTokenAsFloat(GetRequiredToken(Link_DeformAcuracy, 0));
+	}
+
+	const ElementPtr SkinType = sc->GetElement("SkinningType");
+
+	if (SkinType) {
+		std::string skin_type = ParseTokenAsString(GetRequiredToken(SkinType, 0));
+
+		if (skin_type == "Linear") {
+			skinType = Skin_Linear;
+		} else if (skin_type == "Rigid") {
+			skinType = Skin_Rigid;
+		} else if (skin_type == "DualQuaternion") {
+			skinType = Skin_DualQuaternion;
+		} else if (skin_type == "Blend") {
+			skinType = Skin_Blend;
+		} else {
+			print_error("[doc:skin] could not find valid skin type: " + String(skin_type.c_str()));
+		}
+	}
+
+	// resolve assigned clusters
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer");
+
+	//
+
+	clusters.reserve(conns.size());
+	for (const Connection *con : conns) {
+		const Cluster *cluster = ProcessSimpleConnection<Cluster>(*con, false, "Cluster -> Skin", element);
+		if (cluster) {
+			clusters.push_back(cluster);
+			continue;
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Skin::~Skin() {
+}
+// ------------------------------------------------------------------------------------------------
+BlendShape::BlendShape(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Deformer(id, element, doc, name) {
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer");
+	blendShapeChannels.reserve(conns.size());
+	for (const Connection *con : conns) {
+		const BlendShapeChannel *bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element);
+		if (bspc) {
+			blendShapeChannels.push_back(bspc);
+			continue;
+		}
+	}
+}
+// ------------------------------------------------------------------------------------------------
+BlendShape::~BlendShape() {
+}
+// ------------------------------------------------------------------------------------------------
+BlendShapeChannel::BlendShapeChannel(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Deformer(id, element, doc, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+	const ElementPtr DeformPercent = sc->GetElement("DeformPercent");
+	if (DeformPercent) {
+		percent = ParseTokenAsFloat(GetRequiredToken(DeformPercent, 0));
+	}
+	const ElementPtr FullWeights = sc->GetElement("FullWeights");
+	if (FullWeights) {
+		ParseVectorDataArray(fullWeights, FullWeights);
+	}
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Geometry");
+	shapeGeometries.reserve(conns.size());
+	for (const Connection *con : conns) {
+		const ShapeGeometry *const sg = ProcessSimpleConnection<ShapeGeometry>(*con, false, "Shape -> BlendShapeChannel", element);
+		if (sg) {
+			shapeGeometries.push_back(sg);
+			continue;
+		}
+	}
+}
+// ------------------------------------------------------------------------------------------------
+BlendShapeChannel::~BlendShapeChannel() {
+}
+// ------------------------------------------------------------------------------------------------
+} // namespace FBXDocParser

+ 713 - 0
modules/fbx/fbx_parser/FBXDocument.cpp

@@ -0,0 +1,713 @@
+/*************************************************************************/
+/*  FBXDocument.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the*
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXDocument.cpp
+ *  @brief Implementation of the FBX DOM classes
+ */
+
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXImportSettings.h"
+#include "FBXMeshGeometry.h"
+#include "FBXParser.h"
+#include "FBXProperties.h"
+#include "FBXUtil.h"
+
+#include <algorithm>
+#include <functional>
+#include <iostream>
+#include <map>
+#include <memory>
+
+namespace FBXDocParser {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+LazyObject::LazyObject(uint64_t id, const ElementPtr element, const Document &doc) :
+		doc(doc), element(element), id(id), flags() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+LazyObject::~LazyObject() {
+	object.reset();
+}
+
+ObjectPtr LazyObject::LoadObject() {
+	if (IsBeingConstructed() || FailedToConstruct()) {
+		return nullptr;
+	}
+
+	if (object) {
+		return object.get();
+	}
+
+	TokenPtr key = element->KeyToken();
+	ERR_FAIL_COND_V(!key, nullptr);
+	const TokenList &tokens = element->Tokens();
+
+	if (tokens.size() < 3) {
+		//DOMError("expected at least 3 tokens: id, name and class tag",&element);
+		return nullptr;
+	}
+
+	const char *err = nullptr;
+	std::string name = ParseTokenAsString(tokens[1], err);
+	if (err) {
+		DOMError(err, element);
+	}
+
+	// small fix for binary reading: binary fbx files don't use
+	// prefixes such as Model:: in front of their names. The
+	// loading code expects this at many places, though!
+	// so convert the binary representation (a 0x0001) to the
+	// double colon notation.
+	if (tokens[1]->IsBinary()) {
+		for (size_t i = 0; i < name.length(); ++i) {
+			if (name[i] == 0x0 && name[i + 1] == 0x1) {
+				name = name.substr(i + 2) + "::" + name.substr(0, i);
+			}
+		}
+	}
+
+	const std::string classtag = ParseTokenAsString(tokens[2], err);
+	if (err) {
+		DOMError(err, element);
+	}
+
+	// prevent recursive calls
+	flags |= BEING_CONSTRUCTED;
+
+	// this needs to be relatively fast since it happens a lot,
+	// so avoid constructing strings all the time.
+	const char *obtype = key->begin();
+	const size_t length = static_cast<size_t>(key->end() - key->begin());
+
+	if (!strncmp(obtype, "Pose", length)) {
+		object.reset(new FbxPose(id, element, doc, name));
+	} else if (!strncmp(obtype, "Geometry", length)) {
+		if (!strcmp(classtag.c_str(), "Mesh")) {
+			object.reset(new MeshGeometry(id, element, name, doc));
+		}
+		if (!strcmp(classtag.c_str(), "Shape")) {
+			object.reset(new ShapeGeometry(id, element, name, doc));
+		}
+		if (!strcmp(classtag.c_str(), "Line")) {
+			object.reset(new LineGeometry(id, element, name, doc));
+		}
+	} else if (!strncmp(obtype, "NodeAttribute", length)) {
+		if (!strcmp(classtag.c_str(), "Camera")) {
+			object.reset(new Camera(id, element, doc, name));
+		} else if (!strcmp(classtag.c_str(), "CameraSwitcher")) {
+			object.reset(new CameraSwitcher(id, element, doc, name));
+		} else if (!strcmp(classtag.c_str(), "Light")) {
+			object.reset(new Light(id, element, doc, name));
+		} else if (!strcmp(classtag.c_str(), "Null")) {
+			object.reset(new Null(id, element, doc, name));
+		} else if (!strcmp(classtag.c_str(), "LimbNode")) {
+			// This is an older format for bones
+			// this is what blender uses I believe
+			object.reset(new LimbNode(id, element, doc, name));
+		}
+	} else if (!strncmp(obtype, "Constraint", length)) {
+		object.reset(new Constraint(id, element, doc, name));
+	} else if (!strncmp(obtype, "Deformer", length)) {
+		if (!strcmp(classtag.c_str(), "Cluster")) {
+			object.reset(new Cluster(id, element, doc, name));
+		} else if (!strcmp(classtag.c_str(), "Skin")) {
+			object.reset(new Skin(id, element, doc, name));
+		} else if (!strcmp(classtag.c_str(), "BlendShape")) {
+			object.reset(new BlendShape(id, element, doc, name));
+		} else if (!strcmp(classtag.c_str(), "BlendShapeChannel")) {
+			object.reset(new BlendShapeChannel(id, element, doc, name));
+		}
+	} else if (!strncmp(obtype, "Model", length)) {
+		// Model is normal node
+
+		// LimbNode model is a 'bone' node.
+		if (!strcmp(classtag.c_str(), "LimbNode")) {
+			object.reset(new ModelLimbNode(id, element, doc, name));
+
+		} else if (strcmp(classtag.c_str(), "IKEffector") && strcmp(classtag.c_str(), "FKEffector")) {
+			// FK and IK effectors are not supporte
+			object.reset(new Model(id, element, doc, name));
+		}
+	} else if (!strncmp(obtype, "Material", length)) {
+		object.reset(new Material(id, element, doc, name));
+	} else if (!strncmp(obtype, "Texture", length)) {
+		object.reset(new Texture(id, element, doc, name));
+	} else if (!strncmp(obtype, "LayeredTexture", length)) {
+		object.reset(new LayeredTexture(id, element, doc, name));
+	} else if (!strncmp(obtype, "Video", length)) {
+		object.reset(new Video(id, element, doc, name));
+	} else if (!strncmp(obtype, "AnimationStack", length)) {
+		object.reset(new AnimationStack(id, element, name, doc));
+	} else if (!strncmp(obtype, "AnimationLayer", length)) {
+		object.reset(new AnimationLayer(id, element, name, doc));
+	} else if (!strncmp(obtype, "AnimationCurve", length)) {
+		object.reset(new AnimationCurve(id, element, name, doc));
+	} else if (!strncmp(obtype, "AnimationCurveNode", length)) {
+		object.reset(new AnimationCurveNode(id, element, name, doc));
+	} else {
+		ERR_FAIL_V_MSG(nullptr, "FBX contains unsupported object: " + String(obtype));
+	}
+
+	flags &= ~BEING_CONSTRUCTED;
+
+	return object.get();
+}
+
+// ------------------------------------------------------------------------------------------------
+Object::Object(uint64_t id, const ElementPtr element, const std::string &name) :
+		element(element), name(name), id(id) {
+}
+
+// ------------------------------------------------------------------------------------------------
+Object::~Object() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+FileGlobalSettings::FileGlobalSettings(const Document &doc, const PropertyTable *props) :
+		props(props), doc(doc) {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+FileGlobalSettings::~FileGlobalSettings() {
+	if (props != nullptr) {
+		delete props;
+		props = nullptr;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Document::Document(const Parser &parser, const ImportSettings &settings) :
+		settings(settings), parser(parser), SafeToImport(false) {
+	// Cannot use array default initialization syntax because vc8 fails on it
+	for (unsigned int &timeStamp : creationTimeStamp) {
+		timeStamp = 0;
+	}
+
+	// we must check if we can read the header version safely, if its outdated then drop it.
+	if (ReadHeader()) {
+		SafeToImport = true;
+		ReadPropertyTemplates();
+
+		ReadGlobalSettings();
+
+		// This order is important, connections need parsed objects to check
+		// whether connections are ok or not. Objects may not be evaluated yet,
+		// though, since this may require valid connections.
+		ReadObjects();
+		ReadConnections();
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Document::~Document() {
+	for (PropertyTemplateMap::value_type v : templates) {
+		delete v.second;
+	}
+
+	for (ObjectMap::value_type &v : objects) {
+		delete v.second;
+	}
+
+	for (ConnectionMap::value_type &v : src_connections) {
+		delete v.second;
+	}
+
+	if (metadata_properties != nullptr) {
+		delete metadata_properties;
+	}
+	// clear globals import pointer
+	globals.reset();
+}
+
+// ------------------------------------------------------------------------------------------------
+static const unsigned int LowerSupportedVersion = 7300;
+static const unsigned int UpperSupportedVersion = 7700;
+
+bool Document::ReadHeader() {
+	// Read ID objects from "Objects" section
+	ScopePtr sc = parser.GetRootScope();
+	ElementPtr ehead = sc->GetElement("FBXHeaderExtension");
+	if (!ehead || !ehead->Compound()) {
+		DOMError("no FBXHeaderExtension dictionary found");
+	}
+
+	const ScopePtr shead = ehead->Compound();
+	fbxVersion = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(shead, "FBXVersion", ehead), 0));
+
+	// While we may have some success with newer files, we don't support
+	// the older 6.n fbx format
+	if (fbxVersion < LowerSupportedVersion) {
+		DOMWarning("unsupported, old format version, FBX 2015-2020, you must re-export in a more modern version of your original modelling application");
+		return false;
+	}
+	if (fbxVersion > UpperSupportedVersion) {
+		DOMWarning("unsupported, newer format version, supported are only FBX 2015, up to FBX 2020"
+				   " trying to read it nevertheless");
+	}
+
+	const ElementPtr ecreator = shead->GetElement("Creator");
+	if (ecreator) {
+		creator = ParseTokenAsString(GetRequiredToken(ecreator, 0));
+	}
+
+	//
+	// Scene Info
+	//
+
+	const ElementPtr scene_info = shead->GetElement("SceneInfo");
+
+	if (scene_info) {
+		PropertyTable *fileExportProps = const_cast<PropertyTable *>(GetPropertyTable(*this, "", scene_info, scene_info->Compound(), true));
+
+		if (fileExportProps) {
+			metadata_properties = fileExportProps;
+		}
+	}
+
+	const ElementPtr etimestamp = shead->GetElement("CreationTimeStamp");
+	if (etimestamp && etimestamp->Compound()) {
+		const ScopePtr stimestamp = etimestamp->Compound();
+		creationTimeStamp[0] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Year"), 0));
+		creationTimeStamp[1] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Month"), 0));
+		creationTimeStamp[2] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Day"), 0));
+		creationTimeStamp[3] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Hour"), 0));
+		creationTimeStamp[4] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Minute"), 0));
+		creationTimeStamp[5] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Second"), 0));
+		creationTimeStamp[6] = ParseTokenAsInt(GetRequiredToken(GetRequiredElement(stimestamp, "Millisecond"), 0));
+	}
+
+	return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadGlobalSettings() {
+	ERR_FAIL_COND_MSG(globals != nullptr, "Global settings is already setup this is a serious error and should be reported");
+
+	const ScopePtr sc = parser.GetRootScope();
+	const ElementPtr ehead = sc->GetElement("GlobalSettings");
+	if (nullptr == ehead || !ehead->Compound()) {
+		DOMWarning("no GlobalSettings dictionary found");
+		globals = std::make_shared<FileGlobalSettings>(*this, new PropertyTable());
+		return;
+	}
+
+	const PropertyTable *props = GetPropertyTable(*this, "", ehead, ehead->Compound(), true);
+
+	//double v = PropertyGet<float>( *props, std::string("UnitScaleFactor"), 1.0 );
+
+	if (!props) {
+		DOMError("GlobalSettings dictionary contains no property table");
+	}
+
+	globals = std::make_shared<FileGlobalSettings>(*this, props);
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadObjects() {
+	// read ID objects from "Objects" section
+	const ScopePtr sc = parser.GetRootScope();
+	const ElementPtr eobjects = sc->GetElement("Objects");
+	if (!eobjects || !eobjects->Compound()) {
+		DOMError("no Objects dictionary found");
+	}
+
+	// add a dummy entry to represent the Model::RootNode object (id 0),
+	// which is only indirectly defined in the input file
+	objects[0] = new LazyObject(0L, eobjects, *this);
+
+	const ScopePtr sobjects = eobjects->Compound();
+	for (const ElementMap::value_type &iter : sobjects->Elements()) {
+		// extract ID
+		const TokenList &tok = iter.second->Tokens();
+
+		if (tok.empty()) {
+			DOMError("expected ID after object key", iter.second);
+		}
+
+		const char *err;
+		const uint64_t id = ParseTokenAsID(tok[0], err);
+		if (err) {
+			DOMError(err, iter.second);
+		}
+
+		// id=0 is normally implicit
+		if (id == 0L) {
+			DOMError("encountered object with implicitly defined id 0", iter.second);
+		}
+
+		if (objects.find(id) != objects.end()) {
+			DOMWarning("encountered duplicate object id, ignoring first occurrence", iter.second);
+		}
+
+		objects[id] = new LazyObject(id, iter.second, *this);
+
+		// grab all animation stacks upfront since there is no listing of them
+		if (!strcmp(iter.first.c_str(), "AnimationStack")) {
+			animationStacks.push_back(id);
+		} else if (!strcmp(iter.first.c_str(), "Constraint")) {
+			constraints.push_back(id);
+		} else if (!strcmp(iter.first.c_str(), "Pose")) {
+			bind_poses.push_back(id);
+		} else if (!strcmp(iter.first.c_str(), "Material")) {
+			materials.push_back(id);
+		} else if (!strcmp(iter.first.c_str(), "Deformer")) {
+			TokenPtr key = iter.second->KeyToken();
+			ERR_CONTINUE_MSG(!key, "[parser bug] invalid token key for deformer");
+			const TokenList &tokens = iter.second->Tokens();
+			const std::string class_tag = ParseTokenAsString(tokens[2], err);
+
+			if (err) {
+				DOMError(err, iter.second);
+			}
+
+			if (class_tag == "Skin") {
+				//print_verbose("registered skin:" + itos(id));
+				skins.push_back(id);
+			}
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadPropertyTemplates() {
+	const ScopePtr sc = parser.GetRootScope();
+	// read property templates from "Definitions" section
+	const ElementPtr edefs = sc->GetElement("Definitions");
+	if (!edefs || !edefs->Compound()) {
+		DOMWarning("no Definitions dictionary found");
+		return;
+	}
+
+	const ScopePtr sdefs = edefs->Compound();
+	const ElementCollection otypes = sdefs->GetCollection("ObjectType");
+	for (ElementMap::const_iterator it = otypes.first; it != otypes.second; ++it) {
+		const ElementPtr el = (*it).second;
+		const ScopePtr sc_2 = el->Compound();
+		if (!sc_2) {
+			DOMWarning("expected nested scope in ObjectType, ignoring", el);
+			continue;
+		}
+
+		const TokenList &tok = el->Tokens();
+		if (tok.empty()) {
+			DOMWarning("expected name for ObjectType element, ignoring", el);
+			continue;
+		}
+
+		const std::string &oname = ParseTokenAsString(tok[0]);
+
+		const ElementCollection templs = sc_2->GetCollection("PropertyTemplate");
+		for (ElementMap::const_iterator iter = templs.first; iter != templs.second; ++iter) {
+			const ElementPtr el_2 = (*iter).second;
+			const ScopePtr sc_3 = el_2->Compound();
+			if (!sc_3) {
+				DOMWarning("expected nested scope in PropertyTemplate, ignoring", el);
+				continue;
+			}
+
+			const TokenList &tok_2 = el_2->Tokens();
+			if (tok_2.empty()) {
+				DOMWarning("expected name for PropertyTemplate element, ignoring", el);
+				continue;
+			}
+
+			const std::string &pname = ParseTokenAsString(tok_2[0]);
+
+			const ElementPtr Properties70 = sc_3->GetElement("Properties70");
+			if (Properties70) {
+				// PropertyTable(const ElementPtr element, const PropertyTable* templateProps);
+				const PropertyTable *props = new PropertyTable(Properties70, nullptr);
+
+				templates[oname + "." + pname] = props;
+			}
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadConnections() {
+	const ScopePtr sc = parser.GetRootScope();
+
+	// read property templates from "Definitions" section
+	const ElementPtr econns = sc->GetElement("Connections");
+	if (!econns || !econns->Compound()) {
+		DOMError("no Connections dictionary found");
+	}
+
+	uint64_t insertionOrder = 0l;
+	const ScopePtr sconns = econns->Compound();
+	const ElementCollection conns = sconns->GetCollection("C");
+	for (ElementMap::const_iterator it = conns.first; it != conns.second; ++it) {
+		const ElementPtr el = (*it).second;
+		const std::string &type = ParseTokenAsString(GetRequiredToken(el, 0));
+
+		// PP = property-property connection, ignored for now
+		// (tokens: "PP", ID1, "Property1", ID2, "Property2")
+		if (type == "PP") {
+			continue;
+		}
+
+		const uint64_t src = ParseTokenAsID(GetRequiredToken(el, 1));
+		const uint64_t dest = ParseTokenAsID(GetRequiredToken(el, 2));
+
+		// OO = object-object connection
+		// OP = object-property connection, in which case the destination property follows the object ID
+		const std::string &prop = (type == "OP" ? ParseTokenAsString(GetRequiredToken(el, 3)) : "");
+
+		if (objects.find(src) == objects.end()) {
+			DOMWarning("source object for connection does not exist", el);
+			continue;
+		}
+
+		// dest may be 0 (root node) but we added a dummy object before
+		if (objects.find(dest) == objects.end()) {
+			DOMWarning("destination object for connection does not exist", el);
+			continue;
+		}
+
+		// add new connection
+		const Connection *const c = new Connection(insertionOrder++, src, dest, prop, *this);
+		src_connections.insert(ConnectionMap::value_type(src, c));
+		dest_connections.insert(ConnectionMap::value_type(dest, c));
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<const AnimationStack *> &Document::AnimationStacks() const {
+	if (!animationStacksResolved.empty() || animationStacks.empty()) {
+		return animationStacksResolved;
+	}
+
+	animationStacksResolved.reserve(animationStacks.size());
+	for (uint64_t id : animationStacks) {
+		LazyObject *lazy = GetObject(id);
+
+		// Two things happen here:
+		// We cast internally an Object PTR to an Animation Stack PTR
+		// We return invalid weak_ptrs for objects which are invalid
+
+		const AnimationStack *stack = lazy->Get<AnimationStack>();
+		ERR_CONTINUE_MSG(!stack, "invalid ptr to AnimationStack - conversion failure");
+
+		// We push back the weak reference :) to keep things simple, as ownership is on the parser side so it wont be cleaned up.
+		animationStacksResolved.push_back(stack);
+	}
+
+	return animationStacksResolved;
+}
+
+// ------------------------------------------------------------------------------------------------
+LazyObject *Document::GetObject(uint64_t id) const {
+	ObjectMap::const_iterator it = objects.find(id);
+	return it == objects.end() ? nullptr : (*it).second;
+}
+
+#define MAX_CLASSNAMES 6
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsSequenced(uint64_t id, const ConnectionMap &conns) const {
+	std::vector<const Connection *> temp;
+
+	const std::pair<ConnectionMap::const_iterator, ConnectionMap::const_iterator> range =
+			conns.equal_range(id);
+
+	temp.reserve(std::distance(range.first, range.second));
+	for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) {
+		temp.push_back((*it).second);
+	}
+
+	std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare));
+
+	return temp; // NRVO should handle this
+}
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsSequenced(uint64_t id, bool is_src,
+		const ConnectionMap &conns,
+		const char *const *classnames,
+		size_t count) const
+
+{
+	size_t lengths[MAX_CLASSNAMES];
+
+	const size_t c = count;
+	for (size_t i = 0; i < c; ++i) {
+		lengths[i] = strlen(classnames[i]);
+	}
+
+	std::vector<const Connection *> temp;
+	const std::pair<ConnectionMap::const_iterator, ConnectionMap::const_iterator> range =
+			conns.equal_range(id);
+
+	temp.reserve(std::distance(range.first, range.second));
+	for (ConnectionMap::const_iterator it = range.first; it != range.second; ++it) {
+		TokenPtr key = (is_src ? (*it).second->LazyDestinationObject() : (*it).second->LazySourceObject())->GetElement()->KeyToken();
+
+		const char *obtype = key->begin();
+
+		for (size_t i = 0; i < c; ++i) {
+			//ai_assert(classnames[i]);
+			if (static_cast<size_t>(std::distance(key->begin(), key->end())) == lengths[i] && !strncmp(classnames[i], obtype, lengths[i])) {
+				obtype = nullptr;
+				break;
+			}
+		}
+
+		if (obtype) {
+			continue;
+		}
+
+		temp.push_back((*it).second);
+	}
+
+	std::sort(temp.begin(), temp.end(), std::mem_fn(&Connection::Compare));
+	return temp; // NRVO should handle this
+}
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t source) const {
+	return GetConnectionsSequenced(source, ConnectionsBySource());
+}
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t src, const char *classname) const {
+	const char *arr[] = { classname };
+	return GetConnectionsBySourceSequenced(src, arr, 1);
+}
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsBySourceSequenced(uint64_t source,
+		const char *const *classnames, size_t count) const {
+	return GetConnectionsSequenced(source, true, ConnectionsBySource(), classnames, count);
+}
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest,
+		const char *classname) const {
+	const char *arr[] = { classname };
+	return GetConnectionsByDestinationSequenced(dest, arr, 1);
+}
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest) const {
+	return GetConnectionsSequenced(dest, ConnectionsByDestination());
+}
+
+// ------------------------------------------------------------------------------------------------
+std::vector<const Connection *> Document::GetConnectionsByDestinationSequenced(uint64_t dest,
+		const char *const *classnames, size_t count) const {
+	return GetConnectionsSequenced(dest, false, ConnectionsByDestination(), classnames, count);
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection::Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string &prop,
+		const Document &doc) :
+		insertionOrder(insertionOrder), prop(prop), src(src), dest(dest), doc(doc) {
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection::~Connection() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+LazyObject *Connection::LazySourceObject() const {
+	LazyObject *const lazy = doc.GetObject(src);
+	return lazy;
+}
+
+// ------------------------------------------------------------------------------------------------
+LazyObject *Connection::LazyDestinationObject() const {
+	LazyObject *const lazy = doc.GetObject(dest);
+	return lazy;
+}
+
+// ------------------------------------------------------------------------------------------------
+Object *Connection::SourceObject() const {
+	LazyObject *lazy = doc.GetObject(src);
+	//ai_assert(lazy);
+	return lazy->LoadObject();
+}
+
+// ------------------------------------------------------------------------------------------------
+Object *Connection::DestinationObject() const {
+	LazyObject *lazy = doc.GetObject(dest);
+	//ai_assert(lazy);
+	return lazy->LoadObject();
+}
+} // namespace FBXDocParser

+ 1319 - 0
modules/fbx/fbx_parser/FBXDocument.h

@@ -0,0 +1,1319 @@
+/*************************************************************************/
+/*  FBXDocument.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.                */
+/*************************************************************************/
+
+/** @file  FBXDocument.h
+ *  @brief FBX DOM
+ */
+#ifndef FBX_DOCUMENT_H
+#define FBX_DOCUMENT_H
+
+#include "FBXCommon.h"
+#include "FBXParser.h"
+#include "FBXProperties.h"
+#include "core/math/transform.h"
+#include "core/math/vector2.h"
+#include "core/math/vector3.h"
+#include "core/string/print_string.h"
+#include <stdint.h>
+#include <numeric>
+
+#define _AI_CONCAT(a, b) a##b
+#define AI_CONCAT(a, b) _AI_CONCAT(a, b)
+
+namespace FBXDocParser {
+
+class Parser;
+class Object;
+struct ImportSettings;
+class Connection;
+
+class PropertyTable;
+class Document;
+class Material;
+class ShapeGeometry;
+class LineGeometry;
+class Geometry;
+
+class Video;
+
+class AnimationCurve;
+class AnimationCurveNode;
+class AnimationLayer;
+class AnimationStack;
+
+class BlendShapeChannel;
+class BlendShape;
+class Skin;
+class Cluster;
+
+typedef Object *ObjectPtr;
+#define new_Object new Object
+
+/** Represents a delay-parsed FBX objects. Many objects in the scene
+ *  are not needed by assimp, so it makes no sense to parse them
+ *  upfront. */
+class LazyObject {
+public:
+	LazyObject(uint64_t id, const ElementPtr element, const Document &doc);
+	~LazyObject();
+
+	ObjectPtr LoadObject();
+
+	/* Casting weak pointers to their templated type safely and preserving ref counting and safety
+	 * with lock() keyword to prevent leaking memory
+	 */
+	template <typename T>
+	const T *Get() {
+		ObjectPtr ob = LoadObject();
+		return dynamic_cast<const T *>(ob);
+	}
+
+	uint64_t ID() const {
+		return id;
+	}
+
+	bool IsBeingConstructed() const {
+		return (flags & BEING_CONSTRUCTED) != 0;
+	}
+
+	bool FailedToConstruct() const {
+		return (flags & FAILED_TO_CONSTRUCT) != 0;
+	}
+
+	ElementPtr GetElement() const {
+		return element;
+	}
+
+	const Document &GetDocument() const {
+		return doc;
+	}
+
+private:
+	const Document &doc;
+	ElementPtr element = nullptr;
+	std::shared_ptr<Object> object = nullptr;
+	const uint64_t id = 0;
+
+	enum Flags {
+		BEING_CONSTRUCTED = 0x1,
+		FAILED_TO_CONSTRUCT = 0x2
+	};
+
+	unsigned int flags = 0;
+};
+
+/** Base class for in-memory (DOM) representations of FBX objects */
+class Object {
+public:
+	Object(uint64_t id, const ElementPtr element, const std::string &name);
+
+	virtual ~Object();
+
+	ElementPtr SourceElement() const {
+		return element;
+	}
+
+	const std::string &Name() const {
+		return name;
+	}
+
+	uint64_t ID() const {
+		return id;
+	}
+
+protected:
+	const ElementPtr element;
+	const std::string name;
+	const uint64_t id = 0;
+};
+
+/** DOM class for generic FBX NoteAttribute blocks. NoteAttribute's just hold a property table,
+ *  fixed members are added by deriving classes. */
+class NodeAttribute : public Object {
+public:
+	NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~NodeAttribute();
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+private:
+	const PropertyTable *props;
+};
+
+/** DOM base class for FBX camera settings attached to a node */
+class CameraSwitcher : public NodeAttribute {
+public:
+	CameraSwitcher(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~CameraSwitcher();
+
+	int CameraID() const {
+		return cameraId;
+	}
+
+	const std::string &CameraName() const {
+		return cameraName;
+	}
+
+	const std::string &CameraIndexName() const {
+		return cameraIndexName;
+	}
+
+private:
+	int cameraId;
+	std::string cameraName;
+	std::string cameraIndexName;
+};
+
+#define fbx_stringize(a) #a
+
+#define fbx_simple_property(name, type, default_value)                           \
+	type name() const {                                                          \
+		return PropertyGet<type>(Props(), fbx_stringize(name), (default_value)); \
+	}
+
+// XXX improve logging
+#define fbx_simple_enum_property(name, type, default_value)                                               \
+	type name() const {                                                                                   \
+		const int ival = PropertyGet<int>(Props(), fbx_stringize(name), static_cast<int>(default_value)); \
+		if (ival < 0 || ival >= AI_CONCAT(type, _MAX)) {                                                  \
+			return static_cast<type>(default_value);                                                      \
+		}                                                                                                 \
+		return static_cast<type>(ival);                                                                   \
+	}
+
+class FbxPoseNode;
+class FbxPose : public Object {
+public:
+	FbxPose(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	const std::vector<FbxPoseNode *> &GetBindPoses() const {
+		return pose_nodes;
+	}
+
+	virtual ~FbxPose();
+
+private:
+	std::vector<FbxPoseNode *> pose_nodes;
+};
+
+class FbxPoseNode {
+public:
+	FbxPoseNode(const ElementPtr element, const Document &doc, const std::string &name) {
+		const ScopePtr sc = GetRequiredScope(element);
+
+		// get pose node transform
+		const ElementPtr Transform = GetRequiredElement(sc, "Matrix", element);
+		transform = ReadMatrix(Transform);
+
+		// get node id this pose node is for
+		const ElementPtr NodeId = sc->GetElement("Node3D");
+		if (NodeId) {
+			target_id = ParseTokenAsInt64(GetRequiredToken(NodeId, 0));
+		}
+
+		print_verbose("added posenode " + itos(target_id) + " transform: " + transform);
+	}
+	virtual ~FbxPoseNode() {
+	}
+
+	uint64_t GetNodeID() const {
+		return target_id;
+	}
+
+	Transform GetBindPose() const {
+		return transform;
+	}
+
+private:
+	uint64_t target_id;
+	Transform transform;
+};
+
+/** DOM base class for FBX cameras attached to a node */
+class Camera : public NodeAttribute {
+public:
+	Camera(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~Camera();
+
+	fbx_simple_property(Position, Vector3, Vector3(0, 0, 0));
+	fbx_simple_property(UpVector, Vector3, Vector3(0, 1, 0));
+	fbx_simple_property(InterestPosition, Vector3, Vector3(0, 0, 0));
+
+	fbx_simple_property(AspectWidth, float, 1.0f);
+	fbx_simple_property(AspectHeight, float, 1.0f);
+	fbx_simple_property(FilmWidth, float, 1.0f);
+	fbx_simple_property(FilmHeight, float, 1.0f);
+
+	fbx_simple_property(NearPlane, float, 0.1f);
+	fbx_simple_property(FarPlane, float, 100.0f);
+
+	fbx_simple_property(FilmAspectRatio, float, 1.0f);
+	fbx_simple_property(ApertureMode, int, 0);
+
+	fbx_simple_property(FieldOfView, float, 1.0f);
+	fbx_simple_property(FocalLength, float, 1.0f);
+};
+
+/** DOM base class for FBX null markers attached to a node */
+class Null : public NodeAttribute {
+public:
+	Null(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+	virtual ~Null();
+};
+
+/** DOM base class for FBX limb node markers attached to a node */
+class LimbNode : public NodeAttribute {
+public:
+	LimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+	virtual ~LimbNode();
+};
+
+/** DOM base class for FBX lights attached to a node */
+class Light : public NodeAttribute {
+public:
+	Light(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+	virtual ~Light();
+
+	enum Type {
+		Type_Point,
+		Type_Directional,
+		Type_Spot,
+		Type_Area,
+		Type_Volume,
+
+		Type_MAX // end-of-enum sentinel
+	};
+
+	enum Decay {
+		Decay_None,
+		Decay_Linear,
+		Decay_Quadratic,
+		Decay_Cubic,
+
+		Decay_MAX // end-of-enum sentinel
+	};
+
+	fbx_simple_property(Color, Vector3, Vector3(1, 1, 1));
+	fbx_simple_enum_property(LightType, Type, 0);
+	fbx_simple_property(CastLightOnObject, bool, false);
+	fbx_simple_property(DrawVolumetricLight, bool, true);
+	fbx_simple_property(DrawGroundProjection, bool, true);
+	fbx_simple_property(DrawFrontFacingVolumetricLight, bool, false);
+	fbx_simple_property(Intensity, float, 100.0f);
+	fbx_simple_property(InnerAngle, float, 0.0f);
+	fbx_simple_property(OuterAngle, float, 45.0f);
+	fbx_simple_property(Fog, int, 50);
+	fbx_simple_enum_property(DecayType, Decay, 2);
+	fbx_simple_property(DecayStart, float, 1.0f);
+	fbx_simple_property(FileName, std::string, "");
+
+	fbx_simple_property(EnableNearAttenuation, bool, false);
+	fbx_simple_property(NearAttenuationStart, float, 0.0f);
+	fbx_simple_property(NearAttenuationEnd, float, 0.0f);
+	fbx_simple_property(EnableFarAttenuation, bool, false);
+	fbx_simple_property(FarAttenuationStart, float, 0.0f);
+	fbx_simple_property(FarAttenuationEnd, float, 0.0f);
+
+	fbx_simple_property(CastShadows, bool, true);
+	fbx_simple_property(ShadowColor, Vector3, Vector3(0, 0, 0));
+
+	fbx_simple_property(AreaLightShape, int, 0);
+
+	fbx_simple_property(LeftBarnDoor, float, 20.0f);
+	fbx_simple_property(RightBarnDoor, float, 20.0f);
+	fbx_simple_property(TopBarnDoor, float, 20.0f);
+	fbx_simple_property(BottomBarnDoor, float, 20.0f);
+	fbx_simple_property(EnableBarnDoor, bool, true);
+};
+
+class Model;
+
+typedef Model *ModelPtr;
+#define new_Model new Model
+
+/** DOM base class for FBX models (even though its semantics are more "node" than "model" */
+class Model : public Object {
+public:
+	enum RotOrder {
+		RotOrder_EulerXYZ = 0,
+		RotOrder_EulerXZY,
+		RotOrder_EulerYZX,
+		RotOrder_EulerYXZ,
+		RotOrder_EulerZXY,
+		RotOrder_EulerZYX,
+
+		RotOrder_SphericXYZ,
+
+		RotOrder_MAX // end-of-enum sentinel
+	};
+
+	Model(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~Model();
+
+	fbx_simple_property(QuaternionInterpolate, int, 0);
+
+	fbx_simple_property(RotationOffset, Vector3, Vector3());
+	fbx_simple_property(RotationPivot, Vector3, Vector3());
+	fbx_simple_property(ScalingOffset, Vector3, Vector3());
+	fbx_simple_property(ScalingPivot, Vector3, Vector3());
+	fbx_simple_property(TranslationActive, bool, false);
+	fbx_simple_property(TranslationMin, Vector3, Vector3());
+	fbx_simple_property(TranslationMax, Vector3, Vector3());
+
+	fbx_simple_property(TranslationMinX, bool, false);
+	fbx_simple_property(TranslationMaxX, bool, false);
+	fbx_simple_property(TranslationMinY, bool, false);
+	fbx_simple_property(TranslationMaxY, bool, false);
+	fbx_simple_property(TranslationMinZ, bool, false);
+	fbx_simple_property(TranslationMaxZ, bool, false);
+
+	fbx_simple_enum_property(RotationOrder, RotOrder, 0);
+	fbx_simple_property(RotationSpaceForLimitOnly, bool, false);
+	fbx_simple_property(RotationStiffnessX, float, 0.0f);
+	fbx_simple_property(RotationStiffnessY, float, 0.0f);
+	fbx_simple_property(RotationStiffnessZ, float, 0.0f);
+	fbx_simple_property(AxisLen, float, 0.0f);
+
+	fbx_simple_property(PreRotation, Vector3, Vector3());
+	fbx_simple_property(PostRotation, Vector3, Vector3());
+	fbx_simple_property(RotationActive, bool, false);
+
+	fbx_simple_property(RotationMin, Vector3, Vector3());
+	fbx_simple_property(RotationMax, Vector3, Vector3());
+
+	fbx_simple_property(RotationMinX, bool, false);
+	fbx_simple_property(RotationMaxX, bool, false);
+	fbx_simple_property(RotationMinY, bool, false);
+	fbx_simple_property(RotationMaxY, bool, false);
+	fbx_simple_property(RotationMinZ, bool, false);
+	fbx_simple_property(RotationMaxZ, bool, false);
+	fbx_simple_enum_property(InheritType, TransformInheritance, 0);
+
+	fbx_simple_property(ScalingActive, bool, false);
+	fbx_simple_property(ScalingMin, Vector3, Vector3());
+	fbx_simple_property(ScalingMax, Vector3, Vector3(1, 1, 1));
+	fbx_simple_property(ScalingMinX, bool, false);
+	fbx_simple_property(ScalingMaxX, bool, false);
+	fbx_simple_property(ScalingMinY, bool, false);
+	fbx_simple_property(ScalingMaxY, bool, false);
+	fbx_simple_property(ScalingMinZ, bool, false);
+	fbx_simple_property(ScalingMaxZ, bool, false);
+
+	fbx_simple_property(GeometricTranslation, Vector3, Vector3());
+	fbx_simple_property(GeometricRotation, Vector3, Vector3());
+	fbx_simple_property(GeometricScaling, Vector3, Vector3(1, 1, 1));
+
+	fbx_simple_property(MinDampRangeX, float, 0.0f);
+	fbx_simple_property(MinDampRangeY, float, 0.0f);
+	fbx_simple_property(MinDampRangeZ, float, 0.0f);
+	fbx_simple_property(MaxDampRangeX, float, 0.0f);
+	fbx_simple_property(MaxDampRangeY, float, 0.0f);
+	fbx_simple_property(MaxDampRangeZ, float, 0.0f);
+
+	fbx_simple_property(MinDampStrengthX, float, 0.0f);
+	fbx_simple_property(MinDampStrengthY, float, 0.0f);
+	fbx_simple_property(MinDampStrengthZ, float, 0.0f);
+	fbx_simple_property(MaxDampStrengthX, float, 0.0f);
+	fbx_simple_property(MaxDampStrengthY, float, 0.0f);
+	fbx_simple_property(MaxDampStrengthZ, float, 0.0f);
+
+	fbx_simple_property(PreferredAngleX, float, 0.0f);
+	fbx_simple_property(PreferredAngleY, float, 0.0f);
+	fbx_simple_property(PreferredAngleZ, float, 0.0f);
+
+	fbx_simple_property(Show, bool, true);
+	fbx_simple_property(LODBox, bool, false);
+	fbx_simple_property(Freeze, bool, false);
+
+	const std::string &Shading() const {
+		return shading;
+	}
+
+	const std::string &Culling() const {
+		return culling;
+	}
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+	/** Get material links */
+	const std::vector<const Material *> &GetMaterials() const {
+		return materials;
+	}
+
+	/** Get geometry links */
+	const std::vector<const Geometry *> &GetGeometry() const {
+		return geometry;
+	}
+
+	/** Get node attachments */
+	const std::vector<const NodeAttribute *> &GetAttributes() const {
+		return attributes;
+	}
+
+	/** convenience method to check if the node has a Null node marker */
+	bool IsNull() const;
+
+private:
+	void ResolveLinks(const ElementPtr element, const Document &doc);
+
+private:
+	std::vector<const Material *> materials;
+	std::vector<const Geometry *> geometry;
+	std::vector<const NodeAttribute *> attributes;
+
+	std::string shading;
+	std::string culling;
+	const PropertyTable *props = nullptr;
+};
+
+class ModelLimbNode : public Model {
+public:
+	ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~ModelLimbNode();
+};
+
+/** DOM class for generic FBX textures */
+class Texture : public Object {
+public:
+	Texture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~Texture();
+
+	const std::string &Type() const {
+		return type;
+	}
+
+	const std::string &FileName() const {
+		return fileName;
+	}
+
+	const std::string &RelativeFilename() const {
+		return relativeFileName;
+	}
+
+	const std::string &AlphaSource() const {
+		return alphaSource;
+	}
+
+	const Vector2 &UVTranslation() const {
+		return uvTrans;
+	}
+
+	const Vector2 &UVScaling() const {
+		return uvScaling;
+	}
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+	// return a 4-tuple
+	const unsigned int *Crop() const {
+		return crop;
+	}
+
+	const Video *Media() const {
+		return media;
+	}
+
+private:
+	Vector2 uvTrans;
+	Vector2 uvScaling;
+
+	std::string type;
+	std::string relativeFileName;
+	std::string fileName;
+	std::string alphaSource;
+	const PropertyTable *props = nullptr;
+
+	unsigned int crop[4] = { 0 };
+
+	const Video *media = nullptr;
+};
+
+/** DOM class for layered FBX textures */
+class LayeredTexture : public Object {
+public:
+	LayeredTexture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+	virtual ~LayeredTexture();
+
+	// Can only be called after construction of the layered texture object due to construction flag.
+	void fillTexture(const Document &doc);
+
+	enum BlendMode {
+		BlendMode_Translucent,
+		BlendMode_Additive,
+		BlendMode_Modulate,
+		BlendMode_Modulate2,
+		BlendMode_Over,
+		BlendMode_Normal,
+		BlendMode_Dissolve,
+		BlendMode_Darken,
+		BlendMode_ColorBurn,
+		BlendMode_LinearBurn,
+		BlendMode_DarkerColor,
+		BlendMode_Lighten,
+		BlendMode_Screen,
+		BlendMode_ColorDodge,
+		BlendMode_LinearDodge,
+		BlendMode_LighterColor,
+		BlendMode_SoftLight,
+		BlendMode_HardLight,
+		BlendMode_VividLight,
+		BlendMode_LinearLight,
+		BlendMode_PinLight,
+		BlendMode_HardMix,
+		BlendMode_Difference,
+		BlendMode_Exclusion,
+		BlendMode_Subtract,
+		BlendMode_Divide,
+		BlendMode_Hue,
+		BlendMode_Saturation,
+		BlendMode_Color,
+		BlendMode_Luminosity,
+		BlendMode_Overlay,
+		BlendMode_BlendModeCount
+	};
+
+	const Texture *getTexture(int index = 0) const {
+		return textures[index];
+	}
+	int textureCount() const {
+		return static_cast<int>(textures.size());
+	}
+	BlendMode GetBlendMode() const {
+		return blendMode;
+	}
+	float Alpha() {
+		return alpha;
+	}
+
+private:
+	std::vector<const Texture *> textures;
+	BlendMode blendMode;
+	float alpha;
+};
+
+typedef std::map<std::string, const Texture *> TextureMap;
+typedef std::map<std::string, const LayeredTexture *> LayeredTextureMap;
+
+/** DOM class for generic FBX videos */
+class Video : public Object {
+public:
+	Video(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~Video();
+
+	const std::string &Type() const {
+		return type;
+	}
+
+	bool IsEmbedded() const {
+		return contentLength > 0;
+	}
+
+	const std::string &FileName() const {
+		return fileName;
+	}
+
+	const std::string &RelativeFilename() const {
+		return relativeFileName;
+	}
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+	const uint8_t *Content() const {
+		return content;
+	}
+
+	uint64_t ContentLength() const {
+		return contentLength;
+	}
+
+	uint8_t *RelinquishContent() {
+		uint8_t *ptr = content;
+		content = 0;
+		return ptr;
+	}
+
+	bool operator==(const Video &other) const {
+		return (
+				type == other.type && relativeFileName == other.relativeFileName && fileName == other.fileName);
+	}
+
+	bool operator<(const Video &other) const {
+		return std::tie(type, relativeFileName, fileName) < std::tie(other.type, other.relativeFileName, other.fileName);
+	}
+
+private:
+	std::string type;
+	std::string relativeFileName;
+	std::string fileName;
+	const PropertyTable *props = nullptr;
+
+	uint64_t contentLength = 0;
+	uint8_t *content = nullptr;
+};
+
+/** DOM class for generic FBX materials */
+class Material : public Object {
+public:
+	Material(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~Material();
+
+	const std::string &GetShadingModel() const {
+		return shading;
+	}
+
+	bool IsMultilayer() const {
+		return multilayer;
+	}
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+	const TextureMap &Textures() const {
+		return textures;
+	}
+
+	const LayeredTextureMap &LayeredTextures() const {
+		return layeredTextures;
+	}
+
+private:
+	std::string shading;
+	bool multilayer;
+	const PropertyTable *props;
+
+	TextureMap textures;
+	LayeredTextureMap layeredTextures;
+};
+
+// signed int keys (this can happen!)
+typedef std::vector<int64_t> KeyTimeList;
+typedef std::vector<float> KeyValueList;
+
+/** Represents a FBX animation curve (i.e. a 1-dimensional set of keyframes and values therefor) */
+class AnimationCurve : public Object {
+public:
+	AnimationCurve(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc);
+	virtual ~AnimationCurve();
+
+	/** get list of keyframe positions (time).
+     *  Invariant: |GetKeys()| > 0 */
+	const KeyTimeList &GetKeys() const {
+		return keys;
+	}
+
+	/** get list of keyframe values.
+      * Invariant: |GetKeys()| == |GetValues()| && |GetKeys()| > 0*/
+	const KeyValueList &GetValues() const {
+		return values;
+	}
+
+	const std::map<int64_t, float> &GetValueTimeTrack() const {
+		return keyvalues;
+	}
+
+	const std::vector<float> &GetAttributes() const {
+		return attributes;
+	}
+
+	const std::vector<unsigned int> &GetFlags() const {
+		return flags;
+	}
+
+private:
+	KeyTimeList keys;
+	KeyValueList values;
+	std::vector<float> attributes;
+	std::map<int64_t, float> keyvalues;
+	std::vector<unsigned int> flags;
+};
+
+/* Typedef for pointers for the animation handler */
+typedef std::shared_ptr<AnimationCurve> AnimationCurvePtr;
+typedef std::weak_ptr<AnimationCurve> AnimationCurveWeakPtr;
+typedef std::map<std::string, const AnimationCurve *> AnimationMap;
+
+/* Animation Curve node ptr */
+typedef std::shared_ptr<AnimationCurveNode> AnimationCurveNodePtr;
+typedef std::weak_ptr<AnimationCurveNode> AnimationCurveNodeWeakPtr;
+
+/** Represents a FBX animation curve (i.e. a mapping from single animation curves to nodes) */
+class AnimationCurveNode : public Object {
+public:
+	/* the optional white list specifies a list of property names for which the caller
+    wants animations for. If the curve node does not match one of these, std::range_error
+    will be thrown. */
+	AnimationCurveNode(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc,
+			const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0);
+
+	virtual ~AnimationCurveNode();
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+	const AnimationMap &Curves() const;
+
+	/** Object the curve is assigned to, this can be NULL if the
+     *  target object has no DOM representation or could not
+     *  be read for other reasons.*/
+	Object *Target() const {
+		return target;
+	}
+
+	Model *TargetAsModel() const {
+		return dynamic_cast<Model *>(target);
+	}
+
+	NodeAttribute *TargetAsNodeAttribute() const {
+		return dynamic_cast<NodeAttribute *>(target);
+	}
+
+	/** Property of Target() that is being animated*/
+	const std::string &TargetProperty() const {
+		return prop;
+	}
+
+private:
+	Object *target = nullptr;
+	const PropertyTable *props;
+	mutable AnimationMap curves;
+	std::string prop;
+	const Document &doc;
+};
+
+typedef std::vector<const AnimationCurveNode *> AnimationCurveNodeList;
+
+typedef std::shared_ptr<AnimationLayer> AnimationLayerPtr;
+typedef std::weak_ptr<AnimationLayer> AnimationLayerWeakPtr;
+typedef std::vector<const AnimationLayer *> AnimationLayerList;
+
+/** Represents a FBX animation layer (i.e. a list of node animations) */
+class AnimationLayer : public Object {
+public:
+	AnimationLayer(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc);
+	virtual ~AnimationLayer();
+
+	const PropertyTable *Props() const {
+		//ai_assert(props.get());
+		return props;
+	}
+
+	/* the optional white list specifies a list of property names for which the caller
+    wants animations for. Curves not matching this list will not be added to the
+    animation layer. */
+	const AnimationCurveNodeList Nodes(const char *const *target_prop_whitelist = nullptr, size_t whitelist_size = 0) const;
+
+private:
+	const PropertyTable *props;
+	const Document &doc;
+};
+
+/** Represents a FBX animation stack (i.e. a list of animation layers) */
+class AnimationStack : public Object {
+public:
+	AnimationStack(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc);
+	virtual ~AnimationStack();
+
+	fbx_simple_property(LocalStart, int64_t, 0L);
+	fbx_simple_property(LocalStop, int64_t, 0L);
+	fbx_simple_property(ReferenceStart, int64_t, 0L);
+	fbx_simple_property(ReferenceStop, int64_t, 0L);
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+	const AnimationLayerList &Layers() const {
+		return layers;
+	}
+
+private:
+	const PropertyTable *props = nullptr;
+	AnimationLayerList layers;
+};
+
+/** DOM class for deformers */
+class Deformer : public Object {
+public:
+	Deformer(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+	virtual ~Deformer();
+
+	const PropertyTable *Props() const {
+		//ai_assert(props.get());
+		return props;
+	}
+
+private:
+	const PropertyTable *props;
+};
+
+/** Constraints are from Maya they can help us with BoneAttachments :) **/
+class Constraint : public Object {
+public:
+	Constraint(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+	virtual ~Constraint();
+
+private:
+	const PropertyTable *props;
+};
+
+typedef std::vector<float> WeightArray;
+typedef std::vector<unsigned int> WeightIndexArray;
+
+/** DOM class for BlendShapeChannel deformers */
+class BlendShapeChannel : public Deformer {
+public:
+	BlendShapeChannel(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~BlendShapeChannel();
+
+	float DeformPercent() const {
+		return percent;
+	}
+
+	const WeightArray &GetFullWeights() const {
+		return fullWeights;
+	}
+
+	const std::vector<const ShapeGeometry *> &GetShapeGeometries() const {
+		return shapeGeometries;
+	}
+
+private:
+	float percent;
+	WeightArray fullWeights;
+	std::vector<const ShapeGeometry *> shapeGeometries;
+};
+
+/** DOM class for BlendShape deformers */
+class BlendShape : public Deformer {
+public:
+	BlendShape(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~BlendShape();
+
+	const std::vector<const BlendShapeChannel *> &BlendShapeChannels() const {
+		return blendShapeChannels;
+	}
+
+private:
+	std::vector<const BlendShapeChannel *> blendShapeChannels;
+};
+
+/** DOM class for skin deformer clusters (aka sub-deformers) */
+class Cluster : public Deformer {
+public:
+	Cluster(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~Cluster();
+
+	/** get the list of deformer weights associated with this cluster.
+     *  Use #GetIndices() to get the associated vertices. Both arrays
+     *  have the same size (and may also be empty). */
+	const std::vector<float> &GetWeights() const {
+		return weights;
+	}
+
+	/** get indices into the vertex data of the geometry associated
+     *  with this cluster. Use #GetWeights() to get the associated weights.
+     *  Both arrays have the same size (and may also be empty). */
+	const std::vector<unsigned int> &GetIndices() const {
+		return indices;
+	}
+
+	/** */
+	const Transform &GetTransform() const {
+		return transform;
+	}
+
+	const Transform &TransformLink() const {
+		return transformLink;
+	}
+
+	const Model *TargetNode() const {
+		return node;
+	}
+
+	const Transform &TransformAssociateModel() const {
+		return transformAssociateModel;
+	}
+
+	bool TransformAssociateModelValid() const {
+		return valid_transformAssociateModel;
+	}
+
+	// property is not in the fbx file
+	// if the cluster has an associate model
+	// we then have an additive type
+	enum SkinLinkMode {
+		SkinLinkMode_Normalized = 0,
+		SkinLinkMode_Additive = 1
+	};
+
+	SkinLinkMode GetLinkMode() {
+		return link_mode;
+	}
+
+private:
+	std::vector<float> weights;
+	std::vector<unsigned int> indices;
+
+	Transform transform;
+	Transform transformLink;
+	Transform transformAssociateModel;
+	SkinLinkMode link_mode;
+	bool valid_transformAssociateModel;
+	const Model *node = nullptr;
+};
+
+/** DOM class for skin deformers */
+class Skin : public Deformer {
+public:
+	Skin(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name);
+
+	virtual ~Skin();
+
+	float DeformAccuracy() const {
+		return accuracy;
+	}
+
+	const std::vector<const Cluster *> &Clusters() const {
+		return clusters;
+	}
+
+	enum SkinType {
+		Skin_Rigid = 0,
+		Skin_Linear,
+		Skin_DualQuaternion,
+		Skin_Blend
+	};
+
+	const SkinType &GetSkinType() const {
+		return skinType;
+	}
+
+private:
+	float accuracy;
+	SkinType skinType;
+	std::vector<const Cluster *> clusters;
+};
+
+/** Represents a link between two FBX objects. */
+class Connection {
+public:
+	Connection(uint64_t insertionOrder, uint64_t src, uint64_t dest, const std::string &prop, const Document &doc);
+	~Connection();
+
+	// note: a connection ensures that the source and dest objects exist, but
+	// not that they have DOM representations, so the return value of one of
+	// these functions can still be NULL.
+	Object *SourceObject() const;
+	Object *DestinationObject() const;
+
+	// these, however, are always guaranteed to be valid
+	LazyObject *LazySourceObject() const;
+	LazyObject *LazyDestinationObject() const;
+
+	/** return the name of the property the connection is attached to.
+      * this is an empty string for object to object (OO) connections. */
+	const std::string &PropertyName() const {
+		return prop;
+	}
+
+	uint64_t InsertionOrder() const {
+		return insertionOrder;
+	}
+
+	int CompareTo(const Connection *c) const {
+		//ai_assert(nullptr != c);
+
+		// note: can't subtract because this would overflow uint64_t
+		if (InsertionOrder() > c->InsertionOrder()) {
+			return 1;
+		} else if (InsertionOrder() < c->InsertionOrder()) {
+			return -1;
+		}
+		return 0;
+	}
+
+	bool Compare(const Connection *c) const {
+		//ai_assert(nullptr != c);
+
+		return InsertionOrder() < c->InsertionOrder();
+	}
+
+public:
+	uint64_t insertionOrder;
+	const std::string prop;
+
+	uint64_t src, dest;
+	const Document &doc;
+};
+
+// XXX again, unique_ptr would be useful. shared_ptr is too
+// bloated since the objects have a well-defined single owner
+// during their entire lifetime (Document). FBX files have
+// up to many thousands of objects (most of which we never use),
+// so the memory overhead for them should be kept at a minimum.
+typedef std::map<uint64_t, LazyObject *> ObjectMap;
+typedef std::map<std::string, const PropertyTable *> PropertyTemplateMap;
+typedef std::multimap<uint64_t, const Connection *> ConnectionMap;
+
+/** DOM class for global document settings, a single instance per document can
+ *  be accessed via Document.Globals(). */
+class FileGlobalSettings {
+public:
+	FileGlobalSettings(const Document &doc, const PropertyTable *props);
+
+	~FileGlobalSettings();
+
+	const PropertyTable *Props() const {
+		return props;
+	}
+
+	const Document &GetDocument() const {
+		return doc;
+	}
+
+	fbx_simple_property(UpAxis, int, 1);
+	fbx_simple_property(UpAxisSign, int, 1);
+	fbx_simple_property(FrontAxis, int, 2);
+	fbx_simple_property(FrontAxisSign, int, 1);
+	fbx_simple_property(CoordAxis, int, 0);
+	fbx_simple_property(CoordAxisSign, int, 1);
+	fbx_simple_property(OriginalUpAxis, int, 0);
+	fbx_simple_property(OriginalUpAxisSign, int, 1);
+	fbx_simple_property(UnitScaleFactor, float, 1);
+	fbx_simple_property(OriginalUnitScaleFactor, float, 1);
+	fbx_simple_property(AmbientColor, Vector3, Vector3(0, 0, 0));
+	fbx_simple_property(DefaultCamera, std::string, "");
+
+	enum FrameRate {
+		FrameRate_DEFAULT = 0,
+		FrameRate_120 = 1,
+		FrameRate_100 = 2,
+		FrameRate_60 = 3,
+		FrameRate_50 = 4,
+		FrameRate_48 = 5,
+		FrameRate_30 = 6,
+		FrameRate_30_DROP = 7,
+		FrameRate_NTSC_DROP_FRAME = 8,
+		FrameRate_NTSC_FULL_FRAME = 9,
+		FrameRate_PAL = 10,
+		FrameRate_CINEMA = 11,
+		FrameRate_1000 = 12,
+		FrameRate_CINEMA_ND = 13,
+		FrameRate_CUSTOM = 14,
+
+		FrameRate_MAX // end-of-enum sentinel
+	};
+
+	fbx_simple_enum_property(TimeMode, FrameRate, FrameRate_DEFAULT);
+	fbx_simple_property(TimeSpanStart, uint64_t, 0L);
+	fbx_simple_property(TimeSpanStop, uint64_t, 0L);
+	fbx_simple_property(CustomFrameRate, float, -1.0f);
+
+private:
+	const PropertyTable *props = nullptr;
+	const Document &doc;
+};
+
+/** DOM root for a FBX file */
+class Document {
+public:
+	Document(const Parser &parser, const ImportSettings &settings);
+
+	~Document();
+
+	LazyObject *GetObject(uint64_t id) const;
+
+	bool IsSafeToImport() const {
+		return SafeToImport;
+	}
+
+	bool IsBinary() const {
+		return parser.IsBinary();
+	}
+
+	unsigned int FBXVersion() const {
+		return fbxVersion;
+	}
+
+	const std::string &Creator() const {
+		return creator;
+	}
+
+	// elements (in this order): Year, Month, Day, Hour, Second, Millisecond
+	const unsigned int *CreationTimeStamp() const {
+		return creationTimeStamp;
+	}
+
+	const FileGlobalSettings *GlobalSettingsPtr() const {
+		return globals.get();
+	}
+
+	const PropertyTable *GetMetadataProperties() const {
+		return metadata_properties;
+	}
+
+	const PropertyTemplateMap &Templates() const {
+		return templates;
+	}
+
+	const ObjectMap &Objects() const {
+		return objects;
+	}
+
+	const ImportSettings &Settings() const {
+		return settings;
+	}
+
+	const ConnectionMap &ConnectionsBySource() const {
+		return src_connections;
+	}
+
+	const ConnectionMap &ConnectionsByDestination() const {
+		return dest_connections;
+	}
+
+	// note: the implicit rule in all DOM classes is to always resolve
+	// from destination to source (since the FBX object hierarchy is,
+	// with very few exceptions, a DAG, this avoids cycles). In all
+	// cases that may involve back-facing edges in the object graph,
+	// use LazyObject::IsBeingConstructed() to check.
+
+	std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source) const;
+	std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest) const;
+
+	std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source, const char *classname) const;
+	std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest, const char *classname) const;
+
+	std::vector<const Connection *> GetConnectionsBySourceSequenced(uint64_t source,
+			const char *const *classnames, size_t count) const;
+	std::vector<const Connection *> GetConnectionsByDestinationSequenced(uint64_t dest,
+			const char *const *classnames,
+			size_t count) const;
+
+	const std::vector<const AnimationStack *> &AnimationStacks() const;
+	const std::vector<uint64_t> &GetAnimationStackIDs() const {
+		return animationStacks;
+	}
+
+	const std::vector<uint64_t> &GetConstraintStackIDs() const {
+		return constraints;
+	}
+
+	const std::vector<uint64_t> &GetBindPoseIDs() const {
+		return bind_poses;
+	};
+
+	const std::vector<uint64_t> &GetMaterialIDs() const {
+		return materials;
+	};
+
+	const std::vector<uint64_t> &GetSkinIDs() const {
+		return skins;
+	}
+
+private:
+	std::vector<const Connection *> GetConnectionsSequenced(uint64_t id, const ConnectionMap &) const;
+	std::vector<const Connection *> GetConnectionsSequenced(uint64_t id, bool is_src,
+			const ConnectionMap &,
+			const char *const *classnames,
+			size_t count) const;
+	bool ReadHeader();
+	void ReadObjects();
+	void ReadPropertyTemplates();
+	void ReadConnections();
+	void ReadGlobalSettings();
+
+private:
+	const ImportSettings &settings;
+
+	ObjectMap objects;
+	const Parser &parser;
+	bool SafeToImport = false;
+
+	PropertyTemplateMap templates;
+	ConnectionMap src_connections;
+	ConnectionMap dest_connections;
+
+	unsigned int fbxVersion = 0;
+	std::string creator;
+	unsigned int creationTimeStamp[7] = { 0 };
+
+	std::vector<uint64_t> animationStacks;
+	std::vector<uint64_t> bind_poses;
+	// constraints aren't in the tree / at least they are not easy to access.
+	std::vector<uint64_t> constraints;
+	std::vector<uint64_t> materials;
+	std::vector<uint64_t> skins;
+	mutable std::vector<const AnimationStack *> animationStacksResolved;
+	PropertyTable *metadata_properties = nullptr;
+	std::shared_ptr<FileGlobalSettings> globals = nullptr;
+};
+} // namespace FBXDocParser
+
+namespace std {
+template <>
+struct hash<const FBXDocParser::Video> {
+	std::size_t operator()(const FBXDocParser::Video &video) const {
+		using std::hash;
+		using std::size_t;
+		using std::string;
+
+		size_t res = 17;
+		res = res * 31 + hash<string>()(video.Name());
+		res = res * 31 + hash<string>()(video.RelativeFilename());
+		res = res * 31 + hash<string>()(video.Type());
+
+		return res;
+	}
+};
+} // namespace std
+
+#endif // FBX_DOCUMENT_H

+ 172 - 0
modules/fbx/fbx_parser/FBXDocumentUtil.cpp

@@ -0,0 +1,172 @@
+/*************************************************************************/
+/*  FBXDocumentUtil.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXDocumentUtil.cpp
+ *  @brief Implementation of the FBX DOM utility functions declared in FBXDocumentUtil.h
+ */
+
+#include "FBXDocumentUtil.h"
+#include "FBXDocument.h"
+#include "FBXParser.h"
+#include "FBXProperties.h"
+#include "FBXUtil.h"
+#include "core/string/print_string.h"
+
+namespace FBXDocParser {
+namespace Util {
+
+void DOMError(const std::string &message) {
+	print_error("[FBX-DOM]" + String(message.c_str()));
+}
+
+void DOMError(const std::string &message, const Token *token) {
+	print_error("[FBX-DOM]" + String(message.c_str()) + ";" + String(token->StringContents().c_str()));
+}
+
+void DOMError(const std::string &message, const std::shared_ptr<Token> token) {
+	print_error("[FBX-DOM]" + String(message.c_str()) + ";" + String(token->StringContents().c_str()));
+}
+
+void DOMError(const std::string &message, const Element *element /*= NULL*/) {
+	if (element) {
+		DOMError(message, element->KeyToken());
+	}
+	print_error("[FBX-DOM] " + String(message.c_str()));
+}
+
+void DOMError(const std::string &message, const std::shared_ptr<Element> element /*= NULL*/) {
+	if (element) {
+		DOMError(message, element->KeyToken());
+	}
+	print_error("[FBX-DOM] " + String(message.c_str()));
+}
+
+void DOMWarning(const std::string &message) {
+	print_verbose("[FBX-DOM] warning:" + String(message.c_str()));
+}
+
+void DOMWarning(const std::string &message, const Token *token) {
+	print_verbose("[FBX-DOM] warning:" + String(message.c_str()) + ";" + String(token->StringContents().c_str()));
+}
+
+void DOMWarning(const std::string &message, const Element *element /*= NULL*/) {
+	if (element) {
+		DOMWarning(message, element->KeyToken());
+		return;
+	}
+	print_verbose("[FBX-DOM] warning:" + String(message.c_str()));
+}
+
+void DOMWarning(const std::string &message, const std::shared_ptr<Token> token) {
+	print_verbose("[FBX-DOM] warning:" + String(message.c_str()) + ";" + String(token->StringContents().c_str()));
+}
+
+void DOMWarning(const std::string &message, const std::shared_ptr<Element> element /*= NULL*/) {
+	if (element) {
+		DOMWarning(message, element->KeyToken());
+		return;
+	}
+	print_verbose("[FBX-DOM] warning:" + String(message.c_str()));
+}
+
+// ------------------------------------------------------------------------------------------------
+// fetch a property table and the corresponding property template
+const PropertyTable *GetPropertyTable(const Document &doc,
+		const std::string &templateName,
+		const ElementPtr element,
+		const ScopePtr sc,
+		bool no_warn /*= false*/) {
+	// todo: make this an abstraction
+	const ElementPtr Properties70 = sc->GetElement("Properties70");
+	const PropertyTable *templateProps = static_cast<const PropertyTable *>(nullptr);
+
+	if (templateName.length()) {
+		PropertyTemplateMap::const_iterator it = doc.Templates().find(templateName);
+		if (it != doc.Templates().end()) {
+			templateProps = (*it).second;
+		}
+	}
+
+	if (!Properties70 || !Properties70->Compound()) {
+		if (!no_warn) {
+			DOMWarning("property table (Properties70) not found", element);
+		}
+		if (templateProps) {
+			return templateProps;
+		} else {
+			return new const PropertyTable();
+		}
+	}
+
+	return new PropertyTable(Properties70, templateProps);
+}
+} // namespace Util
+} // namespace FBXDocParser

+ 141 - 0
modules/fbx/fbx_parser/FBXDocumentUtil.h

@@ -0,0 +1,141 @@
+/*************************************************************************/
+/*  FBXDocumentUtil.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2012, assimp team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXDocumentUtil.h
+ *  @brief FBX internal utilities used by the DOM reading code
+ */
+#ifndef FBX_DOCUMENT_UTIL_H
+#define FBX_DOCUMENT_UTIL_H
+
+#include "FBXDocument.h"
+#include <memory>
+#include <string>
+
+struct Token;
+struct Element;
+
+namespace FBXDocParser {
+namespace Util {
+
+// Parser errors
+void DOMError(const std::string &message);
+void DOMError(const std::string &message, const Token *token);
+void DOMError(const std::string &message, const Element *element);
+void DOMError(const std::string &message, const std::shared_ptr<Element> element);
+void DOMError(const std::string &message, const std::shared_ptr<Token> token);
+
+// Parser warnings
+void DOMWarning(const std::string &message);
+void DOMWarning(const std::string &message, const Token *token);
+void DOMWarning(const std::string &message, const Element *element);
+void DOMWarning(const std::string &message, const std::shared_ptr<Token> token);
+void DOMWarning(const std::string &message, const std::shared_ptr<Element> element);
+
+// fetch a property table and the corresponding property template
+const PropertyTable *GetPropertyTable(const Document &doc,
+		const std::string &templateName,
+		const ElementPtr element,
+		const ScopePtr sc,
+		bool no_warn = false);
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+const T *ProcessSimpleConnection(const Connection &con,
+		bool is_object_property_conn,
+		const char *name,
+		const ElementPtr element,
+		const char **propNameOut = nullptr) {
+	if (is_object_property_conn && !con.PropertyName().length()) {
+		DOMWarning("expected incoming " + std::string(name) +
+						   " link to be an object-object connection, ignoring",
+				element);
+		return nullptr;
+	} else if (!is_object_property_conn && con.PropertyName().length()) {
+		DOMWarning("expected incoming " + std::string(name) +
+						   " link to be an object-property connection, ignoring",
+				element);
+		return nullptr;
+	}
+
+	if (is_object_property_conn && propNameOut) {
+		// note: this is ok, the return value of PropertyValue() is guaranteed to
+		// remain valid and unchanged as long as the document exists.
+		*propNameOut = con.PropertyName().c_str();
+	}
+
+	// Cast Object to AnimationPlayer for example using safe functions, which return nullptr etc
+	Object *ob = con.SourceObject();
+	ERR_FAIL_COND_V_MSG(!ob, nullptr, "Failed to load object from SourceObject ptr");
+	return dynamic_cast<const T *>(ob);
+}
+} // namespace Util
+} // namespace FBXDocParser
+
+#endif // FBX_DOCUMENT_UTIL_H

+ 173 - 0
modules/fbx/fbx_parser/FBXImportSettings.h

@@ -0,0 +1,173 @@
+/*************************************************************************/
+/*  FBXImportSettings.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXImportSettings.h
+ *  @brief FBX importer runtime configuration
+ */
+#ifndef FBX_IMPORT_SETTINGS_H
+#define FBX_IMPORT_SETTINGS_H
+
+namespace FBXDocParser {
+
+/** FBX import settings, parts of which are publicly accessible via their corresponding AI_CONFIG constants */
+struct ImportSettings {
+	ImportSettings() :
+			strictMode(true), readAllLayers(true), readAllMaterials(true), readMaterials(true), readTextures(true), readCameras(true), readLights(true), readAnimations(true), readWeights(true), preservePivots(true), optimizeEmptyAnimationCurves(true), useLegacyEmbeddedTextureNaming(false), removeEmptyBones(true), convertToMeters(false) {
+		// empty
+	}
+
+	/** enable strict mode:
+     *   - only accept fbx 2012, 2013 files
+     *   - on the slightest error, give up.
+     *
+     *  Basically, strict mode means that the fbx file will actually
+     *  be validated. Strict mode is off by default. */
+	bool strictMode;
+
+	/** specifies whether all geometry layers are read and scanned for
+      * usable data channels. The FBX spec indicates that many readers
+      * will only read the first channel and that this is in some way
+      * the recommended way- in reality, however, it happens a lot that
+      * vertex data is spread among multiple layers. The default
+      * value for this option is true.*/
+	bool readAllLayers;
+
+	/** specifies whether all materials are read, or only those that
+     *  are referenced by at least one mesh. Reading all materials
+     *  may make FBX reading a lot slower since all objects
+     *  need to be processed .
+     *  This bit is ignored unless readMaterials=true*/
+	bool readAllMaterials;
+
+	/** import materials (true) or skip them and assign a default
+     *  material. The default value is true.*/
+	bool readMaterials;
+
+	/** import embedded textures? Default value is true.*/
+	bool readTextures;
+
+	/** import cameras? Default value is true.*/
+	bool readCameras;
+
+	/** import light sources? Default value is true.*/
+	bool readLights;
+
+	/** import animations (i.e. animation curves, the node
+     *  skeleton is always imported). Default value is true. */
+	bool readAnimations;
+
+	/** read bones (vertex weights and deform info).
+     *  Default value is true. */
+	bool readWeights;
+
+	/** preserve transformation pivots and offsets. Since these can
+     *  not directly be represented in assimp, additional dummy
+     *  nodes will be generated. Note that settings this to false
+     *  can make animation import a lot slower. The default value
+     *  is true.
+     *
+     *  The naming scheme for the generated nodes is:
+     *    <OriginalName>_$AssimpFbx$_<TransformName>
+     *
+     *  where <TransformName> is one of
+     *    RotationPivot
+     *    RotationOffset
+     *    PreRotation
+     *    PostRotation
+     *    ScalingPivot
+     *    ScalingOffset
+     *    Translation
+     *    Scaling
+     *    Rotation
+     **/
+	bool preservePivots;
+
+	/** do not import animation curves that specify a constant
+     *  values matching the corresponding node transformation.
+     *  The default value is true. */
+	bool optimizeEmptyAnimationCurves;
+
+	/** use legacy naming for embedded textures eg: (*0, *1, *2)
+    */
+	bool useLegacyEmbeddedTextureNaming;
+
+	/** Empty bones shall be removed
+    */
+	bool removeEmptyBones;
+
+	/** Set to true to perform a conversion from cm to meter after the import
+    */
+	bool convertToMeters;
+};
+} // namespace FBXDocParser
+
+#endif // FBX_IMPORT_SETTINGS_H

+ 407 - 0
modules/fbx/fbx_parser/FBXMaterial.cpp

@@ -0,0 +1,407 @@
+/*************************************************************************/
+/*  FBXMaterial.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2020, assimp team
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXMaterial.cpp
+ *  @brief Assimp::FBX::Material and Assimp::FBX::Texture implementation
+ */
+
+#include "ByteSwapper.h"
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXImportSettings.h"
+#include "FBXParser.h"
+#include "FBXProperties.h"
+
+#include "FBXUtil.h"
+#include <algorithm> // std::transform
+
+namespace FBXDocParser {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Material::Material(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	const ElementPtr ShadingModel = sc->GetElement("ShadingModel");
+	const ElementPtr MultiLayer = sc->GetElement("MultiLayer");
+
+	if (MultiLayer) {
+		multilayer = !!ParseTokenAsInt(GetRequiredToken(MultiLayer, 0));
+	}
+
+	if (ShadingModel) {
+		shading = ParseTokenAsString(GetRequiredToken(ShadingModel, 0));
+	} else {
+		DOMWarning("shading mode not specified, assuming phong", element);
+		shading = "phong";
+	}
+
+	std::string templateName;
+
+	if (shading == "phong") {
+		templateName = "Material.Phong";
+	} else if (shading == "lambert") {
+		templateName = "Material.Lambert";
+	} else if (shading == "unknown") {
+		templateName = "Material.StingRay";
+	} else {
+		DOMWarning("shading mode not recognized: " + shading, element);
+	}
+
+	props = GetPropertyTable(doc, templateName, element, sc);
+
+	// resolve texture links
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID());
+	for (const Connection *con : conns) {
+		// texture link to properties, not objects
+		if (!con->PropertyName().length()) {
+			continue;
+		}
+
+		Object *ob = con->SourceObject();
+		if (!ob) {
+			DOMWarning("failed to read source object for texture link, ignoring", element);
+			continue;
+		}
+
+		const Texture *tex = dynamic_cast<const Texture *>(ob);
+		if (!tex) {
+			LayeredTexture *layeredTexture = dynamic_cast<LayeredTexture *>(ob);
+
+			if (!layeredTexture) {
+				DOMWarning("source object for texture link is not a texture or layered texture, ignoring", element);
+				continue;
+			}
+
+			const std::string &prop = con->PropertyName();
+			if (layeredTextures.find(prop) != layeredTextures.end()) {
+				DOMWarning("duplicate layered texture link: " + prop, element);
+			}
+
+			layeredTextures[prop] = layeredTexture;
+			layeredTexture->fillTexture(doc);
+		} else {
+			const std::string &prop = con->PropertyName();
+			if (textures.find(prop) != textures.end()) {
+				DOMWarning("duplicate texture link: " + prop, element);
+			}
+
+			textures[prop] = tex;
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Material::~Material() {
+	if (props != nullptr) {
+		delete props;
+		props = nullptr;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Texture::Texture(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name), uvScaling(1.0f, 1.0f), media(nullptr) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	const ElementPtr Type = sc->GetElement("Type");
+	const ElementPtr FileName = sc->GetElement("FileName");
+	const ElementPtr RelativeFilename = sc->GetElement("RelativeFilename");
+	const ElementPtr ModelUVTranslation = sc->GetElement("ModelUVTranslation");
+	const ElementPtr ModelUVScaling = sc->GetElement("ModelUVScaling");
+	const ElementPtr Texture_Alpha_Source = sc->GetElement("Texture_Alpha_Source");
+	const ElementPtr Cropping = sc->GetElement("Cropping");
+
+	if (Type) {
+		type = ParseTokenAsString(GetRequiredToken(Type, 0));
+	}
+
+	if (FileName) {
+		fileName = ParseTokenAsString(GetRequiredToken(FileName, 0));
+	}
+
+	if (RelativeFilename) {
+		relativeFileName = ParseTokenAsString(GetRequiredToken(RelativeFilename, 0));
+	}
+
+	if (ModelUVTranslation) {
+		uvTrans = Vector2(ParseTokenAsFloat(GetRequiredToken(ModelUVTranslation, 0)),
+				ParseTokenAsFloat(GetRequiredToken(ModelUVTranslation, 1)));
+	}
+
+	if (ModelUVScaling) {
+		uvScaling = Vector2(ParseTokenAsFloat(GetRequiredToken(ModelUVScaling, 0)),
+				ParseTokenAsFloat(GetRequiredToken(ModelUVScaling, 1)));
+	}
+
+	if (Cropping) {
+		crop[0] = ParseTokenAsInt(GetRequiredToken(Cropping, 0));
+		crop[1] = ParseTokenAsInt(GetRequiredToken(Cropping, 1));
+		crop[2] = ParseTokenAsInt(GetRequiredToken(Cropping, 2));
+		crop[3] = ParseTokenAsInt(GetRequiredToken(Cropping, 3));
+	} else {
+		// vc8 doesn't support the crop() syntax in initialization lists
+		// (and vc9 WARNS about the new (i.e. compliant) behaviour).
+		crop[0] = crop[1] = crop[2] = crop[3] = 0;
+	}
+
+	if (Texture_Alpha_Source) {
+		alphaSource = ParseTokenAsString(GetRequiredToken(Texture_Alpha_Source, 0));
+	}
+
+	props = GetPropertyTable(doc, "Texture.FbxFileTexture", element, sc);
+
+	// 3DS Max and FBX SDK use "Scaling" and "Translation" instead of "ModelUVScaling" and "ModelUVTranslation". Use these properties if available.
+	bool ok;
+	const Vector3 &scaling = PropertyGet<Vector3>(props, "Scaling", ok);
+	if (ok) {
+		uvScaling.x = scaling.x;
+		uvScaling.y = scaling.y;
+	}
+
+	const Vector3 &trans = PropertyGet<Vector3>(props, "Translation", ok);
+	if (ok) {
+		uvTrans.x = trans.x;
+		uvTrans.y = trans.y;
+	}
+
+	// resolve video links
+	if (doc.Settings().readTextures) {
+		const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID());
+		for (const Connection *con : conns) {
+			const Object *const ob = con->SourceObject();
+			if (!ob) {
+				DOMWarning("failed to read source object for texture link, ignoring", element);
+				continue;
+			}
+
+			const Video *const video = dynamic_cast<const Video *>(ob);
+			if (video) {
+				media = video;
+			}
+		}
+	}
+}
+
+Texture::~Texture() {
+	if (props != nullptr) {
+		delete props;
+		props = nullptr;
+	}
+}
+
+LayeredTexture::LayeredTexture(uint64_t id, const ElementPtr element, const Document & /*doc*/, const std::string &name) :
+		Object(id, element, name), blendMode(BlendMode_Modulate), alpha(1) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	ElementPtr BlendModes = sc->GetElement("BlendModes");
+	ElementPtr Alphas = sc->GetElement("Alphas");
+
+	if (BlendModes != 0) {
+		blendMode = (BlendMode)ParseTokenAsInt(GetRequiredToken(BlendModes, 0));
+	}
+	if (Alphas != 0) {
+		alpha = ParseTokenAsFloat(GetRequiredToken(Alphas, 0));
+	}
+}
+
+LayeredTexture::~LayeredTexture() {
+}
+
+void LayeredTexture::fillTexture(const Document &doc) {
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID());
+	for (size_t i = 0; i < conns.size(); ++i) {
+		const Connection *con = conns.at(i);
+
+		const Object *const ob = con->SourceObject();
+		if (!ob) {
+			DOMWarning("failed to read source object for texture link, ignoring", element);
+			continue;
+		}
+
+		const Texture *const tex = dynamic_cast<const Texture *>(ob);
+
+		textures.push_back(tex);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Video::Video(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name), contentLength(0), content(0) {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	const ElementPtr Type = sc->GetElement("Type");
+	// File Version 7500 Crashes if this is not checked fully.
+	// As of writing this comment 7700 exists, in August 2020
+	ElementPtr FileName = nullptr;
+	if (HasElement(sc, "Filename")) {
+		FileName = (ElementPtr)sc->GetElement("Filename");
+	} else if (HasElement(sc, "FileName")) {
+		FileName = (ElementPtr)sc->GetElement("FileName");
+	} else {
+		print_error("file has invalid video material returning...");
+		return;
+	}
+	const ElementPtr RelativeFilename = sc->GetElement("RelativeFilename");
+	const ElementPtr Content = sc->GetElement("Content");
+
+	if (Type) {
+		type = ParseTokenAsString(GetRequiredToken(Type, 0));
+	}
+
+	if (FileName) {
+		fileName = ParseTokenAsString(GetRequiredToken(FileName, 0));
+	}
+
+	if (RelativeFilename) {
+		relativeFileName = ParseTokenAsString(GetRequiredToken(RelativeFilename, 0));
+	}
+
+	if (Content && !Content->Tokens().empty()) {
+		//this field is omitted when the embedded texture is already loaded, let's ignore if it's not found
+		try {
+			const Token *token = GetRequiredToken(Content, 0);
+			const char *data = token->begin();
+			if (!token->IsBinary()) {
+				if (*data != '"') {
+					DOMError("embedded content is not surrounded by quotation marks", element);
+				} else {
+					size_t targetLength = 0;
+					auto numTokens = Content->Tokens().size();
+					// First time compute size (it could be large like 64Gb and it is good to allocate it once)
+					for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) {
+						const Token *dataToken = GetRequiredToken(Content, tokenIdx);
+						size_t tokenLength = dataToken->end() - dataToken->begin() - 2; // ignore double quotes
+						const char *base64data = dataToken->begin() + 1;
+						const size_t outLength = Util::ComputeDecodedSizeBase64(base64data, tokenLength);
+						if (outLength == 0) {
+							DOMError("Corrupted embedded content found", element);
+						}
+						targetLength += outLength;
+					}
+					if (targetLength == 0) {
+						DOMError("Corrupted embedded content found", element);
+					} else {
+						content = new uint8_t[targetLength];
+						contentLength = static_cast<uint64_t>(targetLength);
+						size_t dst_offset = 0;
+						for (uint32_t tokenIdx = 0; tokenIdx < numTokens; ++tokenIdx) {
+							const Token *dataToken = GetRequiredToken(Content, tokenIdx);
+							ERR_FAIL_COND(!dataToken);
+							size_t tokenLength = dataToken->end() - dataToken->begin() - 2; // ignore double quotes
+							const char *base64data = dataToken->begin() + 1;
+							dst_offset += Util::DecodeBase64(base64data, tokenLength, content + dst_offset, targetLength - dst_offset);
+						}
+						if (targetLength != dst_offset) {
+							delete[] content;
+							contentLength = 0;
+							DOMError("Corrupted embedded content found", element);
+						}
+					}
+				}
+			} else if (static_cast<size_t>(token->end() - data) < 5) {
+				DOMError("binary data array is too short, need five (5) bytes for type signature and element count", element);
+			} else if (*data != 'R') {
+				DOMWarning("video content is not raw binary data, ignoring", element);
+			} else {
+				// read number of elements
+				uint32_t len = 0;
+				::memcpy(&len, data + 1, sizeof(len));
+				AI_SWAP4(len);
+
+				contentLength = len;
+
+				content = new uint8_t[len];
+				::memcpy(content, data + 5, len);
+			}
+		} catch (...) {
+			//			//we don't need the content data for contents that has already been loaded
+			//			ASSIMP_LOG_VERBOSE_DEBUG_F("Caught exception in FBXMaterial (likely because content was already loaded): ",
+			//									   runtimeError.what());
+		}
+	}
+
+	props = GetPropertyTable(doc, "Video.FbxVideo", element, sc);
+}
+
+Video::~Video() {
+	if (content) {
+		delete[] content;
+	}
+
+	if (props != nullptr) {
+		delete props;
+		props = nullptr;
+	}
+}
+} // namespace FBXDocParser

+ 485 - 0
modules/fbx/fbx_parser/FBXMeshGeometry.cpp

@@ -0,0 +1,485 @@
+/*************************************************************************/
+/*  FBXMeshGeometry.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXMeshGeometry.cpp
+ *  @brief Assimp::FBX::MeshGeometry implementation
+ */
+
+#include <functional>
+
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXImportSettings.h"
+#include "FBXMeshGeometry.h"
+#include "core/math/vector3.h"
+
+namespace FBXDocParser {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Geometry::Geometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
+		Object(id, element, name), skin() {
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), "Deformer");
+	for (const Connection *con : conns) {
+		const Skin *sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element);
+		if (sk) {
+			skin = sk;
+		}
+		const BlendShape *bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry",
+				element);
+		if (bsp) {
+			blendShapes.push_back(bsp);
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Geometry::~Geometry() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<const BlendShape *> &Geometry::get_blend_shapes() const {
+	return blendShapes;
+}
+
+// ------------------------------------------------------------------------------------------------
+const Skin *Geometry::DeformerSkin() const {
+	return skin;
+}
+
+// ------------------------------------------------------------------------------------------------
+MeshGeometry::MeshGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
+		Geometry(id, element, name, doc) {
+	print_verbose("mesh name: " + String(name.c_str()));
+
+	ScopePtr sc = element->Compound();
+	ERR_FAIL_COND_MSG(sc == nullptr, "failed to read geometry, prevented crash");
+	ERR_FAIL_COND_MSG(!HasElement(sc, "Vertices"), "Detected mesh with no vertexes, didn't populate the mesh");
+
+	// must have Mesh elements:
+	const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element);
+	const ElementPtr PolygonVertexIndex = GetRequiredElement(sc, "PolygonVertexIndex", element);
+
+	if (HasElement(sc, "Edges")) {
+		const ElementPtr element_edges = GetRequiredElement(sc, "Edges", element);
+		ParseVectorDataArray(m_edges, element_edges);
+	}
+
+	// read mesh data into arrays
+	ParseVectorDataArray(m_vertices, Vertices);
+	ParseVectorDataArray(m_face_indices, PolygonVertexIndex);
+
+	ERR_FAIL_COND_MSG(m_vertices.empty(), "mesh with no vertexes in FBX file, did you mean to delete it?");
+	ERR_FAIL_COND_MSG(m_face_indices.empty(), "mesh has no faces, was this intended?");
+
+	// Retrieve layer elements, for all of the mesh
+	const ElementCollection &Layer = sc->GetCollection("Layer");
+
+	// Store all layers
+	std::vector<std::tuple<int, std::string>> valid_layers;
+
+	// now read the sub mesh information from the geometry (normals, uvs, etc)
+	for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) {
+		const ScopePtr layer = GetRequiredScope(it->second);
+		const ElementCollection &LayerElement = layer->GetCollection("LayerElement");
+		for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) {
+			std::string layer_name = eit->first;
+			ElementPtr element_layer = eit->second;
+			const ScopePtr layer_element = GetRequiredScope(element_layer);
+
+			// Actual usable 'type' LayerElementUV, LayerElementNormal, etc
+			const ElementPtr Type = GetRequiredElement(layer_element, "Type");
+			const ElementPtr TypedIndex = GetRequiredElement(layer_element, "TypedIndex");
+			const std::string &type = ParseTokenAsString(GetRequiredToken(Type, 0));
+			const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex, 0));
+
+			// we only need the layer name and the typed index.
+			valid_layers.push_back(std::tuple<int, std::string>(typedIndex, type));
+		}
+	}
+
+	// get object / mesh directly from the FBX by the element ID.
+	const ScopePtr top = GetRequiredScope(element);
+
+	// iterate over all layers for the mesh (uvs, normals, smoothing groups, colors, etc)
+	for (size_t x = 0; x < valid_layers.size(); x++) {
+		const int layer_id = std::get<0>(valid_layers[x]);
+		const std::string &layer_type_name = std::get<1>(valid_layers[x]);
+
+		// Get collection of elements from the XLayerMap (example: LayerElementUV)
+		// this must contain our proper elements.
+
+		// This is stupid, because it means we select them ALL not just the one we want.
+		// but it's fine we can match by id.
+
+		const ElementCollection &candidates = top->GetCollection(layer_type_name);
+
+		ElementMap::const_iterator iter;
+		for (iter = candidates.first; iter != candidates.second; ++iter) {
+			const ScopePtr layer_scope = GetRequiredScope(iter->second);
+			TokenPtr layer_token = GetRequiredToken(iter->second, 0);
+			const int index = ParseTokenAsInt(layer_token);
+
+			ERR_FAIL_COND_MSG(layer_scope == nullptr, "prevented crash, layer scope is invalid");
+
+			if (index == layer_id) {
+				const std::string &MappingInformationType = ParseTokenAsString(GetRequiredToken(
+						GetRequiredElement(layer_scope, "MappingInformationType"), 0));
+
+				const std::string &ReferenceInformationType = ParseTokenAsString(GetRequiredToken(
+						GetRequiredElement(layer_scope, "ReferenceInformationType"), 0));
+
+				if (layer_type_name == "LayerElementUV") {
+					if (index == 0) {
+						m_uv_0 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV");
+					} else if (index == 1) {
+						m_uv_1 = resolve_vertex_data_array<Vector2>(layer_scope, MappingInformationType, ReferenceInformationType, "UV");
+					}
+				} else if (layer_type_name == "LayerElementMaterial") {
+					m_material_allocation_ids = resolve_vertex_data_array<int>(layer_scope, MappingInformationType, ReferenceInformationType, "Materials");
+				} else if (layer_type_name == "LayerElementNormal") {
+					m_normals = resolve_vertex_data_array<Vector3>(layer_scope, MappingInformationType, ReferenceInformationType, "Normals");
+				} else if (layer_type_name == "LayerElementColor") {
+					m_colors = resolve_vertex_data_array<Color>(layer_scope, MappingInformationType, ReferenceInformationType, "Colors", "ColorIndex");
+					// NOTE: this is a useful sanity check to ensure you're getting any color data which is not default.
+					//					const Color first_color_check = m_colors.data[0];
+					//					bool colors_are_all_the_same = true;
+					//					size_t i = 1;
+					//					for(i = 1; i < m_colors.data.size(); i++)
+					//					{
+					//						const Color current_color = m_colors.data[i];
+					//						if(current_color.is_equal_approx(first_color_check))
+					//						{
+					//							continue;
+					//						}
+					//						else
+					//						{
+					//							colors_are_all_the_same = false;
+					//							break;
+					//						}
+					//					}
+					//
+					//					if(colors_are_all_the_same)
+					//					{
+					//						print_error("Color serialisation is not working for vertex colors some should be different in the test asset.");
+					//					}
+					//					else
+					//					{
+					//						print_verbose("Color array has unique colors at index: " + itos(i));
+					//					}
+				}
+			}
+		}
+	}
+
+	print_verbose("Mesh statistics \nuv_0: " + m_uv_0.debug_info() + "\nuv_1: " + m_uv_1.debug_info() + "\nvertices: " + itos(m_vertices.size()));
+
+	// Compose the edge of the mesh.
+	// You can see how the edges are stored into the FBX here: https://gist.github.com/AndreaCatania/da81840f5aa3b2feedf189e26c5a87e6
+	for (size_t i = 0; i < m_edges.size(); i += 1) {
+		ERR_FAIL_INDEX_MSG((size_t)m_edges[i], m_face_indices.size(), "The edge is pointing to a weird location in the face indices. The FBX is corrupted.");
+		int polygon_vertex_0 = m_face_indices[m_edges[i]];
+		int polygon_vertex_1;
+		if (polygon_vertex_0 < 0) {
+			// The polygon_vertex_0 points to the end of a polygon, so it's
+			// connected with the beginning of polygon in the edge list.
+
+			// Fist invert the vertex.
+			polygon_vertex_0 = ~polygon_vertex_0;
+
+			// Search the start vertex of the polygon.
+			// Iterate from the polygon_vertex_index backward till the start of
+			// the polygon is found.
+			ERR_FAIL_COND_MSG(m_edges[i] - 1 < 0, "The polygon is not yet started and we already need the final vertex. This FBX is corrupted.");
+			bool found_it = false;
+			for (int x = m_edges[i] - 1; x >= 0; x -= 1) {
+				if (x == 0) {
+					// This for sure is the start.
+					polygon_vertex_1 = m_face_indices[x];
+					found_it = true;
+					break;
+				} else if (m_face_indices[x] < 0) {
+					// This is the end of the previous polygon, so the next is
+					// the start of the polygon we need.
+					polygon_vertex_1 = m_face_indices[x + 1];
+					found_it = true;
+					break;
+				}
+			}
+			// As the algorithm above, this check is useless. Because the first
+			// ever vertex is always considered the begining of a polygon.
+			ERR_FAIL_COND_MSG(found_it == false, "Was not possible to find the first vertex of this polygon. FBX file is corrupted.");
+
+		} else {
+			ERR_FAIL_INDEX_MSG((size_t)(m_edges[i] + 1), m_face_indices.size(), "FBX The other FBX edge seems to point to an invalid vertices. This FBX file is corrupted.");
+			// Take the next vertex
+			polygon_vertex_1 = m_face_indices[m_edges[i] + 1];
+		}
+
+		if (polygon_vertex_1 < 0) {
+			// We don't care if the `polygon_vertex_1` is the end of the polygon,
+			//  for `polygon_vertex_1` so we can just invert it.
+			polygon_vertex_1 = ~polygon_vertex_1;
+		}
+
+		ERR_FAIL_COND_MSG(polygon_vertex_0 == polygon_vertex_1, "The vertices of this edge can't be the same, Is this a point???. This FBX file is corrupted.");
+
+		// Just create the edge.
+		edge_map.push_back({ polygon_vertex_0, polygon_vertex_1 });
+	}
+}
+
+MeshGeometry::~MeshGeometry() {
+	// empty
+}
+
+const std::vector<Vector3> &MeshGeometry::get_vertices() const {
+	return m_vertices;
+}
+
+const std::vector<MeshGeometry::Edge> &MeshGeometry::get_edge_map() const {
+	return edge_map;
+}
+
+const std::vector<int> &MeshGeometry::get_polygon_indices() const {
+	return m_face_indices;
+}
+
+const std::vector<int> &MeshGeometry::get_edges() const {
+	return m_edges;
+}
+
+const MeshGeometry::MappingData<Vector3> &MeshGeometry::get_normals() const {
+	return m_normals;
+}
+
+const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_0() const {
+	//print_verbose("get uv_0 " + m_uv_0.debug_info() );
+	return m_uv_0;
+}
+
+const MeshGeometry::MappingData<Vector2> &MeshGeometry::get_uv_1() const {
+	//print_verbose("get uv_1 " + m_uv_1.debug_info() );
+	return m_uv_1;
+}
+
+const MeshGeometry::MappingData<Color> &MeshGeometry::get_colors() const {
+	return m_colors;
+}
+
+const MeshGeometry::MappingData<int> &MeshGeometry::get_material_allocation_id() const {
+	return m_material_allocation_ids;
+}
+
+int MeshGeometry::get_edge_id(const std::vector<Edge> &p_map, int p_vertex_a, int p_vertex_b) {
+	for (size_t i = 0; i < p_map.size(); i += 1) {
+		if ((p_map[i].vertex_0 == p_vertex_a && p_map[i].vertex_1 == p_vertex_b) || (p_map[i].vertex_1 == p_vertex_a && p_map[i].vertex_0 == p_vertex_b)) {
+			return i;
+		}
+	}
+	return -1;
+}
+
+MeshGeometry::Edge MeshGeometry::get_edge(const std::vector<Edge> &p_map, int p_id) {
+	ERR_FAIL_INDEX_V_MSG((size_t)p_id, p_map.size(), Edge({ -1, -1 }), "ID not found.");
+	return p_map[p_id];
+}
+
+template <class T>
+MeshGeometry::MappingData<T> MeshGeometry::resolve_vertex_data_array(
+		const ScopePtr source,
+		const std::string &MappingInformationType,
+		const std::string &ReferenceInformationType,
+		const std::string &dataElementName,
+		const std::string &indexOverride) {
+	ERR_FAIL_COND_V_MSG(source == nullptr, MappingData<T>(), "Invalid scope operator preventing memory corruption");
+
+	// UVIndex, MaterialIndex, NormalIndex, etc..
+	std::string indexDataElementName;
+
+	if (indexOverride != "") {
+		// Colors should become ColorIndex
+		indexDataElementName = indexOverride;
+	} else {
+		// Some indexes will exist.
+		indexDataElementName = dataElementName + "Index";
+	}
+
+	// goal: expand everything to be per vertex
+
+	ReferenceType l_ref_type = ReferenceType::direct;
+
+	// Read the reference type into the enumeration
+	if (ReferenceInformationType == "IndexToDirect") {
+		l_ref_type = ReferenceType::index_to_direct;
+	} else if (ReferenceInformationType == "Index") {
+		// set non legacy index to direct mapping
+		l_ref_type = ReferenceType::index;
+	} else if (ReferenceInformationType == "Direct") {
+		l_ref_type = ReferenceType::direct;
+	} else {
+		ERR_FAIL_V_MSG(MappingData<T>(), "invalid reference type has the FBX format changed?");
+	}
+
+	MapType l_map_type = MapType::none;
+
+	if (MappingInformationType == "None") {
+		l_map_type = MapType::none;
+	} else if (MappingInformationType == "ByVertice") {
+		l_map_type = MapType::vertex;
+	} else if (MappingInformationType == "ByPolygonVertex") {
+		l_map_type = MapType::polygon_vertex;
+	} else if (MappingInformationType == "ByPolygon") {
+		l_map_type = MapType::polygon;
+	} else if (MappingInformationType == "ByEdge") {
+		l_map_type = MapType::edge;
+	} else if (MappingInformationType == "AllSame") {
+		l_map_type = MapType::all_the_same;
+	} else {
+		print_error("invalid mapping type: " + String(MappingInformationType.c_str()));
+	}
+
+	// create mapping data
+	MeshGeometry::MappingData<T> tempData;
+	tempData.map_type = l_map_type;
+	tempData.ref_type = l_ref_type;
+
+	// parse data into array
+	ParseVectorDataArray(tempData.data, GetRequiredElement(source, dataElementName));
+
+	// index array wont always exist
+	const ElementPtr element = GetOptionalElement(source, indexDataElementName);
+	if (element) {
+		ParseVectorDataArray(tempData.index, element);
+	}
+
+	return tempData;
+}
+// ------------------------------------------------------------------------------------------------
+ShapeGeometry::ShapeGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
+		Geometry(id, element, name, doc) {
+	const ScopePtr sc = element->Compound();
+	if (nullptr == sc) {
+		DOMError("failed to read Geometry object (class: Shape), no data scope found");
+	}
+	const ElementPtr Indexes = GetRequiredElement(sc, "Indexes", element);
+	const ElementPtr Normals = GetRequiredElement(sc, "Normals", element);
+	const ElementPtr Vertices = GetRequiredElement(sc, "Vertices", element);
+	ParseVectorDataArray(m_indices, Indexes);
+	ParseVectorDataArray(m_vertices, Vertices);
+	ParseVectorDataArray(m_normals, Normals);
+}
+
+// ------------------------------------------------------------------------------------------------
+ShapeGeometry::~ShapeGeometry() {
+	// empty
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<Vector3> &ShapeGeometry::GetVertices() const {
+	return m_vertices;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<Vector3> &ShapeGeometry::GetNormals() const {
+	return m_normals;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<unsigned int> &ShapeGeometry::GetIndices() const {
+	return m_indices;
+}
+// ------------------------------------------------------------------------------------------------
+LineGeometry::LineGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc) :
+		Geometry(id, element, name, doc) {
+	const ScopePtr sc = element->Compound();
+	if (!sc) {
+		DOMError("failed to read Geometry object (class: Line), no data scope found");
+	}
+	const ElementPtr Points = GetRequiredElement(sc, "Points", element);
+	const ElementPtr PointsIndex = GetRequiredElement(sc, "PointsIndex", element);
+	ParseVectorDataArray(m_vertices, Points);
+	ParseVectorDataArray(m_indices, PointsIndex);
+}
+
+// ------------------------------------------------------------------------------------------------
+LineGeometry::~LineGeometry() {
+	// empty
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<Vector3> &LineGeometry::GetVertices() const {
+	return m_vertices;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<int> &LineGeometry::GetIndices() const {
+	return m_indices;
+}
+} // namespace FBXDocParser

+ 263 - 0
modules/fbx/fbx_parser/FBXMeshGeometry.h

@@ -0,0 +1,263 @@
+/*************************************************************************/
+/*  FBXMeshGeometry.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+copyright notice, this list of conditions and the
+following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+copyright notice, this list of conditions and the
+following disclaimer in the documentation and/or other
+materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+contributors may be used to endorse or promote products
+derived from this software without specific prior
+written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+#ifndef FBX_MESH_GEOMETRY_H
+#define FBX_MESH_GEOMETRY_H
+
+#include "core/math/color.h"
+#include "core/math/vector2.h"
+#include "core/math/vector3.h"
+#include "core/templates/vector.h"
+
+#include "FBXDocument.h"
+#include "FBXParser.h"
+
+#include <iostream>
+
+#define AI_MAX_NUMBER_OF_TEXTURECOORDS 4
+#define AI_MAX_NUMBER_OF_COLOR_SETS 8
+
+namespace FBXDocParser {
+
+/*
+ * DOM base class for all kinds of FBX geometry
+ */
+class Geometry : public Object {
+public:
+	Geometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc);
+	virtual ~Geometry();
+
+	/** Get the Skin attached to this geometry or NULL */
+	const Skin *DeformerSkin() const;
+
+	const std::vector<const BlendShape *> &get_blend_shapes() const;
+
+	size_t get_blend_shape_count() const {
+		return blendShapes.size();
+	}
+
+private:
+	const Skin *skin = nullptr;
+	std::vector<const BlendShape *> blendShapes;
+};
+
+typedef std::vector<int> MatIndexArray;
+
+/// Map Geometry stores the FBX file information.
+///
+/// # FBX doc.
+/// ## Reference type declared:
+/// 	- Direct (directly related to the mapping information type)
+/// 	- IndexToDirect (Map with key value, meaning depends on the MappingInformationType)
+///
+/// ## Map Type:
+/// 	* None The mapping is undetermined.
+/// 	* ByVertex There will be one mapping coordinate for each surface control point/vertex (ControlPoint is a vertex).
+/// 		* If you have direct reference type verticies[x]
+/// 		* If you have IndexToDirect reference type the UV
+/// 	* ByPolygonVertex There will be one mapping coordinate for each vertex, for every polygon of which it is a part. This means that a vertex will have as many mapping coordinates as polygons of which it is a part. (Sorted by polygon, referencing vertex)
+/// 	* ByPolygon There can be only one mapping coordinate for the whole polygon.
+/// 		* One mapping per polygon polygon x has this normal x
+/// 		* For each vertex of the polygon then set the normal to x
+/// 	* ByEdge There will be one mapping coordinate for each unique edge in the mesh. This is meant to be used with smoothing layer elements. (Mapping is referencing the edge id)
+/// 	* AllSame There can be only one mapping coordinate for the whole surface.
+class MeshGeometry : public Geometry {
+public:
+	enum class MapType {
+		none = 0, // No mapping type. Stored as "None".
+		vertex, // Maps per vertex. Stored as "ByVertice".
+		polygon_vertex, // Maps per polygon vertex. Stored as "ByPolygonVertex".
+		polygon, // Maps per polygon. Stored as "ByPolygon".
+		edge, // Maps per edge. Stored as "ByEdge".
+		all_the_same // Uaps to everything. Stored as "AllSame".
+	};
+
+	enum class ReferenceType {
+		direct = 0,
+		index = 1,
+		index_to_direct = 2
+	};
+
+	template <class T>
+	struct MappingData {
+		MapType map_type = MapType::none;
+		ReferenceType ref_type = ReferenceType::direct;
+		std::vector<T> data;
+		/// The meaning of the indices depends from the `MapType`.
+		/// If `ref_type` is `direct` this map is hollow.
+		std::vector<int> index;
+
+		String debug_info() const {
+			return "indexes: " + itos(index.size()) + " data: " + itos(data.size());
+		}
+	};
+
+	struct Edge {
+		int vertex_0 = 0, vertex_1 = 0;
+		Edge(int v0, int v1) :
+				vertex_0(v0), vertex_1(v1) {}
+		Edge() {}
+	};
+
+public:
+	MeshGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc);
+
+	virtual ~MeshGeometry();
+
+	const std::vector<Vector3> &get_vertices() const;
+	const std::vector<Edge> &get_edge_map() const;
+	const std::vector<int> &get_polygon_indices() const;
+	const std::vector<int> &get_edges() const;
+	const MappingData<Vector3> &get_normals() const;
+	const MappingData<Vector2> &get_uv_0() const;
+	const MappingData<Vector2> &get_uv_1() const;
+	const MappingData<Color> &get_colors() const;
+	const MappingData<int> &get_material_allocation_id() const;
+
+	/// Returns -1 if the vertices doesn't form an edge. Vertex order, doesn't
+	// matter.
+	static int get_edge_id(const std::vector<Edge> &p_map, int p_vertex_a, int p_vertex_b);
+	// Retuns the edge point bu that ID, or the edge with -1 vertices if the
+	// id is not valid.
+	static Edge get_edge(const std::vector<Edge> &p_map, int p_id);
+
+private:
+	// Read directly from the FBX file.
+	std::vector<Vector3> m_vertices;
+	std::vector<Edge> edge_map;
+	std::vector<int> m_face_indices;
+	std::vector<int> m_edges;
+	MappingData<Vector3> m_normals;
+	MappingData<Vector2> m_uv_0; // first uv coordinates
+	MappingData<Vector2> m_uv_1; // second uv coordinates
+	MappingData<Color> m_colors; // colors for the mesh
+	MappingData<int> m_material_allocation_ids; // slot of material used
+
+	template <class T>
+	MappingData<T> resolve_vertex_data_array(
+			const ScopePtr source,
+			const std::string &MappingInformationType,
+			const std::string &ReferenceInformationType,
+			const std::string &dataElementName,
+			const std::string &indexOverride = "");
+};
+
+/*
+ * DOM class for FBX geometry of type "Shape"
+ */
+class ShapeGeometry : public Geometry {
+public:
+	/** The class constructor */
+	ShapeGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc);
+
+	/** The class destructor */
+	virtual ~ShapeGeometry();
+
+	/** Get a list of all vertex points, non-unique*/
+	const std::vector<Vector3> &GetVertices() const;
+
+	/** Get a list of all vertex normals or an empty array if
+    *  no normals are specified. */
+	const std::vector<Vector3> &GetNormals() const;
+
+	/** Return list of vertex indices. */
+	const std::vector<unsigned int> &GetIndices() const;
+
+private:
+	std::vector<Vector3> m_vertices;
+	std::vector<Vector3> m_normals;
+	std::vector<unsigned int> m_indices;
+};
+/**
+*  DOM class for FBX geometry of type "Line"
+*/
+class LineGeometry : public Geometry {
+public:
+	/** The class constructor */
+	LineGeometry(uint64_t id, const ElementPtr element, const std::string &name, const Document &doc);
+
+	/** The class destructor */
+	virtual ~LineGeometry();
+
+	/** Get a list of all vertex points, non-unique*/
+	const std::vector<Vector3> &GetVertices() const;
+
+	/** Return list of vertex indices. */
+	const std::vector<int> &GetIndices() const;
+
+private:
+	std::vector<Vector3> m_vertices;
+	std::vector<int> m_indices;
+};
+} // namespace FBXDocParser
+
+#endif // FBX_MESH_GEOMETRY_H

+ 176 - 0
modules/fbx/fbx_parser/FBXModel.cpp

@@ -0,0 +1,176 @@
+/*************************************************************************/
+/*  FBXModel.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXModel.cpp
+ *  @brief Assimp::FBX::Model implementation
+ */
+
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXMeshGeometry.h"
+#include "FBXParser.h"
+
+namespace FBXDocParser {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Model::Model(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name), shading("Y") {
+	const ScopePtr sc = GetRequiredScope(element);
+	const ElementPtr Shading = sc->GetElement("Shading");
+	const ElementPtr Culling = sc->GetElement("Culling");
+
+	if (Shading) {
+		shading = GetRequiredToken(Shading, 0)->StringContents();
+	}
+
+	if (Culling) {
+		culling = ParseTokenAsString(GetRequiredToken(Culling, 0));
+	}
+
+	props = GetPropertyTable(doc, "Model.FbxNode", element, sc);
+	ResolveLinks(element, doc);
+}
+
+// ------------------------------------------------------------------------------------------------
+Model::~Model() {
+	if (props != nullptr) {
+		delete props;
+		props = nullptr;
+	}
+}
+
+ModelLimbNode::ModelLimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Model(id, element, doc, name){};
+
+ModelLimbNode::~ModelLimbNode() {
+}
+
+// ------------------------------------------------------------------------------------------------
+void Model::ResolveLinks(const ElementPtr element, const Document &doc) {
+	const char *const arr[] = { "Geometry", "Material", "NodeAttribute" };
+
+	// resolve material
+	const std::vector<const Connection *> &conns = doc.GetConnectionsByDestinationSequenced(ID(), arr, 3);
+
+	materials.reserve(conns.size());
+	geometry.reserve(conns.size());
+	attributes.reserve(conns.size());
+	for (const Connection *con : conns) {
+		// material and geometry links should be Object-Object connections
+		if (con->PropertyName().length()) {
+			continue;
+		}
+
+		const Object *const ob = con->SourceObject();
+		if (!ob) {
+			//DOMWarning("failed to read source object for incoming Model link, ignoring",&element);
+			continue;
+		}
+
+		const Material *const mat = dynamic_cast<const Material *>(ob);
+		if (mat) {
+			materials.push_back(mat);
+			continue;
+		}
+
+		const Geometry *const geo = dynamic_cast<const Geometry *>(ob);
+		if (geo) {
+			geometry.push_back(geo);
+			continue;
+		}
+
+		const NodeAttribute *const att = dynamic_cast<const NodeAttribute *>(ob);
+		if (att) {
+			attributes.push_back(att);
+			continue;
+		}
+
+		DOMWarning("source object for model link is neither Material, NodeAttribute nor Geometry, ignoring", element);
+		continue;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+bool Model::IsNull() const {
+	const std::vector<const NodeAttribute *> &attrs = GetAttributes();
+	for (const NodeAttribute *att : attrs) {
+		const Null *null_tag = dynamic_cast<const Null *>(att);
+		if (null_tag) {
+			return true;
+		}
+	}
+
+	return false;
+}
+} // namespace FBXDocParser

+ 183 - 0
modules/fbx/fbx_parser/FBXNodeAttribute.cpp

@@ -0,0 +1,183 @@
+/*************************************************************************/
+/*  FBXNodeAttribute.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXNoteAttribute.cpp
+ *  @brief Assimp::FBX::NodeAttribute (and subclasses) implementation
+ */
+
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXParser.h"
+#include <iostream>
+
+namespace FBXDocParser {
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+NodeAttribute::NodeAttribute(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name), props() {
+	const ScopePtr sc = GetRequiredScope(element);
+
+	const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2));
+
+	// hack on the deriving type but Null/LimbNode attributes are the only case in which
+	// the property table is by design absent and no warning should be generated
+	// for it.
+	const bool is_null_or_limb = !strcmp(classname.c_str(), "Null") || !strcmp(classname.c_str(), "LimbNode");
+	props = GetPropertyTable(doc, "NodeAttribute.Fbx" + classname, element, sc, is_null_or_limb);
+}
+
+// ------------------------------------------------------------------------------------------------
+NodeAttribute::~NodeAttribute() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+CameraSwitcher::CameraSwitcher(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		NodeAttribute(id, element, doc, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+	const ElementPtr CameraId = sc->GetElement("CameraId");
+	const ElementPtr CameraName = sc->GetElement("CameraName");
+	const ElementPtr CameraIndexName = sc->GetElement("CameraIndexName");
+
+	if (CameraId) {
+		cameraId = ParseTokenAsInt(GetRequiredToken(CameraId, 0));
+	}
+
+	if (CameraName) {
+		cameraName = GetRequiredToken(CameraName, 0)->StringContents();
+	}
+
+	if (CameraIndexName && CameraIndexName->Tokens().size()) {
+		cameraIndexName = GetRequiredToken(CameraIndexName, 0)->StringContents();
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+CameraSwitcher::~CameraSwitcher() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Camera::Camera(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		NodeAttribute(id, element, doc, name) {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Camera::~Camera() {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Light::Light(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		NodeAttribute(id, element, doc, name) {
+	// empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Light::~Light() {
+}
+
+// ------------------------------------------------------------------------------------------------
+Null::Null(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		NodeAttribute(id, element, doc, name) {
+}
+
+// ------------------------------------------------------------------------------------------------
+Null::~Null() {
+}
+
+// ------------------------------------------------------------------------------------------------
+LimbNode::LimbNode(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		NodeAttribute(id, element, doc, name) {
+	//std::cout << "limb node: " << name << std::endl;
+	//const Scope &sc = GetRequiredScope(element);
+
+	//const ElementPtr const TypeFlag = sc["TypeFlags"];
+
+	// keep this it can dump new properties for you
+	// for( auto element : sc.Elements())
+	// {
+	//     std::cout << "limbnode element: " << element.first << std::endl;
+	// }
+
+	// if(TypeFlag)
+	// {
+	// //    std::cout << "type flag: " << GetRequiredToken(*TypeFlag, 0).StringContents() << std::endl;
+	// }
+}
+
+// ------------------------------------------------------------------------------------------------
+LimbNode::~LimbNode() {
+}
+} // namespace FBXDocParser

+ 111 - 0
modules/fbx/fbx_parser/FBXParseTools.h

@@ -0,0 +1,111 @@
+/*************************************************************************/
+/*  FBXParseTools.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_PARSE_TOOLS_H
+#define FBX_PARSE_TOOLS_H
+
+#include "core/error/error_macros.h"
+#include "core/string/ustring.h"
+
+#include <stdint.h>
+#include <algorithm>
+#include <locale>
+
+template <class char_t>
+inline bool IsNewLine(char_t c) {
+	return c == '\n' || c == '\r';
+}
+template <class char_t>
+inline bool IsSpace(char_t c) {
+	return (c == (char_t)' ' || c == (char_t)'\t');
+}
+
+template <class char_t>
+inline bool IsSpaceOrNewLine(char_t c) {
+	return IsNewLine(c) || IsSpace(c);
+}
+
+template <class char_t>
+inline bool IsLineEnd(char_t c) {
+	return (c == (char_t)'\r' || c == (char_t)'\n' || c == (char_t)'\0' || c == (char_t)'\f');
+}
+
+// ------------------------------------------------------------------------------------
+// Special version of the function, providing higher accuracy and safety
+// It is mainly used by fast_atof to prevent ugly and unwanted integer overflows.
+// ------------------------------------------------------------------------------------
+inline uint64_t strtoul10_64(const char *in, bool &errored, const char **out = 0, unsigned int *max_inout = 0) {
+	unsigned int cur = 0;
+	uint64_t value = 0;
+
+	errored = *in < '0' || *in > '9';
+	ERR_FAIL_COND_V_MSG(errored, 0, "The string cannot be converted parser error");
+
+	for (;;) {
+		if (*in < '0' || *in > '9') {
+			break;
+		}
+
+		const uint64_t new_value = (value * (uint64_t)10) + ((uint64_t)(*in - '0'));
+
+		// numeric overflow, we rely on you
+		if (new_value < value) {
+			//WARN_PRINT( "Converting the string \" " + in + " \" into a value resulted in overflow." );
+			return 0;
+		}
+
+		value = new_value;
+
+		++in;
+		++cur;
+
+		if (max_inout && *max_inout == cur) {
+			if (out) { /* skip to end */
+				while (*in >= '0' && *in <= '9') {
+					++in;
+				}
+				*out = in;
+			}
+
+			return value;
+		}
+	}
+	if (out) {
+		*out = in;
+	}
+
+	if (max_inout) {
+		*max_inout = cur;
+	}
+
+	return value;
+}
+
+#endif // FBX_PARSE_TOOLS_H

+ 1295 - 0
modules/fbx/fbx_parser/FBXParser.cpp

@@ -0,0 +1,1295 @@
+/*************************************************************************/
+/*  FBXParser.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXParser.cpp
+ *  @brief Implementation of the FBX parser and the rudimentary DOM that we use
+ */
+
+#include "thirdparty/zlib/zlib.h"
+#include <stdlib.h> /* strtol */
+
+#include "ByteSwapper.h"
+#include "FBXParseTools.h"
+#include "FBXParser.h"
+#include "FBXTokenizer.h"
+#include "core/math/math_defs.h"
+#include "core/math/transform.h"
+#include "core/math/vector3.h"
+#include "core/string/print_string.h"
+
+using namespace FBXDocParser;
+namespace {
+
+// Initially, we did reinterpret_cast, breaking strict aliasing rules.
+// This actually caused trouble on Android, so let's be safe this time.
+// https://github.com/assimp/assimp/issues/24
+template <typename T>
+T SafeParse(const char *data, const char *end) {
+	// Actual size validation happens during Tokenization so
+	// this is valid as an assertion.
+	(void)(end);
+	//ai_assert(static_cast<size_t>(end - data) >= sizeof(T));
+	T result = static_cast<T>(0);
+	::memcpy(&result, data, sizeof(T));
+	return result;
+}
+} // namespace
+
+namespace FBXDocParser {
+
+// ------------------------------------------------------------------------------------------------
+Element::Element(const TokenPtr key_token, Parser &parser) :
+		key_token(key_token) {
+	TokenPtr n = nullptr;
+	do {
+		n = parser.AdvanceToNextToken();
+		if (n == nullptr) {
+			continue;
+		}
+
+		if (!n) {
+			print_error("unexpected end of file, expected closing bracket" + String(parser.LastToken()->StringContents().c_str()));
+		}
+
+		if (n && n->Type() == TokenType_DATA) {
+			tokens.push_back(n);
+			TokenPtr prev = n;
+			n = parser.AdvanceToNextToken();
+
+			if (n == nullptr) {
+				break;
+			}
+
+			if (!n) {
+				print_error("unexpected end of file, expected bracket, comma or key" + String(parser.LastToken()->StringContents().c_str()));
+			}
+
+			const TokenType ty = n->Type();
+
+			// some exporters are missing a comma on the next line
+			if (ty == TokenType_DATA && prev->Type() == TokenType_DATA && (n->Line() == prev->Line() + 1)) {
+				tokens.push_back(n);
+				continue;
+			}
+
+			if (ty != TokenType_OPEN_BRACKET && ty != TokenType_CLOSE_BRACKET && ty != TokenType_COMMA && ty != TokenType_KEY) {
+				print_error("unexpected token; expected bracket, comma or key" + String(n->StringContents().c_str()));
+			}
+		}
+
+		if (n && n->Type() == TokenType_OPEN_BRACKET) {
+			compound = new_Scope(parser);
+			parser.scopes.push_back(compound);
+
+			// current token should be a TOK_CLOSE_BRACKET
+			n = parser.CurrentToken();
+
+			if (n && n->Type() != TokenType_CLOSE_BRACKET) {
+				print_error("expected closing bracket" + String(n->StringContents().c_str()));
+			}
+
+			parser.AdvanceToNextToken();
+			return;
+		}
+	} while (n && n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET);
+}
+
+// ------------------------------------------------------------------------------------------------
+Element::~Element() {
+}
+
+// ------------------------------------------------------------------------------------------------
+Scope::Scope(Parser &parser, bool topLevel) {
+	if (!topLevel) {
+		TokenPtr t = parser.CurrentToken();
+		if (t->Type() != TokenType_OPEN_BRACKET) {
+			print_error("expected open bracket" + String(t->StringContents().c_str()));
+		}
+	}
+
+	TokenPtr n = parser.AdvanceToNextToken();
+	if (n == nullptr) {
+		print_error("unexpected end of file");
+	}
+
+	// note: empty scopes are allowed
+	while (n && n->Type() != TokenType_CLOSE_BRACKET) {
+		if (n->Type() != TokenType_KEY) {
+			print_error("unexpected token, expected TOK_KEY" + String(n->StringContents().c_str()));
+		}
+
+		const std::string str = n->StringContents();
+
+		// std::multimap<std::string, ElementPtr> (key and value)
+		elements.insert(ElementMap::value_type(str, new_Element(n, parser)));
+
+		// Element() should stop at the next Key token (or right after a Close token)
+		n = parser.CurrentToken();
+		if (n == nullptr) {
+			if (topLevel) {
+				return;
+			}
+
+			//print_error("unexpected end of file" + String(parser.LastToken()->StringContents().c_str()));
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Scope::~Scope() {
+	for (ElementMap::value_type &v : elements) {
+		delete v.second;
+		v.second = nullptr;
+	}
+
+	elements.clear();
+}
+
+// ------------------------------------------------------------------------------------------------
+Parser::Parser(const TokenList &tokens, bool is_binary) :
+		tokens(tokens), last(), current(), cursor(tokens.begin()), is_binary(is_binary) {
+	root = new_Scope(*this, true);
+	scopes.push_back(root);
+}
+
+// ------------------------------------------------------------------------------------------------
+Parser::~Parser() {
+	for (ScopePtr scope : scopes) {
+		delete scope;
+		scope = nullptr;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+TokenPtr Parser::AdvanceToNextToken() {
+	last = current;
+	if (cursor == tokens.end()) {
+		current = nullptr;
+	} else {
+		current = *cursor++;
+	}
+	return current;
+}
+
+// ------------------------------------------------------------------------------------------------
+TokenPtr Parser::CurrentToken() const {
+	return current;
+}
+
+// ------------------------------------------------------------------------------------------------
+TokenPtr Parser::LastToken() const {
+	return last;
+}
+
+// ------------------------------------------------------------------------------------------------
+uint64_t ParseTokenAsID(const TokenPtr t, const char *&err_out) {
+	ERR_FAIL_COND_V_MSG(t == nullptr, 0L, "Invalid token passed to ParseTokenAsID");
+	err_out = nullptr;
+
+	if (t->Type() != TokenType_DATA) {
+		err_out = "expected TOK_DATA token";
+		return 0L;
+	}
+
+	if (t->IsBinary()) {
+		const char *data = t->begin();
+		if (data[0] != 'L') {
+			err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)";
+			return 0L;
+		}
+
+		uint64_t id = SafeParse<uint64_t>(data + 1, t->end());
+		return id;
+	}
+
+	// XXX: should use size_t here
+	unsigned int length = static_cast<unsigned int>(t->end() - t->begin());
+	//ai_assert(length > 0);
+
+	const char *out = nullptr;
+	bool errored = false;
+
+	const uint64_t id = strtoul10_64(t->begin(), errored, &out, &length);
+	if (errored || out > t->end()) {
+		err_out = "failed to parse ID (text)";
+		return 0L;
+	}
+
+	return id;
+}
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsID() with print_error handling
+uint64_t ParseTokenAsID(const TokenPtr t) {
+	const char *err = nullptr;
+	const uint64_t i = ParseTokenAsID(t, err);
+	if (err) {
+		print_error(String(err) + " " + String(t->StringContents().c_str()));
+	}
+	return i;
+}
+
+// ------------------------------------------------------------------------------------------------
+size_t ParseTokenAsDim(const TokenPtr t, const char *&err_out) {
+	// same as ID parsing, except there is a trailing asterisk
+	err_out = nullptr;
+
+	if (t->Type() != TokenType_DATA) {
+		err_out = "expected TOK_DATA token";
+		return 0;
+	}
+
+	if (t->IsBinary()) {
+		const char *data = t->begin();
+		if (data[0] != 'L') {
+			err_out = "failed to parse ID, unexpected data type, expected L(ong) (binary)";
+			return 0;
+		}
+
+		uint64_t id = SafeParse<uint64_t>(data + 1, t->end());
+		AI_SWAP8(id);
+		return static_cast<size_t>(id);
+	}
+
+	if (*t->begin() != '*') {
+		err_out = "expected asterisk before array dimension";
+		return 0;
+	}
+
+	// XXX: should use size_t here
+	unsigned int length = static_cast<unsigned int>(t->end() - t->begin());
+	if (length == 0) {
+		err_out = "expected valid integer number after asterisk";
+		return 0;
+	}
+
+	const char *out = nullptr;
+	bool errored = false;
+	const size_t id = static_cast<size_t>(strtoul10_64(t->begin() + 1, errored, &out, &length));
+	if (errored || out > t->end()) {
+		print_error("failed to parse id");
+		err_out = "failed to parse ID";
+		return 0;
+	}
+
+	return id;
+}
+
+// ------------------------------------------------------------------------------------------------
+float ParseTokenAsFloat(const TokenPtr t, const char *&err_out) {
+	err_out = nullptr;
+
+	if (t->Type() != TokenType_DATA) {
+		err_out = "expected TOK_DATA token";
+		return 0.0f;
+	}
+
+	if (t->IsBinary()) {
+		const char *data = t->begin();
+		if (data[0] != 'F' && data[0] != 'D') {
+			err_out = "failed to parse F(loat) or D(ouble), unexpected data type (binary)";
+			return 0.0f;
+		}
+
+		if (data[0] == 'F') {
+			return SafeParse<float>(data + 1, t->end());
+		} else {
+			return static_cast<float>(SafeParse<double>(data + 1, t->end()));
+		}
+	}
+
+// need to copy the input string to a temporary buffer
+// first - next in the fbx token stream comes ',',
+// which fast_atof could interpret as decimal point.
+#define MAX_FLOAT_LENGTH 31
+	char temp[MAX_FLOAT_LENGTH + 1];
+	const size_t length = static_cast<size_t>(t->end() - t->begin());
+	std::copy(t->begin(), t->end(), temp);
+	temp[std::min(static_cast<size_t>(MAX_FLOAT_LENGTH), length)] = '\0';
+
+	return atof(temp);
+}
+
+// ------------------------------------------------------------------------------------------------
+int ParseTokenAsInt(const TokenPtr t, const char *&err_out) {
+	err_out = nullptr;
+
+	if (t->Type() != TokenType_DATA) {
+		err_out = "expected TOK_DATA token";
+		return 0;
+	}
+
+	// binary files are simple to parse
+	if (t->IsBinary()) {
+		const char *data = t->begin();
+		if (data[0] != 'I') {
+			err_out = "failed to parse I(nt), unexpected data type (binary)";
+			return 0;
+		}
+
+		int32_t ival = SafeParse<int32_t>(data + 1, t->end());
+		AI_SWAP4(ival);
+		return static_cast<int>(ival);
+	}
+
+	// ASCII files are unsafe.
+	const size_t length = static_cast<size_t>(t->end() - t->begin());
+	if (length == 0) {
+		err_out = "expected valid integer number after asterisk";
+		ERR_FAIL_V_MSG(0, "expected valid integer number after asterisk");
+	}
+
+	// must not be null for strtol to work
+	char *out = (char *)t->end();
+	// string begin, end ptr ref, base 10
+	const int value = strtol(t->begin(), &out, 10);
+	if (out == nullptr || out != t->end()) {
+		err_out = "failed to parse ID";
+		ERR_FAIL_V_MSG(0, "failed to parse ID");
+	}
+
+	return value;
+}
+
+// ------------------------------------------------------------------------------------------------
+int64_t ParseTokenAsInt64(const TokenPtr t, const char *&err_out) {
+	err_out = nullptr;
+
+	if (t->Type() != TokenType_DATA) {
+		err_out = "expected TOK_DATA token";
+		return 0L;
+	}
+
+	if (t->IsBinary()) {
+		const char *data = t->begin();
+		if (data[0] != 'L') {
+			err_out = "failed to parse Int64, unexpected data type";
+			return 0L;
+		}
+
+		int64_t id = SafeParse<int64_t>(data + 1, t->end());
+		AI_SWAP8(id);
+		return id;
+	}
+
+	// XXX: should use size_t here
+	unsigned int length = static_cast<unsigned int>(t->end() - t->begin());
+	//ai_assert(length > 0);
+
+	char *out = nullptr;
+	const int64_t id = strtol(t->begin(), &out, length);
+	if (out > t->end()) {
+		err_out = "failed to parse Int64 (text)";
+		return 0L;
+	}
+
+	return id;
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string ParseTokenAsString(const TokenPtr t, const char *&err_out) {
+	err_out = nullptr;
+
+	if (t->Type() != TokenType_DATA) {
+		err_out = "expected TOK_DATA token";
+		return "";
+	}
+
+	if (t->IsBinary()) {
+		const char *data = t->begin();
+		if (data[0] != 'S') {
+			err_out = "failed to parse String, unexpected data type (binary)";
+			return "";
+		}
+
+		// read string length
+		int32_t len = SafeParse<int32_t>(data + 1, t->end());
+		AI_SWAP4(len);
+
+		//ai_assert(t.end() - data == 5 + len);
+		return std::string(data + 5, len);
+	}
+
+	const size_t length = static_cast<size_t>(t->end() - t->begin());
+	if (length < 2) {
+		err_out = "token is too short to hold a string";
+		return "";
+	}
+
+	const char *s = t->begin(), *e = t->end() - 1;
+	if (*s != '\"' || *e != '\"') {
+		err_out = "expected double quoted string";
+		return "";
+	}
+
+	return std::string(s + 1, length - 2);
+}
+
+namespace {
+
+// ------------------------------------------------------------------------------------------------
+// read the type code and element count of a binary data array and stop there
+void ReadBinaryDataArrayHead(const char *&data, const char *end, char &type, uint32_t &count,
+		const ElementPtr el) {
+	TokenPtr token = el->KeyToken();
+	if (static_cast<size_t>(end - data) < 5) {
+		print_error("binary data array is too short, need five (5) bytes for type signature and element count: " + String(token->StringContents().c_str()));
+	}
+
+	// data type
+	type = *data;
+
+	// read number of elements
+	uint32_t len = SafeParse<uint32_t>(data + 1, end);
+	AI_SWAP4(len);
+
+	count = len;
+	data += 5;
+}
+
+// ------------------------------------------------------------------------------------------------
+// read binary data array, assume cursor points to the 'compression mode' field (i.e. behind the header)
+void ReadBinaryDataArray(char type, uint32_t count, const char *&data, const char *end,
+		std::vector<char> &buff,
+		const ElementPtr /*el*/) {
+	uint32_t encmode = SafeParse<uint32_t>(data, end);
+	AI_SWAP4(encmode);
+	data += 4;
+
+	// next comes the compressed length
+	uint32_t comp_len = SafeParse<uint32_t>(data, end);
+	AI_SWAP4(comp_len);
+	data += 4;
+
+	//ai_assert(data + comp_len == end);
+
+	// determine the length of the uncompressed data by looking at the type signature
+	uint32_t stride = 0;
+	switch (type) {
+		case 'f':
+		case 'i':
+			stride = 4;
+			break;
+
+		case 'd':
+		case 'l':
+			stride = 8;
+			break;
+	}
+
+	const uint32_t full_length = stride * count;
+	buff.resize(full_length);
+
+	if (encmode == 0) {
+		//ai_assert(full_length == comp_len);
+
+		// plain data, no compression
+		std::copy(data, end, buff.begin());
+	} else if (encmode == 1) {
+		// zlib/deflate, next comes ZIP head (0x78 0x01)
+		// see http://www.ietf.org/rfc/rfc1950.txt
+
+		z_stream zstream;
+		zstream.opaque = Z_NULL;
+		zstream.zalloc = Z_NULL;
+		zstream.zfree = Z_NULL;
+		zstream.data_type = Z_BINARY;
+
+		// http://hewgill.com/journal/entries/349-how-to-decompress-gzip-stream-with-zlib
+		if (Z_OK != inflateInit(&zstream)) {
+			print_error("failure initializing zlib");
+		}
+
+		zstream.next_in = reinterpret_cast<Bytef *>(const_cast<char *>(data));
+		zstream.avail_in = comp_len;
+
+		zstream.avail_out = static_cast<uInt>(buff.size());
+		zstream.next_out = reinterpret_cast<Bytef *>(&*buff.begin());
+		const int ret = inflate(&zstream, Z_FINISH);
+
+		if (ret != Z_STREAM_END && ret != Z_OK) {
+			print_error("failure decompressing compressed data section");
+		}
+
+		// terminate zlib
+		inflateEnd(&zstream);
+	}
+#ifdef ASSIMP_BUILD_DEBUG
+	else {
+		// runtime check for this happens at tokenization stage
+		//ai_assert(false);
+	}
+#endif
+
+	data += comp_len;
+	//ai_assert(data == end);
+}
+} // namespace
+
+// ------------------------------------------------------------------------------------------------
+// read an array of float3 tuples
+void ParseVectorDataArray(std::vector<Vector3> &out, const ElementPtr el) {
+	out.resize(0);
+
+	const TokenList &tok = el->Tokens();
+	TokenPtr token = el->KeyToken();
+	if (tok.empty()) {
+		print_error("unexpected empty element" + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (count % 3 != 0) {
+			print_error("number of floats is not a multiple of three (3) (binary)" + String(token->StringContents().c_str()));
+		}
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'd' && type != 'f') {
+			print_error("expected float or double array (binary)" + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * (type == 'd' ? 8 : 4));
+
+		const uint32_t count3 = count / 3;
+		out.reserve(count3);
+
+		if (type == 'd') {
+			const double *d = reinterpret_cast<const double *>(&buff[0]);
+			for (unsigned int i = 0; i < count3; ++i, d += 3) {
+				out.push_back(Vector3(static_cast<real_t>(d[0]),
+						static_cast<real_t>(d[1]),
+						static_cast<real_t>(d[2])));
+			}
+			// for debugging
+			/*for ( size_t i = 0; i < out.size(); i++ ) {
+                aiVector3D vec3( out[ i ] );
+                std::stringstream stream;
+                stream << " vec3.x = " << vec3.x << " vec3.y = " << vec3.y << " vec3.z = " << vec3.z << std::endl;
+                DefaultLogger::get()->info( stream.str() );
+            }*/
+		} else if (type == 'f') {
+			const float *f = reinterpret_cast<const float *>(&buff[0]);
+			for (unsigned int i = 0; i < count3; ++i, f += 3) {
+				out.push_back(Vector3(f[0], f[1], f[2]));
+			}
+		}
+
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	// may throw bad_alloc if the input is rubbish, but this need
+	// not to be prevented - importing would fail but we wouldn't
+	// crash since assimp handles this case properly.
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	if (a->Tokens().size() % 3 != 0) {
+		print_error("number of floats is not a multiple of three (3)" + String(token->StringContents().c_str()));
+	} else {
+		for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+			Vector3 v;
+			v.x = ParseTokenAsFloat(*it++);
+			v.y = ParseTokenAsFloat(*it++);
+			v.z = ParseTokenAsFloat(*it++);
+
+			out.push_back(v);
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// read an array of color4 tuples
+void ParseVectorDataArray(std::vector<Color> &out, const ElementPtr el) {
+	out.resize(0);
+	const TokenList &tok = el->Tokens();
+
+	TokenPtr token = el->KeyToken();
+
+	if (tok.empty()) {
+		print_error("unexpected empty element" + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (count % 4 != 0) {
+			print_error("number of floats is not a multiple of four (4) (binary)" + String(token->StringContents().c_str()));
+		}
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'd' && type != 'f') {
+			print_error("expected float or double array (binary)" + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * (type == 'd' ? 8 : 4));
+
+		const uint32_t count4 = count / 4;
+		out.reserve(count4);
+
+		if (type == 'd') {
+			const double *d = reinterpret_cast<const double *>(&buff[0]);
+			for (unsigned int i = 0; i < count4; ++i, d += 4) {
+				out.push_back(Color(static_cast<float>(d[0]),
+						static_cast<float>(d[1]),
+						static_cast<float>(d[2]),
+						static_cast<float>(d[3])));
+			}
+		} else if (type == 'f') {
+			const float *f = reinterpret_cast<const float *>(&buff[0]);
+			for (unsigned int i = 0; i < count4; ++i, f += 4) {
+				out.push_back(Color(f[0], f[1], f[2], f[3]));
+			}
+		}
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	//  see notes in ParseVectorDataArray() above
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	if (a->Tokens().size() % 4 != 0) {
+		print_error("number of floats is not a multiple of four (4)" + String(token->StringContents().c_str()));
+	}
+	for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+		Color v;
+		v.r = ParseTokenAsFloat(*it++);
+		v.g = ParseTokenAsFloat(*it++);
+		v.b = ParseTokenAsFloat(*it++);
+		v.a = ParseTokenAsFloat(*it++);
+
+		out.push_back(v);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// read an array of float2 tuples
+void ParseVectorDataArray(std::vector<Vector2> &out, const ElementPtr el) {
+	out.resize(0);
+	const TokenList &tok = el->Tokens();
+	TokenPtr token = el->KeyToken();
+	if (tok.empty()) {
+		print_error("unexpected empty element" + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (count % 2 != 0) {
+			print_error("number of floats is not a multiple of two (2) (binary)" + String(token->StringContents().c_str()));
+		}
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'd' && type != 'f') {
+			print_error("expected float or double array (binary)" + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * (type == 'd' ? 8 : 4));
+
+		const uint32_t count2 = count / 2;
+		out.reserve(count2);
+
+		if (type == 'd') {
+			const double *d = reinterpret_cast<const double *>(&buff[0]);
+			for (unsigned int i = 0; i < count2; ++i, d += 2) {
+				out.push_back(Vector2(static_cast<float>(d[0]),
+						static_cast<float>(d[1])));
+			}
+		} else if (type == 'f') {
+			const float *f = reinterpret_cast<const float *>(&buff[0]);
+			for (unsigned int i = 0; i < count2; ++i, f += 2) {
+				out.push_back(Vector2(f[0], f[1]));
+			}
+		}
+
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	// see notes in ParseVectorDataArray() above
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	if (a->Tokens().size() % 2 != 0) {
+		print_error("number of floats is not a multiple of two (2)" + String(token->StringContents().c_str()));
+	} else {
+		for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+			Vector2 v;
+			v.x = ParseTokenAsFloat(*it++);
+			v.y = ParseTokenAsFloat(*it++);
+			out.push_back(v);
+		}
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// read an array of ints
+void ParseVectorDataArray(std::vector<int> &out, const ElementPtr el) {
+	out.resize(0);
+	const TokenList &tok = el->Tokens();
+	TokenPtr token = el->KeyToken();
+	if (tok.empty()) {
+		print_error("unexpected empty element" + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'i') {
+			print_error("expected int array (binary)" + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * 4);
+
+		out.reserve(count);
+
+		const int32_t *ip = reinterpret_cast<const int32_t *>(&buff[0]);
+		for (unsigned int i = 0; i < count; ++i, ++ip) {
+			int32_t val = *ip;
+			AI_SWAP4(val);
+			out.push_back(val);
+		}
+
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	// see notes in ParseVectorDataArray()
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+		const int ival = ParseTokenAsInt(*it++);
+		out.push_back(ival);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// read an array of floats
+void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el) {
+	out.resize(0);
+	const TokenList &tok = el->Tokens();
+	TokenPtr token = el->KeyToken();
+	if (tok.empty()) {
+		print_error("unexpected empty element: " + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'd' && type != 'f') {
+			print_error("expected float or double array (binary) " + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * (type == 'd' ? 8 : 4));
+
+		if (type == 'd') {
+			const double *d = reinterpret_cast<const double *>(&buff[0]);
+			for (unsigned int i = 0; i < count; ++i, ++d) {
+				out.push_back(static_cast<float>(*d));
+			}
+		} else if (type == 'f') {
+			const float *f = reinterpret_cast<const float *>(&buff[0]);
+			for (unsigned int i = 0; i < count; ++i, ++f) {
+				out.push_back(*f);
+			}
+		}
+
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	// see notes in ParseVectorDataArray()
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+		const float ival = ParseTokenAsFloat(*it++);
+		out.push_back(ival);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// read an array of uints
+void ParseVectorDataArray(std::vector<unsigned int> &out, const ElementPtr el) {
+	out.resize(0);
+	const TokenList &tok = el->Tokens();
+	const TokenPtr token = el->KeyToken();
+
+	ERR_FAIL_COND_MSG(!token, "invalid ParseVectorDataArrat token invalid");
+
+	if (tok.empty()) {
+		print_error("unexpected empty element: " + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'i') {
+			print_error("expected (u)int array (binary)" + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * 4);
+
+		out.reserve(count);
+
+		const int32_t *ip = reinterpret_cast<const int32_t *>(&buff[0]);
+		for (unsigned int i = 0; i < count; ++i, ++ip) {
+			int32_t val = *ip;
+			if (val < 0) {
+				print_error("encountered negative integer index (binary)");
+			}
+
+			out.push_back(val);
+		}
+
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	// see notes in ParseVectorDataArray()
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+		const int ival = ParseTokenAsInt(*it++);
+		if (ival < 0) {
+			print_error("encountered negative integer index");
+		}
+		out.push_back(static_cast<unsigned int>(ival));
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// read an array of uint64_ts
+void ParseVectorDataArray(std::vector<uint64_t> &out, const ElementPtr el) {
+	out.resize(0);
+
+	const TokenList &tok = el->Tokens();
+	TokenPtr token = el->KeyToken();
+	ERR_FAIL_COND(!token);
+
+	if (tok.empty()) {
+		print_error("unexpected empty element " + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'l') {
+			print_error("expected long array (binary): " + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * 8);
+
+		out.reserve(count);
+
+		const uint64_t *ip = reinterpret_cast<const uint64_t *>(&buff[0]);
+		for (unsigned int i = 0; i < count; ++i, ++ip) {
+			uint64_t val = *ip;
+			AI_SWAP8(val);
+			out.push_back(val);
+		}
+
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	// see notes in ParseVectorDataArray()
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+		const uint64_t ival = ParseTokenAsID(*it++);
+
+		out.push_back(ival);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+// read an array of int64_ts
+void ParseVectorDataArray(std::vector<int64_t> &out, const ElementPtr el) {
+	out.resize(0);
+	const TokenList &tok = el->Tokens();
+	TokenPtr token = el->KeyToken();
+	ERR_FAIL_COND(!token);
+	if (tok.empty()) {
+		print_error("unexpected empty element: " + String(token->StringContents().c_str()));
+	}
+
+	if (tok[0]->IsBinary()) {
+		const char *data = tok[0]->begin(), *end = tok[0]->end();
+
+		char type;
+		uint32_t count;
+		ReadBinaryDataArrayHead(data, end, type, count, el);
+
+		if (!count) {
+			return;
+		}
+
+		if (type != 'l') {
+			print_error("expected long array (binary) " + String(token->StringContents().c_str()));
+		}
+
+		std::vector<char> buff;
+		ReadBinaryDataArray(type, count, data, end, buff, el);
+
+		//ai_assert(data == end);
+		//ai_assert(buff.size() == count * 8);
+
+		out.reserve(count);
+
+		const int64_t *ip = reinterpret_cast<const int64_t *>(&buff[0]);
+		for (unsigned int i = 0; i < count; ++i, ++ip) {
+			int64_t val = *ip;
+			AI_SWAP8(val);
+			out.push_back(val);
+		}
+
+		return;
+	}
+
+	const size_t dim = ParseTokenAsDim(tok[0]);
+
+	// see notes in ParseVectorDataArray()
+	out.reserve(dim);
+
+	const ScopePtr scope = GetRequiredScope(el);
+	const ElementPtr a = GetRequiredElement(scope, "a", el);
+
+	for (TokenList::const_iterator it = a->Tokens().begin(), end = a->Tokens().end(); it != end;) {
+		const int64_t val = ParseTokenAsInt64(*it++);
+		out.push_back(val);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+Transform ReadMatrix(const ElementPtr element) {
+	std::vector<float> values;
+	ParseVectorDataArray(values, element);
+
+	if (values.size() != 16) {
+		print_error("expected 16 matrix elements");
+	}
+
+	// clean values to prevent any IBM damage on inverse() / affine_inverse()
+	for (float &value : values) {
+		if (::Math::is_equal_approx(0, value)) {
+			value = 0;
+		}
+	}
+
+	Transform xform;
+	Basis basis;
+
+	basis.set(
+			Vector3(values[0], values[1], values[2]),
+			Vector3(values[4], values[5], values[6]),
+			Vector3(values[8], values[9], values[10]));
+
+	xform.basis = basis;
+	xform.origin = Vector3(values[12], values[13], values[14]);
+	// determine if we need to think about this with dynamic rotation order?
+	// for example:
+	// xform.basis = z_axis * y_axis * x_axis;
+	//xform.basis.transpose();
+
+	print_verbose("xform verbose basis: " + (xform.basis.get_euler() * (180 / Math_PI)) + " xform origin:" + xform.origin);
+
+	return xform;
+}
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsString() with print_error handling
+std::string ParseTokenAsString(const TokenPtr t) {
+	ERR_FAIL_COND_V(!t, "");
+	const char *err;
+	const std::string &i = ParseTokenAsString(t, err);
+	if (err) {
+		print_error(String(err) + ", " + String(t->StringContents().c_str()));
+	}
+	return i;
+}
+
+// ------------------------------------------------------------------------------------------------
+// extract a required element from a scope, abort if the element cannot be found
+ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= NULL*/) {
+	const ElementPtr el = sc->GetElement(index);
+	TokenPtr token = el->KeyToken();
+	ERR_FAIL_COND_V(!token, nullptr);
+	if (!el) {
+		print_error("did not find required element \"" + String(index.c_str()) + "\" " + String(token->StringContents().c_str()));
+	}
+	return el;
+}
+
+bool HasElement(const ScopePtr sc, const std::string &index) {
+	const ElementPtr el = sc->GetElement(index);
+	if (nullptr == el) {
+		return false;
+	}
+
+	return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// extract a required element from a scope, abort if the element cannot be found
+ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element /*= NULL*/) {
+	const ElementPtr el = sc->GetElement(index);
+	return el;
+}
+
+// ------------------------------------------------------------------------------------------------
+// extract required compound scope
+ScopePtr GetRequiredScope(const ElementPtr el) {
+	if (el) {
+		ScopePtr s = el->Compound();
+		TokenPtr token = el->KeyToken();
+		ERR_FAIL_COND_V(!token, nullptr);
+		if (s) {
+			return s;
+		}
+
+		ERR_FAIL_V_MSG(nullptr, "expected compound scope " + String(token->StringContents().c_str()));
+	}
+
+	ERR_FAIL_V_MSG(nullptr, "Invalid element supplied to parser");
+}
+
+// ------------------------------------------------------------------------------------------------
+// get token at a particular index
+TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index) {
+	if (el) {
+		const TokenList &x = el->Tokens();
+		TokenPtr token = el->KeyToken();
+
+		ERR_FAIL_COND_V(!token, nullptr);
+
+		if (index >= x.size()) {
+			ERR_FAIL_V_MSG(nullptr, "missing token at index: " + itos(index) + " " + String(token->StringContents().c_str()));
+		}
+
+		return x[index];
+	}
+
+	return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsDim() with print_error handling
+size_t ParseTokenAsDim(const TokenPtr t) {
+	const char *err;
+	const size_t i = ParseTokenAsDim(t, err);
+	if (err) {
+		print_error(String(err) + " " + String(t->StringContents().c_str()));
+	}
+	return i;
+}
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsFloat() with print_error handling
+float ParseTokenAsFloat(const TokenPtr t) {
+	const char *err;
+	const float i = ParseTokenAsFloat(t, err);
+	if (err) {
+		print_error(String(err) + " " + String(t->StringContents().c_str()));
+	}
+	return i;
+}
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsInt() with print_error handling
+int ParseTokenAsInt(const TokenPtr t) {
+	const char *err;
+	const int i = ParseTokenAsInt(t, err);
+	if (err) {
+		print_error(String(err) + " " + String(t->StringContents().c_str()));
+	}
+	return i;
+}
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsInt64() with print_error handling
+int64_t ParseTokenAsInt64(const TokenPtr t) {
+	const char *err;
+	const int64_t i = ParseTokenAsInt64(t, err);
+	if (err) {
+		print_error(String(err) + " " + String(t->StringContents().c_str()));
+	}
+	return i;
+}
+} // namespace FBXDocParser

+ 263 - 0
modules/fbx/fbx_parser/FBXParser.h

@@ -0,0 +1,263 @@
+/*************************************************************************/
+/*  FBXParser.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXParser.h
+ *  @brief FBX parsing code
+ */
+#ifndef FBX_PARSER_H
+#define FBX_PARSER_H
+
+#include <stdint.h>
+#include <map>
+#include <memory>
+
+#include "core/math/color.h"
+#include "core/math/transform.h"
+#include "core/math/vector2.h"
+#include "core/math/vector3.h"
+
+#include "FBXTokenizer.h"
+
+namespace FBXDocParser {
+
+class Scope;
+class Parser;
+class Element;
+
+typedef Element *ElementPtr;
+typedef Scope *ScopePtr;
+
+typedef std::vector<ScopePtr> ScopeList;
+typedef std::multimap<std::string, ElementPtr> ElementMap;
+typedef std::pair<ElementMap::const_iterator, ElementMap::const_iterator> ElementCollection;
+
+#define new_Scope new Scope
+#define new_Element new Element
+
+/** FBX data entity that consists of a key:value tuple.
+ *
+ *  Example:
+ *  @verbatim
+ *    AnimationCurve: 23, "AnimCurve::", "" {
+ *        [..]
+ *    }
+ *  @endverbatim
+ *
+ *  As can be seen in this sample, elements can contain nested #Scope
+ *  as their trailing member.  **/
+class Element {
+public:
+	Element(TokenPtr key_token, Parser &parser);
+	~Element();
+
+	ScopePtr Compound() const {
+		return compound;
+	}
+
+	TokenPtr KeyToken() const {
+		return key_token;
+	}
+
+	const TokenList &Tokens() const {
+		return tokens;
+	}
+
+private:
+	TokenList tokens;
+	ScopePtr compound = nullptr;
+	std::vector<ScopePtr> compound_scope;
+	TokenPtr key_token = nullptr;
+};
+
+/** FBX data entity that consists of a 'scope', a collection
+ *  of not necessarily unique #Element instances.
+ *
+ *  Example:
+ *  @verbatim
+ *    GlobalSettings:  {
+ *        Version: 1000
+ *        Properties70:
+ *        [...]
+ *    }
+ *  @endverbatim  */
+class Scope {
+public:
+	Scope(Parser &parser, bool topLevel = false);
+	~Scope();
+
+	ElementPtr GetElement(const std::string &index) const {
+		ElementMap::const_iterator it = elements.find(index);
+		return it == elements.end() ? nullptr : (*it).second;
+	}
+
+	ElementPtr FindElementCaseInsensitive(const std::string &elementName) const {
+		for (auto element = elements.begin(); element != elements.end(); ++element) {
+			if (element->first.compare(elementName)) {
+				return element->second;
+			}
+		}
+
+		// nothing to reference / expired.
+		return nullptr;
+	}
+
+	ElementCollection GetCollection(const std::string &index) const {
+		return elements.equal_range(index);
+	}
+
+	const ElementMap &Elements() const {
+		return elements;
+	}
+
+private:
+	ElementMap elements;
+};
+
+/** FBX parsing class, takes a list of input tokens and generates a hierarchy
+ *  of nested #Scope instances, representing the fbx DOM.*/
+class Parser {
+public:
+	/** Parse given a token list. Does not take ownership of the tokens -
+     *  the objects must persist during the entire parser lifetime */
+	Parser(const TokenList &tokens, bool is_binary);
+	~Parser();
+
+	ScopePtr GetRootScope() const {
+		return root;
+	}
+
+	bool IsBinary() const {
+		return is_binary;
+	}
+
+private:
+	friend class Scope;
+	friend class Element;
+
+	TokenPtr AdvanceToNextToken();
+	TokenPtr LastToken() const;
+	TokenPtr CurrentToken() const;
+
+private:
+	ScopeList scopes;
+	const TokenList &tokens;
+
+	TokenPtr last = nullptr, current = nullptr;
+	TokenList::const_iterator cursor;
+	ScopePtr root = nullptr;
+
+	const bool is_binary;
+};
+
+/* token parsing - this happens when building the DOM out of the parse-tree*/
+uint64_t ParseTokenAsID(const TokenPtr t, const char *&err_out);
+size_t ParseTokenAsDim(const TokenPtr t, const char *&err_out);
+float ParseTokenAsFloat(const TokenPtr t, const char *&err_out);
+int ParseTokenAsInt(const TokenPtr t, const char *&err_out);
+int64_t ParseTokenAsInt64(const TokenPtr t, const char *&err_out);
+std::string ParseTokenAsString(const TokenPtr t, const char *&err_out);
+
+/* wrapper around ParseTokenAsXXX() with DOMError handling */
+uint64_t ParseTokenAsID(const TokenPtr t);
+size_t ParseTokenAsDim(const TokenPtr t);
+float ParseTokenAsFloat(const TokenPtr t);
+int ParseTokenAsInt(const TokenPtr t);
+int64_t ParseTokenAsInt64(const TokenPtr t);
+std::string ParseTokenAsString(const TokenPtr t);
+
+/* read data arrays */
+void ParseVectorDataArray(std::vector<Vector3> &out, const ElementPtr el);
+void ParseVectorDataArray(std::vector<Color> &out, const ElementPtr el);
+void ParseVectorDataArray(std::vector<Vector2> &out, const ElementPtr el);
+void ParseVectorDataArray(std::vector<int> &out, const ElementPtr el);
+void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el);
+void ParseVectorDataArray(std::vector<float> &out, const ElementPtr el);
+void ParseVectorDataArray(std::vector<unsigned int> &out, const ElementPtr el);
+void ParseVectorDataArray(std::vector<uint64_t> &out, const ElementPtr ep);
+void ParseVectorDataArray(std::vector<int64_t> &out, const ElementPtr el);
+bool HasElement(const ScopePtr sc, const std::string &index);
+
+// extract a required element from a scope, abort if the element cannot be found
+ElementPtr GetRequiredElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr);
+ScopePtr GetRequiredScope(const ElementPtr el); // New in 2020. (less likely to destroy application)
+ElementPtr GetOptionalElement(const ScopePtr sc, const std::string &index, const ElementPtr element = nullptr);
+// extract required compound scope
+ScopePtr GetRequiredScope(const ElementPtr el);
+// get token at a particular index
+TokenPtr GetRequiredToken(const ElementPtr el, unsigned int index);
+
+// ------------------------------------------------------------------------------------------------
+// read a 4x4 matrix from an array of 16 floats
+Transform ReadMatrix(const ElementPtr element);
+} // namespace FBXDocParser
+
+#endif // FBX_PARSER_H

+ 104 - 0
modules/fbx/fbx_parser/FBXPose.cpp

@@ -0,0 +1,104 @@
+/*************************************************************************/
+/*  FBXPose.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXNoteAttribute.cpp
+ *  @brief Assimp::FBX::NodeAttribute (and subclasses) implementation
+ */
+
+#include "FBXDocument.h"
+#include "FBXParser.h"
+#include <iostream>
+
+namespace FBXDocParser {
+
+class FbxPoseNode;
+// ------------------------------------------------------------------------------------------------
+FbxPose::FbxPose(uint64_t id, const ElementPtr element, const Document &doc, const std::string &name) :
+		Object(id, element, name) {
+	const ScopePtr sc = GetRequiredScope(element);
+	//const std::string &classname = ParseTokenAsString(GetRequiredToken(element, 2));
+
+	const ElementCollection &PoseNodes = sc->GetCollection("PoseNode");
+	for (ElementMap::const_iterator it = PoseNodes.first; it != PoseNodes.second; ++it) {
+		std::string entry_name = (*it).first;
+		ElementPtr some_element = (*it).second;
+		FbxPoseNode *pose_node = new FbxPoseNode(some_element, doc, entry_name);
+		pose_nodes.push_back(pose_node);
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+FbxPose::~FbxPose() {
+	pose_nodes.clear();
+	// empty
+}
+} // namespace FBXDocParser

+ 237 - 0
modules/fbx/fbx_parser/FBXProperties.cpp

@@ -0,0 +1,237 @@
+/*************************************************************************/
+/*  FBXProperties.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXProperties.cpp
+ *  @brief Implementation of the FBX dynamic properties system
+ */
+
+#include "FBXProperties.h"
+#include "FBXDocumentUtil.h"
+#include "FBXParser.h"
+#include "FBXTokenizer.h"
+
+namespace FBXDocParser {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Property::Property() {
+}
+
+// ------------------------------------------------------------------------------------------------
+Property::~Property() {
+}
+
+namespace {
+
+// ------------------------------------------------------------------------------------------------
+// read a typed property out of a FBX element. The return value is NULL if the property cannot be read.
+PropertyPtr ReadTypedProperty(const ElementPtr element) {
+	//ai_assert(element.KeyToken().StringContents() == "P");
+
+	const TokenList &tok = element->Tokens();
+	//ai_assert(tok.size() >= 5);
+
+	const std::string &s = ParseTokenAsString(tok[1]);
+	const char *const cs = s.c_str();
+	if (!strcmp(cs, "KString")) {
+		return new TypedProperty<std::string>(ParseTokenAsString(tok[4]));
+	} else if (!strcmp(cs, "bool") || !strcmp(cs, "Bool")) {
+		return new TypedProperty<bool>(ParseTokenAsInt(tok[4]) != 0);
+	} else if (!strcmp(cs, "int") || !strcmp(cs, "Int") || !strcmp(cs, "enum") || !strcmp(cs, "Enum")) {
+		return new TypedProperty<int>(ParseTokenAsInt(tok[4]));
+	} else if (!strcmp(cs, "ULongLong")) {
+		return new TypedProperty<uint64_t>(ParseTokenAsID(tok[4]));
+	} else if (!strcmp(cs, "KTime")) {
+		return new TypedProperty<int64_t>(ParseTokenAsInt64(tok[4]));
+	} else if (!strcmp(cs, "Vector3D") ||
+			   !strcmp(cs, "ColorRGB") ||
+			   !strcmp(cs, "Vector") ||
+			   !strcmp(cs, "Color") ||
+			   !strcmp(cs, "Lcl Translation") ||
+			   !strcmp(cs, "Lcl Rotation") ||
+			   !strcmp(cs, "Lcl Scaling")) {
+		return new TypedProperty<Vector3>(Vector3(
+				ParseTokenAsFloat(tok[4]),
+				ParseTokenAsFloat(tok[5]),
+				ParseTokenAsFloat(tok[6])));
+	} else if (!strcmp(cs, "double") || !strcmp(cs, "Number") || !strcmp(cs, "Float") || !strcmp(cs, "float") || !strcmp(cs, "FieldOfView") || !strcmp(cs, "UnitScaleFactor")) {
+		return new TypedProperty<float>(ParseTokenAsFloat(tok[4]));
+	}
+
+	return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+// peek into an element and check if it contains a FBX property, if so return its name.
+std::string PeekPropertyName(const Element &element) {
+	//ai_assert(element.KeyToken().StringContents() == "P");
+	const TokenList &tok = element.Tokens();
+	if (tok.size() < 4) {
+		return "";
+	}
+
+	return ParseTokenAsString(tok[0]);
+}
+} // namespace
+
+// ------------------------------------------------------------------------------------------------
+PropertyTable::PropertyTable() :
+		templateProps(), element() {
+}
+
+// ------------------------------------------------------------------------------------------------
+PropertyTable::PropertyTable(const ElementPtr element, const PropertyTable *templateProps) :
+		templateProps(templateProps), element(element) {
+	const ScopePtr scope = GetRequiredScope(element);
+	ERR_FAIL_COND(!scope);
+	for (const ElementMap::value_type &v : scope->Elements()) {
+		if (v.first != "P") {
+			DOMWarning("expected only P elements in property table", v.second);
+			continue;
+		}
+
+		const std::string &name = PeekPropertyName(*v.second);
+		if (!name.length()) {
+			DOMWarning("could not read property name", v.second);
+			continue;
+		}
+
+		LazyPropertyMap::const_iterator it = lazyProps.find(name);
+		if (it != lazyProps.end()) {
+			DOMWarning("duplicate property name, will hide previous value: " + name, v.second);
+			continue;
+		}
+
+		// since the above checks for duplicates we can be sure to insert the only match here.
+		lazyProps[name] = v.second;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+PropertyTable::~PropertyTable() {
+	for (PropertyMap::value_type &v : props) {
+		delete v.second;
+	}
+}
+
+// ------------------------------------------------------------------------------------------------
+PropertyPtr PropertyTable::Get(const std::string &name) const {
+	PropertyMap::const_iterator it = props.find(name);
+	if (it == props.end()) {
+		// hasn't been parsed yet?
+		LazyPropertyMap::const_iterator lit = lazyProps.find(name);
+		if (lit != lazyProps.end()) {
+			props[name] = ReadTypedProperty(lit->second);
+			it = props.find(name);
+
+			//ai_assert(it != props.end());
+		}
+
+		if (it == props.end()) {
+			// check property template
+			if (templateProps) {
+				return templateProps->Get(name);
+			}
+
+			return nullptr;
+		}
+	}
+
+	return (*it).second;
+}
+
+DirectPropertyMap PropertyTable::GetUnparsedProperties() const {
+	DirectPropertyMap result;
+
+	// Loop through all the lazy properties (which is all the properties)
+	for (const LazyPropertyMap::value_type &element : lazyProps) {
+		// Skip parsed properties
+		if (props.end() != props.find(element.first))
+			continue;
+
+		// Read the element's value.
+		// Wrap the naked pointer (since the call site is required to acquire ownership)
+		// std::unique_ptr from C++11 would be preferred both as a wrapper and a return value.
+		Property *prop = ReadTypedProperty(element.second);
+
+		// Element could not be read. Skip it.
+		if (!prop)
+			continue;
+
+		// Add to result
+		result[element.first] = prop;
+	}
+
+	return result;
+}
+} // namespace FBXDocParser

+ 221 - 0
modules/fbx/fbx_parser/FBXProperties.h

@@ -0,0 +1,221 @@
+/*************************************************************************/
+/*  FBXProperties.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXProperties.h
+ *  @brief FBX dynamic properties
+ */
+#ifndef FBX_PROPERTIES_H
+#define FBX_PROPERTIES_H
+
+#include "FBXParser.h"
+#include <map>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace FBXDocParser {
+
+// Forward declarations
+class Element;
+
+/** Represents a dynamic property. Type info added by deriving classes,
+ *  see #TypedProperty.
+ Example:
+ @verbatim
+   P: "ShininessExponent", "double", "Number", "",0.5
+ @endvebatim
+*/
+class Property {
+protected:
+	Property();
+
+public:
+	virtual ~Property();
+
+public:
+	template <typename T>
+	const T *As() const {
+		return dynamic_cast<const T *>(this);
+	}
+};
+
+template <typename T>
+class TypedProperty : public Property {
+public:
+	explicit TypedProperty(const T &value) :
+			value(value) {
+		// empty
+	}
+
+	const T &Value() const {
+		return value;
+	}
+
+private:
+	T value;
+};
+
+#define new_Property new Property
+typedef Property *PropertyPtr;
+typedef std::map<std::string, PropertyPtr> DirectPropertyMap;
+typedef std::map<std::string, PropertyPtr> PropertyMap;
+typedef std::map<std::string, ElementPtr> LazyPropertyMap;
+
+/**
+ *  Represents a property table as can be found in the newer FBX files (Properties60, Properties70)
+ */
+class PropertyTable {
+public:
+	// in-memory property table with no source element
+	PropertyTable();
+	PropertyTable(const ElementPtr element, const PropertyTable *templateProps);
+	~PropertyTable();
+
+	PropertyPtr Get(const std::string &name) const;
+
+	// PropertyTable's need not be coupled with FBX elements so this can be NULL
+	ElementPtr GetElement() const {
+		return element;
+	}
+
+	PropertyMap &GetProperties() const {
+		return props;
+	}
+
+	const LazyPropertyMap &GetLazyProperties() const {
+		return lazyProps;
+	}
+
+	const PropertyTable *TemplateProps() const {
+		return templateProps;
+	}
+
+	DirectPropertyMap GetUnparsedProperties() const;
+
+private:
+	LazyPropertyMap lazyProps;
+	mutable PropertyMap props;
+	const PropertyTable *templateProps = nullptr;
+	const ElementPtr element = nullptr;
+};
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+inline T PropertyGet(const PropertyTable *in, const std::string &name, const T &defaultValue) {
+	PropertyPtr prop = in->Get(name);
+	if (nullptr == prop) {
+		return defaultValue;
+	}
+
+	// strong typing, no need to be lenient
+	const TypedProperty<T> *const tprop = prop->As<TypedProperty<T>>();
+	if (nullptr == tprop) {
+		return defaultValue;
+	}
+
+	return tprop->Value();
+}
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+inline T PropertyGet(const PropertyTable *in, const std::string &name, bool &result, bool useTemplate = false) {
+	PropertyPtr prop = in->Get(name);
+	if (nullptr == prop) {
+		if (!useTemplate) {
+			result = false;
+			return T();
+		}
+		const PropertyTable *templ = in->TemplateProps();
+		if (nullptr == templ) {
+			result = false;
+			return T();
+		}
+		prop = templ->Get(name);
+		if (nullptr == prop) {
+			result = false;
+			return T();
+		}
+	}
+
+	// strong typing, no need to be lenient
+	const TypedProperty<T> *const tprop = prop->As<TypedProperty<T>>();
+	if (nullptr == tprop) {
+		result = false;
+		return T();
+	}
+
+	result = true;
+	return tprop->Value();
+}
+} // namespace FBXDocParser
+
+#endif // FBX_PROPERTIES_H

+ 251 - 0
modules/fbx/fbx_parser/FBXTokenizer.cpp

@@ -0,0 +1,251 @@
+/*************************************************************************/
+/*  FBXTokenizer.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXTokenizer.cpp
+ *  @brief Implementation of the FBX broadphase lexer
+ */
+
+// tab width for logging columns
+#define ASSIMP_FBX_TAB_WIDTH 4
+
+#include "FBXTokenizer.h"
+#include "core/string/print_string.h"
+
+namespace FBXDocParser {
+
+// ------------------------------------------------------------------------------------------------
+Token::Token(const char *p_sbegin, const char *p_send, TokenType p_type, unsigned int p_line, unsigned int p_column) :
+		sbegin(p_sbegin),
+		send(p_send),
+		type(p_type),
+		line(p_line),
+		column(p_column) {
+#ifdef DEBUG_ENABLED
+	contents = std::string(sbegin, static_cast<size_t>(send - sbegin));
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+Token::~Token() {
+}
+
+namespace {
+
+// ------------------------------------------------------------------------------------------------
+void TokenizeError(const std::string &message, unsigned int line, unsigned int column) {
+	print_error("[FBX-Tokenize]" + String(message.c_str()) + " " + itos(line) + ":" + itos(column));
+}
+
+// process a potential data token up to 'cur', adding it to 'output_tokens'.
+// ------------------------------------------------------------------------------------------------
+void ProcessDataToken(TokenList &output_tokens, const char *&start, const char *&end,
+		unsigned int line,
+		unsigned int column,
+		TokenType type = TokenType_DATA,
+		bool must_have_token = false) {
+	if (start && end) {
+		// sanity check:
+		// tokens should have no whitespace outside quoted text and [start,end] should
+		// properly delimit the valid range.
+		bool in_double_quotes = false;
+		for (const char *c = start; c != end + 1; ++c) {
+			if (*c == '\"') {
+				in_double_quotes = !in_double_quotes;
+			}
+
+			if (!in_double_quotes && IsSpaceOrNewLine(*c)) {
+				TokenizeError("unexpected whitespace in token", line, column);
+			}
+		}
+
+		if (in_double_quotes) {
+			TokenizeError("non-terminated double quotes", line, column);
+		}
+
+		output_tokens.push_back(new_Token(start, end + 1, type, line, column));
+	} else if (must_have_token) {
+		TokenizeError("unexpected character, expected data token", line, column);
+	}
+
+	start = end = nullptr;
+}
+} // namespace
+
+// ------------------------------------------------------------------------------------------------
+void Tokenize(TokenList &output_tokens, const char *input, size_t length) {
+	// line and column numbers numbers are one-based
+	unsigned int line = 1;
+	unsigned int column = 1;
+
+	bool comment = false;
+	bool in_double_quotes = false;
+	bool pending_data_token = false;
+
+	const char *token_begin = nullptr, *token_end = nullptr;
+
+	// input (starting string), *cur the current string, column +=
+	// modified to fix strlen() and stop buffer overflow
+	for (size_t x = 0; x < length; x++) {
+		const char c = input[x];
+		const char *cur = &input[x];
+		column += (c == '\t' ? ASSIMP_FBX_TAB_WIDTH : 1);
+
+		if (IsLineEnd(c)) {
+			comment = false;
+
+			column = 0;
+			++line;
+		}
+
+		if (comment) {
+			continue;
+		}
+
+		if (in_double_quotes) {
+			if (c == '\"') {
+				in_double_quotes = false;
+				token_end = cur;
+
+				ProcessDataToken(output_tokens, token_begin, token_end, line, column);
+				pending_data_token = false;
+			}
+			continue;
+		}
+
+		switch (c) {
+			case '\"':
+				if (token_begin) {
+					TokenizeError("unexpected double-quote", line, column);
+				}
+				token_begin = cur;
+				in_double_quotes = true;
+				continue;
+
+			case ';':
+				ProcessDataToken(output_tokens, token_begin, token_end, line, column);
+				comment = true;
+				continue;
+
+			case '{':
+				ProcessDataToken(output_tokens, token_begin, token_end, line, column);
+				output_tokens.push_back(new_Token(cur, cur + 1, TokenType_OPEN_BRACKET, line, column));
+				continue;
+
+			case '}':
+				ProcessDataToken(output_tokens, token_begin, token_end, line, column);
+				output_tokens.push_back(new_Token(cur, cur + 1, TokenType_CLOSE_BRACKET, line, column));
+				continue;
+
+			case ',':
+				if (pending_data_token) {
+					ProcessDataToken(output_tokens, token_begin, token_end, line, column, TokenType_DATA, true);
+				}
+				output_tokens.push_back(new_Token(cur, cur + 1, TokenType_COMMA, line, column));
+				continue;
+
+			case ':':
+				if (pending_data_token) {
+					ProcessDataToken(output_tokens, token_begin, token_end, line, column, TokenType_KEY, true);
+				} else {
+					TokenizeError("unexpected colon", line, column);
+				}
+				continue;
+		}
+
+		if (IsSpaceOrNewLine(c)) {
+			if (token_begin) {
+				// peek ahead and check if the next token is a colon in which
+				// case this counts as KEY token.
+				TokenType type = TokenType_DATA;
+				for (const char *peek = cur; *peek && IsSpaceOrNewLine(*peek); ++peek) {
+					if (*peek == ':') {
+						type = TokenType_KEY;
+						cur = peek;
+						break;
+					}
+				}
+
+				ProcessDataToken(output_tokens, token_begin, token_end, line, column, type);
+			}
+
+			pending_data_token = false;
+		} else {
+			token_end = cur;
+			if (!token_begin) {
+				token_begin = cur;
+			}
+
+			pending_data_token = true;
+		}
+	}
+}
+} // namespace FBXDocParser

+ 203 - 0
modules/fbx/fbx_parser/FBXTokenizer.h

@@ -0,0 +1,203 @@
+/*************************************************************************/
+/*  FBXTokenizer.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXTokenizer.h
+ *  @brief FBX lexer
+ */
+#ifndef FBX_TOKENIZER_H
+#define FBX_TOKENIZER_H
+
+#include "FBXParseTools.h"
+#include "core/string/ustring.h"
+#include <iostream>
+#include <memory>
+#include <string>
+#include <vector>
+
+namespace FBXDocParser {
+/** Rough classification for text FBX tokens used for constructing the
+ *  basic scope hierarchy. */
+enum TokenType {
+	// {
+	TokenType_OPEN_BRACKET = 0,
+
+	// }
+	TokenType_CLOSE_BRACKET,
+
+	// '"blablubb"', '2', '*14' - very general token class,
+	// further processing happens at a later stage.
+	TokenType_DATA,
+
+	//
+	TokenType_BINARY_DATA,
+
+	// ,
+	TokenType_COMMA,
+
+	// blubb:
+	TokenType_KEY
+};
+
+/** Represents a single token in a FBX file. Tokens are
+ *  classified by the #TokenType enumerated types.
+ *
+ *  Offers iterator protocol. Tokens are immutable. */
+class Token {
+private:
+	static const unsigned int BINARY_MARKER = static_cast<unsigned int>(-1);
+
+public:
+	/** construct a textual token */
+	Token(const char *p_sbegin, const char *p_send, TokenType p_type, unsigned int p_line, unsigned int p_column);
+
+	/** construct a binary token */
+	Token(const char *p_sbegin, const char *p_send, TokenType p_type, size_t p_offset);
+	~Token();
+
+public:
+	std::string StringContents() const {
+		return std::string(begin(), end());
+	}
+
+	bool IsBinary() const {
+		return column == BINARY_MARKER;
+	}
+
+	const char *begin() const {
+		return sbegin;
+	}
+
+	const char *end() const {
+		return send;
+	}
+
+	TokenType Type() const {
+		return type;
+	}
+
+	size_t Offset() const {
+		return offset;
+	}
+
+	unsigned int Line() const {
+		return static_cast<unsigned int>(line);
+	}
+
+	unsigned int Column() const {
+		return column;
+	}
+
+private:
+#ifdef DEBUG_ENABLED
+	// full string copy for the sole purpose that it nicely appears
+	// in msvc's debugger window.
+	std::string contents;
+#endif
+
+	const char *sbegin = nullptr;
+	const char *send = nullptr;
+	const TokenType type;
+
+	union {
+		size_t line;
+		size_t offset;
+	};
+	const unsigned int column = 0;
+};
+
+// Fixed leak by using shared_ptr for tokens
+typedef Token *TokenPtr;
+typedef std::vector<TokenPtr> TokenList;
+
+#define new_Token new Token
+
+/** Main FBX tokenizer function. Transform input buffer into a list of preprocessed tokens.
+ *
+ *  Skips over comments and generates line and column numbers.
+ *
+ * @param output_tokens Receives a list of all tokens in the input data.
+ * @param input_buffer Textual input buffer to be processed, 0-terminated.
+ * @print_error if something goes wrong */
+void Tokenize(TokenList &output_tokens, const char *input, size_t length);
+
+/** Tokenizer function for binary FBX files.
+ *
+ *  Emits a token list suitable for direct parsing.
+ *
+ * @param output_tokens Receives a list of all tokens in the input data.
+ * @param input_buffer Binary input buffer to be processed.
+ * @param length Length of input buffer, in bytes. There is no 0-terminal.
+ * @print_error if something goes wrong */
+void TokenizeBinary(TokenList &output_tokens, const char *input, size_t length);
+} // namespace FBXDocParser
+
+#endif // FBX_TOKENIZER_H

+ 220 - 0
modules/fbx/fbx_parser/FBXUtil.cpp

@@ -0,0 +1,220 @@
+/*************************************************************************/
+/*  FBXUtil.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXUtil.cpp
+ *  @brief Implementation of internal FBX utility functions
+ */
+
+#include "FBXUtil.h"
+#include "FBXTokenizer.h"
+#include <cstring>
+#include <string>
+
+namespace FBXDocParser {
+namespace Util {
+
+// ------------------------------------------------------------------------------------------------
+const char *TokenTypeString(TokenType t) {
+	switch (t) {
+		case TokenType_OPEN_BRACKET:
+			return "TOK_OPEN_BRACKET";
+
+		case TokenType_CLOSE_BRACKET:
+			return "TOK_CLOSE_BRACKET";
+
+		case TokenType_DATA:
+			return "TOK_DATA";
+
+		case TokenType_COMMA:
+			return "TOK_COMMA";
+
+		case TokenType_KEY:
+			return "TOK_KEY";
+
+		case TokenType_BINARY_DATA:
+			return "TOK_BINARY_DATA";
+	}
+
+	//ai_assert(false);
+	return "";
+}
+
+// Generated by this formula: T["ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"[i]] = i;
+static const uint8_t base64DecodeTable[128] = {
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255,
+	255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 62, 255, 255, 255, 63,
+	52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 255, 255, 255, 255, 255, 255,
+	255, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+	15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 255, 255, 255, 255, 255,
+	255, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+	41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 255, 255, 255, 255, 255
+};
+
+uint8_t DecodeBase64(char ch) {
+	const auto idx = static_cast<uint8_t>(ch);
+	if (idx > 127)
+		return 255;
+	return base64DecodeTable[idx];
+}
+
+size_t ComputeDecodedSizeBase64(const char *in, size_t inLength) {
+	if (inLength < 2) {
+		return 0;
+	}
+	const size_t equals = size_t(in[inLength - 1] == '=') + size_t(in[inLength - 2] == '=');
+	const size_t full_length = (inLength * 3) >> 2; // div by 4
+	if (full_length < equals) {
+		return 0;
+	}
+	return full_length - equals;
+}
+
+size_t DecodeBase64(const char *in, size_t inLength, uint8_t *out, size_t maxOutLength) {
+	if (maxOutLength == 0 || inLength < 2) {
+		return 0;
+	}
+	const size_t realLength = inLength - size_t(in[inLength - 1] == '=') - size_t(in[inLength - 2] == '=');
+	size_t dst_offset = 0;
+	int val = 0, valb = -8;
+	for (size_t src_offset = 0; src_offset < realLength; ++src_offset) {
+		const uint8_t table_value = Util::DecodeBase64(in[src_offset]);
+		if (table_value == 255) {
+			return 0;
+		}
+		val = (val << 6) + table_value;
+		valb += 6;
+		if (valb >= 0) {
+			out[dst_offset++] = static_cast<uint8_t>((val >> valb) & 0xFF);
+			valb -= 8;
+			val &= 0xFFF;
+		}
+	}
+	return dst_offset;
+}
+
+static const char to_base64_string[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+char EncodeBase64(char byte) {
+	return to_base64_string[(size_t)byte];
+}
+
+/** Encodes a block of 4 bytes to base64 encoding
+*  @param bytes Bytes to encode.
+*  @param out_string String to write encoded values to.
+*  @param string_pos Position in out_string.
+*/
+void EncodeByteBlock(const char *bytes, std::string &out_string, size_t string_pos) {
+	char b0 = (bytes[0] & 0xFC) >> 2;
+	char b1 = (bytes[0] & 0x03) << 4 | ((bytes[1] & 0xF0) >> 4);
+	char b2 = (bytes[1] & 0x0F) << 2 | ((bytes[2] & 0xC0) >> 6);
+	char b3 = (bytes[2] & 0x3F);
+
+	out_string[string_pos + 0] = EncodeBase64(b0);
+	out_string[string_pos + 1] = EncodeBase64(b1);
+	out_string[string_pos + 2] = EncodeBase64(b2);
+	out_string[string_pos + 3] = EncodeBase64(b3);
+}
+
+std::string EncodeBase64(const char *data, size_t length) {
+	// calculate extra bytes needed to get a multiple of 3
+	size_t extraBytes = 3 - length % 3;
+
+	// number of base64 bytes
+	size_t encodedBytes = 4 * (length + extraBytes) / 3;
+
+	std::string encoded_string(encodedBytes, '=');
+
+	// read blocks of 3 bytes
+	for (size_t ib3 = 0; ib3 < length / 3; ib3++) {
+		const size_t iByte = ib3 * 3;
+		const size_t iEncodedByte = ib3 * 4;
+		const char *currData = &data[iByte];
+
+		EncodeByteBlock(currData, encoded_string, iEncodedByte);
+	}
+
+	// if size of data is not a multiple of 3, also encode the final bytes (and add zeros where needed)
+	if (extraBytes > 0) {
+		char finalBytes[4] = { 0, 0, 0, 0 };
+		memcpy(&finalBytes[0], &data[length - length % 3], length % 3);
+
+		const size_t iEncodedByte = encodedBytes - 4;
+		EncodeByteBlock(&finalBytes[0], encoded_string, iEncodedByte);
+
+		// add '=' at the end
+		for (size_t i = 0; i < 4 * extraBytes / 3; i++)
+			encoded_string[encodedBytes - i - 1] = '=';
+	}
+	return encoded_string;
+}
+} // namespace Util
+} // namespace FBXDocParser

+ 122 - 0
modules/fbx/fbx_parser/FBXUtil.h

@@ -0,0 +1,122 @@
+/*************************************************************************/
+/*  FBXUtil.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.                */
+/*************************************************************************/
+
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2019, assimp team
+
+
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+----------------------------------------------------------------------
+*/
+
+/** @file  FBXUtil.h
+ *  @brief FBX utility functions for internal use
+ */
+#ifndef FBX_UTIL_H
+#define FBX_UTIL_H
+
+#include "FBXTokenizer.h"
+#include <stdint.h>
+
+namespace FBXDocParser {
+
+namespace Util {
+
+/** Get a string representation for a #TokenType. */
+const char *TokenTypeString(TokenType t);
+
+/** Decode a single Base64-encoded character.
+*
+*  @param ch Character to decode (from base64 to binary).
+*  @return decoded byte value*/
+uint8_t DecodeBase64(char ch);
+
+/** Compute decoded size of a Base64-encoded string
+*
+*  @param in Characters to decode.
+*  @param inLength Number of characters to decode.
+*  @return size of the decoded data (number of bytes)*/
+size_t ComputeDecodedSizeBase64(const char *in, size_t inLength);
+
+/** Decode a Base64-encoded string
+*
+*  @param in Characters to decode.
+*  @param inLength Number of characters to decode.
+*  @param out Pointer where we will store the decoded data.
+*  @param maxOutLength Size of output buffer.
+*  @return size of the decoded data (number of bytes)*/
+size_t DecodeBase64(const char *in, size_t inLength, uint8_t *out, size_t maxOutLength);
+
+char EncodeBase64(char byte);
+
+/** Encode bytes in base64-encoding
+*
+*  @param data Binary data to encode.
+*  @param inLength Number of bytes to encode.
+*  @return base64-encoded string*/
+std::string EncodeBase64(const char *data, size_t length);
+} // namespace Util
+} // namespace FBXDocParser
+
+#endif // FBX_UTIL_H

+ 39 - 0
modules/fbx/fbx_parser/LICENSE

@@ -0,0 +1,39 @@
+The files in this folder were originally from ASSIMP, but have been heavily modified to fix bugs and match coding
+conventions of the Godot Engine project. We have kept a copy of the applicable licenses in the folder as required by
+the license.
+
+Open Asset Import Library (assimp)
+
+Copyright (c) 2006-2020, assimp team
+All rights reserved.
+
+Redistribution and use of this software in source and binary forms,
+with or without modification, are permitted provided that the
+following conditions are met:
+
+* Redistributions of source code must retain the above
+  copyright notice, this list of conditions and the
+  following disclaimer.
+
+* Redistributions in binary form must reproduce the above
+  copyright notice, this list of conditions and the
+  following disclaimer in the documentation and/or other
+  materials provided with the distribution.
+
+* Neither the name of the assimp team, nor the names of its
+  contributors may be used to endorse or promote products
+  derived from this software without specific prior
+  written permission of the assimp team.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+

+ 58 - 0
modules/fbx/register_types.cpp

@@ -0,0 +1,58 @@
+/*************************************************************************/
+/*  register_types.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 "register_types.h"
+
+#include "editor/editor_node.h"
+#include "editor_scene_importer_fbx.h"
+
+#ifdef TOOLS_ENABLED
+static void _editor_init() {
+	Ref<EditorSceneImporterFBX> import_fbx;
+	import_fbx.instance();
+	ResourceImporterScene::get_singleton()->add_importer(import_fbx);
+}
+#endif
+
+void register_fbx_types() {
+#ifdef TOOLS_ENABLED
+	ClassDB::APIType prev_api = ClassDB::get_current_api();
+	ClassDB::set_current_api(ClassDB::API_EDITOR);
+
+	ClassDB::register_class<EditorSceneImporterFBX>();
+
+	ClassDB::set_current_api(prev_api);
+
+	EditorNode::add_init_callback(_editor_init);
+#endif
+}
+
+void unregister_fbx_types() {
+}

+ 37 - 0
modules/fbx/register_types.h

@@ -0,0 +1,37 @@
+/*************************************************************************/
+/*  register_types.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_REGISTER_TYPES_H
+#define FBX_REGISTER_TYPES_H
+
+void register_fbx_types();
+void unregister_fbx_types();
+
+#endif // FBX_REGISTER_TYPES_H

+ 151 - 0
modules/fbx/tools/import_utils.cpp

@@ -0,0 +1,151 @@
+/*************************************************************************/
+/*  import_utils.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 "import_utils.h"
+
+Vector3 ImportUtils::deg2rad(const Vector3 &p_rotation) {
+	return p_rotation / 180.0 * Math_PI;
+}
+
+Vector3 ImportUtils::rad2deg(const Vector3 &p_rotation) {
+	return p_rotation / Math_PI * 180.0;
+}
+
+Basis ImportUtils::EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation) {
+	Basis ret;
+
+	// FBX is using intrinsic euler, we can convert intrinsic to extrinsic (the one used in godot
+	// by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf
+	switch (mode) {
+		case FBXDocParser::Model::RotOrder_EulerXYZ:
+			ret.set_euler_zyx(p_rotation);
+			break;
+
+		case FBXDocParser::Model::RotOrder_EulerXZY:
+			ret.set_euler_yzx(p_rotation);
+			break;
+
+		case FBXDocParser::Model::RotOrder_EulerYZX:
+			ret.set_euler_xzy(p_rotation);
+			break;
+
+		case FBXDocParser::Model::RotOrder_EulerYXZ:
+			ret.set_euler_zxy(p_rotation);
+			break;
+
+		case FBXDocParser::Model::RotOrder_EulerZXY:
+			ret.set_euler_yxz(p_rotation);
+			break;
+
+		case FBXDocParser::Model::RotOrder_EulerZYX:
+			ret.set_euler_xyz(p_rotation);
+			break;
+
+		case FBXDocParser::Model::RotOrder_SphericXYZ:
+			// TODO do this.
+			break;
+
+		default:
+			// If you land here, Please integrate all enums.
+			CRASH_NOW_MSG("This is not unreachable.");
+	}
+
+	return ret;
+}
+
+Quat ImportUtils::EulerToQuaternion(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation) {
+	return ImportUtils::EulerToBasis(mode, p_rotation);
+}
+
+Vector3 ImportUtils::BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basis &p_rotation) {
+	// FBX is using intrinsic euler, we can convert intrinsic to extrinsic (the one used in godot
+	// by simply invert its order: https://www.cs.utexas.edu/~theshark/courses/cs354/lectures/cs354-14.pdf
+	switch (mode) {
+		case FBXDocParser::Model::RotOrder_EulerXYZ:
+			return p_rotation.get_euler_zyx();
+
+		case FBXDocParser::Model::RotOrder_EulerXZY:
+			return p_rotation.get_euler_yzx();
+
+		case FBXDocParser::Model::RotOrder_EulerYZX:
+			return p_rotation.get_euler_xzy();
+
+		case FBXDocParser::Model::RotOrder_EulerYXZ:
+			return p_rotation.get_euler_zxy();
+
+		case FBXDocParser::Model::RotOrder_EulerZXY:
+			return p_rotation.get_euler_yxz();
+
+		case FBXDocParser::Model::RotOrder_EulerZYX:
+			return p_rotation.get_euler_xyz();
+
+		case FBXDocParser::Model::RotOrder_SphericXYZ:
+			// TODO
+			return Vector3();
+
+		default:
+			// If you land here, Please integrate all enums.
+			CRASH_NOW_MSG("This is not unreachable.");
+			return Vector3();
+	}
+}
+
+Vector3 ImportUtils::QuaternionToEuler(FBXDocParser::Model::RotOrder mode, const Quat &p_rotation) {
+	return BasisToEuler(mode, p_rotation);
+}
+
+Transform get_unscaled_transform(const Transform &p_initial, real_t p_scale) {
+	Transform unscaled = Transform(p_initial.basis, p_initial.origin * p_scale);
+	ERR_FAIL_COND_V_MSG(unscaled.basis.determinant() == 0, Transform(), "det is zero unscaled?");
+	return unscaled;
+}
+
+Vector3 get_poly_normal(const std::vector<Vector3> &p_vertices) {
+	ERR_FAIL_COND_V_MSG(p_vertices.size() < 3, Vector3(0, 0, 0), "At least 3 vertices are necesary");
+	// Using long double to make sure that normal is computed for even really tiny objects.
+	typedef long double ldouble;
+	ldouble x = 0.0;
+	ldouble y = 0.0;
+	ldouble z = 0.0;
+	for (size_t i = 0; i < p_vertices.size(); i += 1) {
+		const Vector3 current = p_vertices[i];
+		const Vector3 next = p_vertices[(i + 1) % p_vertices.size()];
+		x += (ldouble(current.y) - ldouble(next.y)) * (ldouble(current.z) + ldouble(next.z));
+		y += (ldouble(current.z) - ldouble(next.z)) * (ldouble(current.x) + ldouble(next.x));
+		z += (ldouble(current.x) - ldouble(next.x)) * (ldouble(current.y) + ldouble(next.y));
+	}
+	const ldouble l2 = x * x + y * y + z * z;
+	if (l2 == 0.0) {
+		return (p_vertices[0] - p_vertices[1]).normalized().cross((p_vertices[0] - p_vertices[2]).normalized()).normalized();
+	} else {
+		const double l = Math::sqrt(double(l2));
+		return Vector3(x / l, y / l, z / l);
+	}
+}

+ 400 - 0
modules/fbx/tools/import_utils.h

@@ -0,0 +1,400 @@
+/*************************************************************************/
+/*  import_utils.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_UTILS_FBX_IMPORTER_H
+#define IMPORT_UTILS_FBX_IMPORTER_H
+
+#include "core/io/image_loader.h"
+
+#include "data/import_state.h"
+#include "fbx_parser/FBXDocument.h"
+
+#include <string>
+
+#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000LL
+
+/**
+ * Import Utils
+ * Conversion tools / glue code to convert from FBX to Godot
+*/
+class ImportUtils {
+public:
+	///	Convert a vector from degrees to radians.
+	static Vector3 deg2rad(const Vector3 &p_rotation);
+
+	///	Convert a vector from radians to degrees.
+	static Vector3 rad2deg(const Vector3 &p_rotation);
+
+	/// Converts rotation order vector (in rad) to quaternion.
+	static Basis EulerToBasis(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation);
+
+	/// Converts rotation order vector (in rad) to quaternion.
+	static Quat EulerToQuaternion(FBXDocParser::Model::RotOrder mode, const Vector3 &p_rotation);
+
+	/// Converts basis into rotation order vector (in rad).
+	static Vector3 BasisToEuler(FBXDocParser::Model::RotOrder mode, const Basis &p_rotation);
+
+	/// Converts quaternion into rotation order vector (in rad).
+	static Vector3 QuaternionToEuler(FBXDocParser::Model::RotOrder mode, const Quat &p_rotation);
+
+	static void debug_xform(String name, const Transform &t) {
+		print_verbose(name + " " + t.origin + " rotation: " + (t.basis.get_euler() * (180 / Math_PI)));
+	}
+
+	static String FBXNodeToName(const std::string &name) {
+		// strip Model:: prefix, avoiding ambiguities (i.e. don't strip if
+		// this causes ambiguities, well possible between empty identifiers,
+		// such as "Model::" and ""). Make sure the behaviour is consistent
+		// across multiple calls to FixNodeName().
+
+		// We must remove this from the name
+		// Some bones have this
+		// SubDeformer::
+		// Meshes, Joints have this, some other IK elements too.
+		// Model::
+
+		String node_name = String(name.c_str());
+
+		if (node_name.substr(0, 7) == "Model::") {
+			node_name = node_name.substr(7, node_name.length() - 7);
+			return node_name.replace(":", "");
+		}
+
+		if (node_name.substr(0, 13) == "SubDeformer::") {
+			node_name = node_name.substr(13, node_name.length() - 13);
+			return node_name.replace(":", "");
+		}
+
+		if (node_name.substr(0, 11) == "AnimStack::") {
+			node_name = node_name.substr(11, node_name.length() - 11);
+			return node_name.replace(":", "");
+		}
+
+		if (node_name.substr(0, 15) == "AnimCurveNode::") {
+			node_name = node_name.substr(15, node_name.length() - 15);
+			return node_name.replace(":", "");
+		}
+
+		if (node_name.substr(0, 11) == "AnimCurve::") {
+			node_name = node_name.substr(11, node_name.length() - 11);
+			return node_name.replace(":", "");
+		}
+
+		if (node_name.substr(0, 10) == "Geometry::") {
+			node_name = node_name.substr(10, node_name.length() - 10);
+			return node_name.replace(":", "");
+		}
+
+		if (node_name.substr(0, 10) == "Material::") {
+			node_name = node_name.substr(10, node_name.length() - 10);
+			return node_name.replace(":", "");
+		}
+
+		if (node_name.substr(0, 9) == "Texture::") {
+			node_name = node_name.substr(9, node_name.length() - 9);
+			return node_name.replace(":", "");
+		}
+
+		return node_name.replace(":", "");
+	}
+
+	static std::string FBXAnimMeshName(const std::string &name) {
+		if (name.length()) {
+			size_t indexOf = name.find_first_of("::");
+			if (indexOf != std::string::npos && indexOf < name.size() - 2) {
+				return name.substr(indexOf + 2);
+			}
+		}
+		return name.length() ? name : "AnimMesh";
+	}
+
+	static Vector3 safe_import_vector3(const Vector3 &p_vec) {
+		Vector3 vector = p_vec;
+		if (Math::is_equal_approx(0, vector.x)) {
+			vector.x = 0;
+		}
+
+		if (Math::is_equal_approx(0, vector.y)) {
+			vector.y = 0;
+		}
+
+		if (Math::is_equal_approx(0, vector.z)) {
+			vector.z = 0;
+		}
+		return vector;
+	}
+
+	static void debug_xform(String name, const Basis &t) {
+		//print_verbose(name + " rotation: " + (t.get_euler() * (180 / Math_PI)));
+	}
+
+	static Vector3 FixAxisConversions(Vector3 input) {
+		return Vector3(input.x, input.y, input.z);
+	}
+
+	static void AlignMeshAxes(std::vector<Vector3> &vertex_data) {
+		for (size_t x = 0; x < vertex_data.size(); x++) {
+			vertex_data[x] = FixAxisConversions(vertex_data[x]);
+		}
+	}
+
+	struct AssetImportFbx {
+		enum ETimeMode {
+			TIME_MODE_DEFAULT = 0,
+			TIME_MODE_120 = 1,
+			TIME_MODE_100 = 2,
+			TIME_MODE_60 = 3,
+			TIME_MODE_50 = 4,
+			TIME_MODE_48 = 5,
+			TIME_MODE_30 = 6,
+			TIME_MODE_30_DROP = 7,
+			TIME_MODE_NTSC_DROP_FRAME = 8,
+			TIME_MODE_NTSC_FULL_FRAME = 9,
+			TIME_MODE_PAL = 10,
+			TIME_MODE_CINEMA = 11,
+			TIME_MODE_1000 = 12,
+			TIME_MODE_CINEMA_ND = 13,
+			TIME_MODE_CUSTOM = 14,
+			TIME_MODE_TIME_MODE_COUNT = 15
+		};
+		enum UpAxis {
+			UP_VECTOR_AXIS_X = 1,
+			UP_VECTOR_AXIS_Y = 2,
+			UP_VECTOR_AXIS_Z = 3
+		};
+		enum FrontAxis {
+			FRONT_PARITY_EVEN = 1,
+			FRONT_PARITY_ODD = 2,
+		};
+
+		enum CoordAxis {
+			COORD_RIGHT = 0,
+			COORD_LEFT = 1
+		};
+	};
+
+	/** Get fbx fps for time mode meta data
+     */
+	static float get_fbx_fps(int32_t time_mode) {
+		switch (time_mode) {
+			case AssetImportFbx::TIME_MODE_DEFAULT:
+				return 24;
+			case AssetImportFbx::TIME_MODE_120:
+				return 120;
+			case AssetImportFbx::TIME_MODE_100:
+				return 100;
+			case AssetImportFbx::TIME_MODE_60:
+				return 60;
+			case AssetImportFbx::TIME_MODE_50:
+				return 50;
+			case AssetImportFbx::TIME_MODE_48:
+				return 48;
+			case AssetImportFbx::TIME_MODE_30:
+				return 30;
+			case AssetImportFbx::TIME_MODE_30_DROP:
+				return 30;
+			case AssetImportFbx::TIME_MODE_NTSC_DROP_FRAME:
+				return 29.9700262f;
+			case AssetImportFbx::TIME_MODE_NTSC_FULL_FRAME:
+				return 29.9700262f;
+			case AssetImportFbx::TIME_MODE_PAL:
+				return 25;
+			case AssetImportFbx::TIME_MODE_CINEMA:
+				return 24;
+			case AssetImportFbx::TIME_MODE_1000:
+				return 1000;
+			case AssetImportFbx::TIME_MODE_CINEMA_ND:
+				return 23.976f;
+			case AssetImportFbx::TIME_MODE_CUSTOM:
+				return -1;
+		}
+		return 0;
+	}
+
+	static float get_fbx_fps(const FBXDocParser::FileGlobalSettings *FBXSettings) {
+		int time_mode = FBXSettings->TimeMode();
+
+		// get the animation FPS
+		float frames_per_second = get_fbx_fps(time_mode);
+
+		// handle animation custom FPS time.
+		if (time_mode == ImportUtils::AssetImportFbx::TIME_MODE_CUSTOM) {
+			print_verbose("FBX Animation has custom FPS setting");
+			frames_per_second = FBXSettings->CustomFrameRate();
+
+			// not our problem this is the modeller, we can print as an error so they can fix the source.
+			if (frames_per_second == 0) {
+				print_error("Custom animation time in file is set to 0 value, animation won't play, please edit your file to correct the FPS value");
+			}
+		}
+		return frames_per_second;
+	}
+
+	/**
+	  * Find hardcoded textures from assimp which could be in many different directories
+	  */
+
+	/**
+	  * set_texture_mapping_mode
+	  * Helper to check the mapping mode of the texture (repeat, clamp and mirror)
+	  */
+	// static void set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<ImageTexture> texture) {
+	// 	ERR_FAIL_COND(texture.is_null());
+	// 	ERR_FAIL_COND(map_mode == NULL);
+	// 	aiTextureMapMode tex_mode = map_mode[0];
+
+	// 	int32_t flags = Texture::FLAGS_DEFAULT;
+	// 	if (tex_mode == aiTextureMapMode_Wrap) {
+	// 		//Default
+	// 	} else if (tex_mode == aiTextureMapMode_Clamp) {
+	// 		flags = flags & ~Texture::FLAG_REPEAT;
+	// 	} else if (tex_mode == aiTextureMapMode_Mirror) {
+	// 		flags = flags | Texture::FLAG_MIRRORED_REPEAT;
+	// 	}
+	// 	texture->set_flags(flags);
+	// }
+
+	/**
+	  * Load or load from cache image :)
+	  * We need to upgrade this in the later version :) should not be hard
+	  */
+	//static Ref<Image> load_image(ImportState &state, const aiScene *p_scene, String p_path){
+	// Map<String, Ref<Image> >::Element *match = state.path_to_image_cache.find(p_path);
+
+	// // if our cache contains this image then don't bother
+	// if (match) {
+	// 	return match->get();
+	// }
+
+	// Vector<String> split_path = p_path.get_basename().split("*");
+	// if (split_path.size() == 2) {
+	// 	size_t texture_idx = split_path[1].to_int();
+	// 	ERR_FAIL_COND_V(texture_idx >= p_scene->mNumTextures, Ref<Image>());
+	// 	aiTexture *tex = p_scene->mTextures[texture_idx];
+	// 	String filename = AssimpUtils::get_raw_string_from_assimp(tex->mFilename);
+	// 	filename = filename.get_file();
+	// 	print_verbose("Open Asset Import: Loading embedded texture " + filename);
+	// 	if (tex->mHeight == 0) {
+	// 		if (tex->CheckFormat("png")) {
+	// 			Ref<Image> img = Image::_png_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
+	// 			ERR_FAIL_COND_V(img.is_null(), Ref<Image>());
+	// 			state.path_to_image_cache.insert(p_path, img);
+	// 			return img;
+	// 		} else if (tex->CheckFormat("jpg")) {
+	// 			Ref<Image> img = Image::_jpg_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
+	// 			ERR_FAIL_COND_V(img.is_null(), Ref<Image>());
+	// 			state.path_to_image_cache.insert(p_path, img);
+	// 			return img;
+	// 		} else if (tex->CheckFormat("dds")) {
+	// 			ERR_FAIL_COND_V_MSG(true, Ref<Image>(), "Open Asset Import: Embedded dds not implemented");
+	// 		}
+	// 	} else {
+	// 		Ref<Image> img;
+	// 		img.instance();
+	// 		PoolByteArray arr;
+	// 		uint32_t size = tex->mWidth * tex->mHeight;
+	// 		arr.resize(size);
+	// 		memcpy(arr.write().ptr(), tex->pcData, size);
+	// 		ERR_FAIL_COND_V(arr.size() % 4 != 0, Ref<Image>());
+	// 		//ARGB8888 to RGBA8888
+	// 		for (int32_t i = 0; i < arr.size() / 4; i++) {
+	// 			arr.write().ptr()[(4 * i) + 3] = arr[(4 * i) + 0];
+	// 			arr.write().ptr()[(4 * i) + 0] = arr[(4 * i) + 1];
+	// 			arr.write().ptr()[(4 * i) + 1] = arr[(4 * i) + 2];
+	// 			arr.write().ptr()[(4 * i) + 2] = arr[(4 * i) + 3];
+	// 		}
+	// 		img->create(tex->mWidth, tex->mHeight, true, Image::FORMAT_RGBA8, arr);
+	// 		ERR_FAIL_COND_V(img.is_null(), Ref<Image>());
+	// 		state.path_to_image_cache.insert(p_path, img);
+	// 		return img;
+	// 	}
+	// 	return Ref<Image>();
+	// } else {
+	// 	Ref<Texture> texture = ResourceLoader::load(p_path);
+	// 	ERR_FAIL_COND_V(texture.is_null(), Ref<Image>());
+	// 	Ref<Image> image = texture->get_data();
+	// 	ERR_FAIL_COND_V(image.is_null(), Ref<Image>());
+	// 	state.path_to_image_cache.insert(p_path, image);
+	// 	return image;
+	// }
+
+	// return Ref<Image>();
+	//}
+
+	// /* create texture from assimp data, if found in path */
+	// static bool CreateAssimpTexture(
+	// 		AssimpImporter::ImportState &state,
+	// 		aiString texture_path,
+	// 		String &filename,
+	// 		String &path,
+	// 		AssimpImageData &image_state) {
+	// 	filename = get_raw_string_from_assimp(texture_path);
+	// 	path = state.path.get_base_dir().plus_file(filename.replace("\\", "/"));
+	// 	bool found = false;
+	// 	find_texture_path(state.path, path, found);
+	// 	if (found) {
+	// 		image_state.raw_image = AssimpUtils::load_image(state, state.assimp_scene, path);
+	// 		if (image_state.raw_image.is_valid()) {
+	// 			image_state.texture.instance();
+	// 			image_state.texture->create_from_image(image_state.raw_image);
+	// 			image_state.texture->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
+	// 			return true;
+	// 		}
+	// 	}
+
+	// 	return false;
+	// }
+	// /** GetAssimpTexture
+	//   * Designed to retrieve textures for you
+	//   */
+	// static bool GetAssimpTexture(
+	// 		AssimpImporter::ImportState &state,
+	// 		aiMaterial *ai_material,
+	// 		aiTextureType texture_type,
+	// 		String &filename,
+	// 		String &path,
+	// 		AssimpImageData &image_state) {
+	// 	aiString ai_filename = aiString();
+	// 	if (AI_SUCCESS == ai_material->GetTexture(texture_type, 0, &ai_filename, NULL, NULL, NULL, NULL, image_state.map_mode)) {
+	// 		return CreateAssimpTexture(state, ai_filename, filename, path, image_state);
+	// 	}
+
+	// 	return false;
+	// }
+};
+
+// Apply the transforms so the basis will have scale 1.
+Transform get_unscaled_transform(const Transform &p_initial, real_t p_scale);
+
+/// Uses the Newell's method to compute any polygon normal.
+/// The polygon must be at least size of 3 or bigger.
+Vector3 get_poly_normal(const std::vector<Vector3> &p_vertices);
+
+#endif // IMPORT_UTILS_FBX_IMPORTER_H

+ 48 - 0
modules/fbx/tools/validation_tools.cpp

@@ -0,0 +1,48 @@
+/*************************************************************************/
+/*  validation_tools.cpp                                                 */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#include "validation_tools.h"
+
+#ifdef TOOLS_ENABLED
+
+#include "core/string/print_string.h"
+#include "core/string/ustring.h"
+
+ValidationTracker::Entries *ValidationTracker::entries_singleton = memnew(ValidationTracker::Entries);
+
+// for printing our CSV to dump validation problems of files
+// later we can make some agnostic tooling for this but this is fine for the time being.
+void ValidationTracker::Entries::add_validation_error(String asset_path, String message) {
+	print_error(message);
+	// note: implementation is static
+	validation_entries[asset_path].push_back(message);
+}
+
+#endif // TOOLS_ENABLED

+ 92 - 0
modules/fbx/tools/validation_tools.h

@@ -0,0 +1,92 @@
+/*************************************************************************/
+/*  validation_tools.h                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2020 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2020 Godot Engine contributors (cf. AUTHORS.md).   */
+/*                                                                       */
+/* Permission is hereby granted, free of charge, to any person obtaining */
+/* a copy of this software and associated documentation files (the       */
+/* "Software"), to deal in the Software without restriction, including   */
+/* without limitation the rights to use, copy, modify, merge, publish,   */
+/* distribute, sublicense, and/or sell copies of the Software, and to    */
+/* permit persons to whom the Software is furnished to do so, subject to */
+/* the following conditions:                                             */
+/*                                                                       */
+/* The above copyright notice and this permission notice shall be        */
+/* included in all copies or substantial portions of the Software.       */
+/*                                                                       */
+/* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,       */
+/* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF    */
+/* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.*/
+/* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY  */
+/* CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,  */
+/* TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE     */
+/* SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.                */
+/*************************************************************************/
+
+#ifndef FBX_VALIDATION_TOOLS_H
+#define FBX_VALIDATION_TOOLS_H
+
+#ifdef TOOLS_ENABLED
+
+#include "core/io/json.h"
+#include "core/os/file_access.h"
+#include "core/string/ustring.h"
+#include "core/templates/local_vector.h"
+#include "core/templates/map.h"
+
+class ValidationTracker {
+protected:
+	struct Entries {
+		Map<String, LocalVector<String>> validation_entries = Map<String, LocalVector<String>>();
+
+		// for printing our CSV to dump validation problems of files
+		// later we can make some agnostic tooling for this but this is fine for the time being.
+		void add_validation_error(String asset_path, String message);
+		void print_to_csv() {
+			print_verbose("Exporting assset validation log please wait");
+			String massive_log_file;
+
+			String csv_header = "file_path, error message, extra data\n";
+			massive_log_file += csv_header;
+
+			for (Map<String, LocalVector<String>>::Element *element = validation_entries.front(); element; element = element->next()) {
+				for (unsigned int x = 0; x < element->value().size(); x++) {
+					const String &line_entry = element->key() + ", " + element->value()[x].c_escape() + "\n";
+					massive_log_file += line_entry;
+				}
+			}
+
+			String path = "asset_validation_errors.csv";
+			Error err;
+			FileAccess *file = FileAccess::open(path, FileAccess::WRITE, &err);
+			if (!file || err) {
+				if (file)
+					memdelete(file);
+				print_error("ValidationTracker Error - failed to create file - path: %s\n" + path);
+				return;
+			}
+
+			file->store_string(massive_log_file);
+			if (file->get_error() != OK && file->get_error() != ERR_FILE_EOF) {
+				print_error("ValidationTracker Error - failed to write to file - path: %s\n" + path);
+			}
+			file->close();
+			memdelete(file);
+		}
+	};
+	// asset path, error messages
+	static Entries *entries_singleton;
+
+public:
+	static Entries *get_singleton() {
+		return entries_singleton;
+	}
+};
+
+#endif // TOOLS_ENABLED
+#endif // FBX_VALIDATION_TOOLS_H