Browse Source

Merge pull request #23837 from fire/asset_import

Add Open Asset Importer to Godot.
Rémi Verschelde 6 years ago
parent
commit
7ca9863079
100 changed files with 34680 additions and 2 deletions
  1. 5 0
      COPYRIGHT.txt
  2. 38 0
      doc/classes/EditorSceneImporterAssimp.xml
  3. 1 1
      drivers/gles3/rasterizer_storage_gles3.cpp
  4. 95 0
      modules/assimp/SCsub
  5. 5 0
      modules/assimp/config.py
  6. 2181 0
      modules/assimp/editor_scene_importer_assimp.cpp
  7. 206 0
      modules/assimp/editor_scene_importer_assimp.h
  8. 261 0
      modules/assimp/godot_update_assimp.sh
  9. 53 0
      modules/assimp/register_types.cpp
  10. 32 0
      modules/assimp/register_types.h
  11. 1 1
      scene/3d/mesh_instance.cpp
  12. 6 0
      thirdparty/README.md
  13. 183 0
      thirdparty/assimp/CREDITS
  14. 78 0
      thirdparty/assimp/LICENSE
  15. 980 0
      thirdparty/assimp/assimp/config.h
  16. 616 0
      thirdparty/assimp/code/BaseImporter.cpp
  17. 107 0
      thirdparty/assimp/code/BaseProcess.cpp
  18. 290 0
      thirdparty/assimp/code/BaseProcess.h
  19. 155 0
      thirdparty/assimp/code/Bitmap.cpp
  20. 136 0
      thirdparty/assimp/code/CInterfaceIOWrapper.cpp
  21. 99 0
      thirdparty/assimp/code/CInterfaceIOWrapper.h
  22. 319 0
      thirdparty/assimp/code/CalcTangentsProcess.cpp
  23. 117 0
      thirdparty/assimp/code/CalcTangentsProcess.h
  24. 506 0
      thirdparty/assimp/code/ComputeUVMappingProcess.cpp
  25. 148 0
      thirdparty/assimp/code/ComputeUVMappingProcess.h
  26. 414 0
      thirdparty/assimp/code/ConvertToLHProcess.cpp
  27. 170 0
      thirdparty/assimp/code/ConvertToLHProcess.h
  28. 92 0
      thirdparty/assimp/code/CreateAnimMesh.cpp
  29. 465 0
      thirdparty/assimp/code/DeboneProcess.cpp
  30. 134 0
      thirdparty/assimp/code/DeboneProcess.h
  31. 154 0
      thirdparty/assimp/code/DefaultIOStream.cpp
  32. 257 0
      thirdparty/assimp/code/DefaultIOSystem.cpp
  33. 418 0
      thirdparty/assimp/code/DefaultLogger.cpp
  34. 65 0
      thirdparty/assimp/code/DefaultProgressHandler.h
  35. 109 0
      thirdparty/assimp/code/DropFaceNormalsProcess.cpp
  36. 86 0
      thirdparty/assimp/code/DropFaceNormalsProcess.h
  37. 152 0
      thirdparty/assimp/code/EmbedTexturesProcess.cpp
  38. 85 0
      thirdparty/assimp/code/EmbedTexturesProcess.h
  39. 648 0
      thirdparty/assimp/code/Exporter.cpp
  40. 305 0
      thirdparty/assimp/code/FBXAnimation.cpp
  41. 466 0
      thirdparty/assimp/code/FBXBinaryTokenizer.cpp
  42. 86 0
      thirdparty/assimp/code/FBXCommon.h
  43. 70 0
      thirdparty/assimp/code/FBXCompileConfig.h
  44. 3515 0
      thirdparty/assimp/code/FBXConverter.cpp
  45. 457 0
      thirdparty/assimp/code/FBXConverter.h
  46. 213 0
      thirdparty/assimp/code/FBXDeformer.cpp
  47. 726 0
      thirdparty/assimp/code/FBXDocument.cpp
  48. 1180 0
      thirdparty/assimp/code/FBXDocument.h
  49. 135 0
      thirdparty/assimp/code/FBXDocumentUtil.cpp
  50. 120 0
      thirdparty/assimp/code/FBXDocumentUtil.h
  51. 568 0
      thirdparty/assimp/code/FBXExportNode.cpp
  52. 271 0
      thirdparty/assimp/code/FBXExportNode.h
  53. 364 0
      thirdparty/assimp/code/FBXExportProperty.cpp
  54. 129 0
      thirdparty/assimp/code/FBXExportProperty.h
  55. 2480 0
      thirdparty/assimp/code/FBXExporter.cpp
  56. 178 0
      thirdparty/assimp/code/FBXExporter.h
  57. 153 0
      thirdparty/assimp/code/FBXImportSettings.h
  58. 197 0
      thirdparty/assimp/code/FBXImporter.cpp
  59. 100 0
      thirdparty/assimp/code/FBXImporter.h
  60. 351 0
      thirdparty/assimp/code/FBXMaterial.cpp
  61. 711 0
      thirdparty/assimp/code/FBXMeshGeometry.cpp
  62. 235 0
      thirdparty/assimp/code/FBXMeshGeometry.h
  63. 153 0
      thirdparty/assimp/code/FBXModel.cpp
  64. 170 0
      thirdparty/assimp/code/FBXNodeAttribute.cpp
  65. 1313 0
      thirdparty/assimp/code/FBXParser.cpp
  66. 235 0
      thirdparty/assimp/code/FBXParser.h
  67. 235 0
      thirdparty/assimp/code/FBXProperties.cpp
  68. 185 0
      thirdparty/assimp/code/FBXProperties.h
  69. 248 0
      thirdparty/assimp/code/FBXTokenizer.cpp
  70. 187 0
      thirdparty/assimp/code/FBXTokenizer.h
  71. 120 0
      thirdparty/assimp/code/FBXUtil.cpp
  72. 105 0
      thirdparty/assimp/code/FBXUtil.h
  73. 1834 0
      thirdparty/assimp/code/FIReader.cpp
  74. 107 0
      thirdparty/assimp/code/FileLogStream.h
  75. 345 0
      thirdparty/assimp/code/FileSystemFilter.h
  76. 300 0
      thirdparty/assimp/code/FindDegenerates.cpp
  77. 129 0
      thirdparty/assimp/code/FindDegenerates.h
  78. 277 0
      thirdparty/assimp/code/FindInstancesProcess.cpp
  79. 137 0
      thirdparty/assimp/code/FindInstancesProcess.h
  80. 424 0
      thirdparty/assimp/code/FindInvalidDataProcess.cpp
  81. 105 0
      thirdparty/assimp/code/FindInvalidDataProcess.h
  82. 184 0
      thirdparty/assimp/code/FixNormalsStep.cpp
  83. 91 0
      thirdparty/assimp/code/FixNormalsStep.h
  84. 146 0
      thirdparty/assimp/code/GenFaceNormalsProcess.cpp
  85. 87 0
      thirdparty/assimp/code/GenFaceNormalsProcess.h
  86. 239 0
      thirdparty/assimp/code/GenVertexNormalsProcess.cpp
  87. 115 0
      thirdparty/assimp/code/GenVertexNormalsProcess.h
  88. 1171 0
      thirdparty/assimp/code/Importer.cpp
  89. 247 0
      thirdparty/assimp/code/Importer.h
  90. 371 0
      thirdparty/assimp/code/ImporterRegistry.cpp
  91. 386 0
      thirdparty/assimp/code/ImproveCacheLocality.cpp
  92. 100 0
      thirdparty/assimp/code/ImproveCacheLocality.h
  93. 463 0
      thirdparty/assimp/code/JoinVerticesProcess.cpp
  94. 99 0
      thirdparty/assimp/code/JoinVerticesProcess.h
  95. 201 0
      thirdparty/assimp/code/LimitBoneWeightsProcess.cpp
  96. 148 0
      thirdparty/assimp/code/LimitBoneWeightsProcess.h
  97. 83 0
      thirdparty/assimp/code/MMDCpp14.h
  98. 370 0
      thirdparty/assimp/code/MMDImporter.cpp
  99. 96 0
      thirdparty/assimp/code/MMDImporter.h
  100. 597 0
      thirdparty/assimp/code/MMDPmdParser.h

+ 5 - 0
COPYRIGHT.txt

@@ -110,6 +110,11 @@ Copyright: 2007, Starbreeze Studios
  2014-2019, Godot Engine contributors.
 License: Expat and Zlib
 
+Files: ./thirdparty/assimp/
+Comment: Open Asset Import Library (assimp)
+Copyright: 2006-2016, assimp team
+License: BSD-3-clause
+
 Files: ./thirdparty/b2d_convexdecomp/
 Comment: Box2D (ConvexDecomp)
 Copyright: 2007, Eric Jordan

+ 38 - 0
doc/classes/EditorSceneImporterAssimp.xml

@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<class name="EditorSceneImporterAssimp" inherits="EditorSceneImporter" category="Core" version="3.2">
+	<brief_description>
+		This is a multi-format 3d asset importer.
+	</brief_description>
+	<description>
+		This is a multi-format 3d asset importer.
+		Use these FBX export settings from Autodesk Maya.
+		[codeblock]
+		* Smoothing Groups
+		* Smooth Mesh
+		* Triangluate (For mesh with blendshapes)
+		* Bake Animation
+		* Resample All
+		* Deformed Models
+		* Skins
+		* Blend Shapes
+		* Curve Filters
+		* Constant Key Reducer
+		* Auto Tangents Only
+		* DO NOT CHECK Constraints (Will Break File)
+		* Can check Embed Media (Embeds textures into FBX file to import)
+		-- Note: When importing embed media, texture and mesh will be a un-alterable file.
+		--       Reimport of fbx with updated texture is need if texture is updated.
+		* Units: Centimeters
+		* Up Axis: Y
+		* Binary format in FBX 2017
+		[/codeblock]
+	</description>
+	<tutorials>
+	</tutorials>
+	<demos>
+	</demos>
+	<methods>
+	</methods>
+	<constants>
+	</constants>
+</class>

+ 1 - 1
drivers/gles3/rasterizer_storage_gles3.cpp

@@ -4131,7 +4131,7 @@ void RasterizerStorageGLES3::mesh_render_blend_shapes(Surface *s, const float *p
 	for (int ti = 0; ti < mtc; ti++) {
 		float weight = p_weights[ti];
 
-		if (weight < 0.001) //not bother with this one
+		if (weight < 0.00001) //not bother with this one
 			continue;
 
 		glBindVertexArray(s->blend_shapes[ti].array_id);

+ 95 - 0
modules/assimp/SCsub

@@ -0,0 +1,95 @@
+#!/usr/bin/env python
+
+Import('env')
+Import('env_modules')
+
+env_assimp = env_modules.Clone()
+env_assimp.Append(CPPPATH=['#thirdparty/assimp'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/include'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/code/Importer/IFC'])
+env_assimp.Append(CPPPATH=['#thirdparty/misc'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/code'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/contrib/irrXML/'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/contrib/unzip/'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/code/Importer/STEPParser'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/'])
+env_assimp.Append(CPPPATH=['#thirdparty/zlib/'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/contrib/openddlparser/include'])
+env_assimp.Append(CPPPATH=['#thirdparty/assimp/contrib/rapidjson/include'])
+env_assimp.Append(CPPPATH=['.'])
+#env_assimp.Append(CPPFLAGS=['-DASSIMP_DOUBLE_PRECISION']) # TODO default to what godot is compiled with for future double support
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_BOOST_WORKAROUND'])
+env_assimp.Append(CPPFLAGS=['-DOPENDDLPARSER_BUILD'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OWN_ZLIB'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_EXPORT'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_X_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_AMF_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3DS_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD3_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD5_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MDL_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD2_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_PLY_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_ASE_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OBJ_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_HMP_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_SMD_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MDC_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MD5_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_STL_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_LWO_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_DXF_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_NFF_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_RAW_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_SIB_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OFF_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_AC_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_BVH_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IRRMESH_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IRR_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_Q3D_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_B3D_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_COLLADA_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_TERRAGEN_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_CSM_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3D_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_LWS_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OGRE_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_OPENGEX_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_MS3D_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_COB_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_BLEND_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_Q3BSP_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_NDO_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_STEP_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_IFC_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_XGL_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_ASSBIN_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_GLTF_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_C4D_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_3MF_IMPORTER'])
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_NO_X3D_IMPORTER'])
+
+env_assimp.Append(CPPFLAGS=['-DASSIMP_BUILD_SINGLETHREADED'])
+
+if (not env.msvc):
+    env_assimp.Append(CXXFLAGS=['-std=c++11'])
+elif (env.msvc == False and env['platform'] == 'windows'):
+    env_assimp.Append(LDFLAGS=['-pthread'])
+
+if(env['platform'] == 'windows'):
+    env_assimp.Append(CPPFLAGS=['-DPLATFORM_WINDOWS'])
+    env_assimp.Append(CPPFLAGS=['-DPLATFORM=WINDOWS'])
+elif(env['platform'] == 'x11'):
+    env_assimp.Append(CPPFLAGS=['-DPLATFORM_LINUX'])
+    env_assimp.Append(CPPFLAGS=['-DPLATFORM=LINUX'])
+elif(env['platform'] == 'osx'):
+    env_assimp.Append(CPPFLAGS=['-DPLATFORM_DARWIN'])
+    env_assimp.Append(CPPFLAGS=['-DPLATFORM=DARWIN'])
+    
+env_thirdparty = env_assimp.Clone()
+env_thirdparty.disable_warnings()
+env_thirdparty.add_source_files(env.modules_sources, Glob('#thirdparty/assimp/code/*.cpp'))
+
+# Godot's own source files
+env_assimp.add_source_files(env.modules_sources, "*.cpp")

+ 5 - 0
modules/assimp/config.py

@@ -0,0 +1,5 @@
+def can_build(env, platform):
+    return env['tools']
+
+def configure(env):
+    pass

+ 2181 - 0
modules/assimp/editor_scene_importer_assimp.cpp

@@ -0,0 +1,2181 @@
+/*************************************************************************/
+/*  editor_scene_importer_assimp.cpp                                     */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 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 "assimp/DefaultLogger.hpp"
+#include "assimp/Importer.hpp"
+#include "assimp/LogStream.hpp"
+#include "assimp/Logger.hpp"
+#include "assimp/SceneCombiner.h"
+#include "assimp/cexport.h"
+#include "assimp/cimport.h"
+#include "assimp/matrix4x4.h"
+#include "assimp/pbrmaterial.h"
+#include "assimp/postprocess.h"
+#include "assimp/scene.h"
+
+#include "core/bind/core_bind.h"
+#include "core/io/image_loader.h"
+#include "editor/editor_file_system.h"
+#include "editor/import/resource_importer_scene.h"
+#include "editor_scene_importer_assimp.h"
+#include "editor_settings.h"
+#include "scene/3d/camera.h"
+#include "scene/3d/light.h"
+#include "scene/3d/mesh_instance.h"
+#include "scene/animation/animation_player.h"
+#include "scene/main/node.h"
+#include "scene/resources/material.h"
+#include "scene/resources/surface_tool.h"
+#include "zutil.h"
+#include <string>
+
+void EditorSceneImporterAssimp::get_extensions(List<String> *r_extensions) const {
+
+	const String import_setting_string = "filesystem/import/open_asset_import/";
+
+	Map<String, ImportFormat> import_format;
+	{
+		Vector<String> exts;
+		exts.push_back("fbx");
+		ImportFormat import = { exts, true };
+		import_format.insert("fbx", import);
+	}
+	{
+		Vector<String> exts;
+		exts.push_back("pmx");
+		ImportFormat import = { exts, true };
+		import_format.insert("mmd", import);
+	}
+	for (Map<String, ImportFormat>::Element *E = import_format.front(); E; E = E->next()) {
+		_register_project_setting_import(E->key(), import_setting_string, E->get().extensions, r_extensions, E->get().is_default);
+	}
+}
+
+void EditorSceneImporterAssimp::_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 EditorSceneImporterAssimp::get_import_flags() const {
+	return IMPORT_SCENE;
+}
+
+AssimpStream::AssimpStream() {
+	// empty
+}
+
+AssimpStream::~AssimpStream() {
+	// empty
+}
+
+void AssimpStream::write(const char *message) {
+	print_verbose(String("Open Asset Import: ") + String(message).strip_edges());
+}
+
+void EditorSceneImporterAssimp::_bind_methods() {
+}
+
+Node *EditorSceneImporterAssimp::import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err) {
+	Assimp::Importer importer;
+	std::wstring w_path = ProjectSettings::get_singleton()->globalize_path(p_path).c_str();
+	std::string s_path(w_path.begin(), w_path.end());
+	importer.SetPropertyBool(AI_CONFIG_PP_FD_REMOVE, true);
+	// Cannot remove pivot points because the static mesh will be in the wrong place
+	importer.SetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
+	int32_t max_bone_weights = 4;
+	//if (p_flags & IMPORT_ANIMATION_EIGHT_WEIGHTS) {
+	//	const int eight_bones = 8;
+	//	importer.SetPropertyBool(AI_CONFIG_PP_LBW_MAX_WEIGHTS, eight_bones);
+	//	max_bone_weights = eight_bones;
+	//}
+
+	importer.SetPropertyInteger(AI_CONFIG_PP_SBP_REMOVE, aiPrimitiveType_LINE | aiPrimitiveType_POINT);
+	//importer.SetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD, 1.0f);
+	int32_t post_process_Steps = aiProcess_CalcTangentSpace |
+								 //aiProcess_FlipUVs |
+								 //aiProcess_FlipWindingOrder |
+								 aiProcess_DropNormals |
+								 aiProcess_GenSmoothNormals |
+								 aiProcess_JoinIdenticalVertices |
+								 aiProcess_ImproveCacheLocality |
+								 aiProcess_LimitBoneWeights |
+								 //aiProcess_RemoveRedundantMaterials | // Causes a crash
+								 aiProcess_SplitLargeMeshes |
+								 aiProcess_Triangulate |
+								 aiProcess_GenUVCoords |
+								 //aiProcess_FindDegenerates |
+								 aiProcess_SortByPType |
+								 aiProcess_FindInvalidData |
+								 aiProcess_TransformUVCoords |
+								 aiProcess_FindInstances |
+								 //aiProcess_FixInfacingNormals |
+								 //aiProcess_ValidateDataStructure |
+								 aiProcess_OptimizeMeshes |
+								 //aiProcess_OptimizeGraph |
+								 //aiProcess_Debone |
+								 aiProcess_EmbedTextures |
+								 aiProcess_SplitByBoneCount |
+								 0;
+	const aiScene *scene = importer.ReadFile(s_path.c_str(),
+			post_process_Steps);
+	ERR_EXPLAIN(String("Open Asset Import failed to open: ") + String(importer.GetErrorString()));
+	ERR_FAIL_COND_V(scene == NULL, NULL);
+	return _generate_scene(p_path, scene, p_flags, p_bake_fps, max_bone_weights);
+}
+
+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) {
+
+		float t2 = t * t;
+		float t3 = t2 * t;
+
+		return 0.5f * ((2.0f * p1) + (-p0 + p2) * t + (2.0f * p0 - 5.0f * p1 + 4 * 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. */
+		real_t omt = (1.0 - t);
+		real_t omt2 = omt * omt;
+		real_t omt3 = omt2 * omt;
+		real_t t2 = t * t;
+		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 EditorSceneImporterAssimp::_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]);
+}
+
+Spatial *EditorSceneImporterAssimp::_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights) {
+	ERR_FAIL_COND_V(scene == NULL, NULL);
+	Spatial *root = memnew(Spatial);
+	AnimationPlayer *ap = NULL;
+	if (p_flags & IMPORT_ANIMATION) {
+		ap = memnew(AnimationPlayer);
+		root->add_child(ap);
+		ap->set_owner(root);
+		ap->set_name(TTR("AnimationPlayer"));
+	}
+	Set<String> bone_names;
+	Set<String> light_names;
+	Set<String> camera_names;
+	real_t factor = 1.0f;
+	String ext = p_path.get_file().get_extension().to_lower();
+	if ((ext == "fbx")) {
+		if (scene->mMetaData != NULL) {
+			scene->mMetaData->Get("UnitScaleFactor", factor);
+			factor = factor * 0.01f;
+		}
+	}
+	for (size_t l = 0; l < scene->mNumLights; l++) {
+		Light *light = NULL;
+		aiLight *ai_light = scene->mLights[l];
+		ERR_CONTINUE(ai_light == NULL);
+		if (ai_light->mType == aiLightSource_DIRECTIONAL) {
+			light = memnew(DirectionalLight);
+			Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
+			dir.normalize();
+			Transform xform;
+			Quat quat;
+			quat.set_euler(dir);
+			Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
+			pos = factor * pos;
+			xform.origin = pos;
+			light->set_transform(xform);
+		} else if (ai_light->mType == aiLightSource_POINT) {
+			light = memnew(OmniLight);
+			Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
+			Transform xform;
+			xform.origin = pos;
+			pos = factor * pos;
+			light->set_transform(xform);
+			// No idea for energy
+			light->set_param(Light::PARAM_ATTENUATION, 0.0f);
+		} else if (ai_light->mType == aiLightSource_SPOT) {
+			light = memnew(SpotLight);
+			Vector3 pos = Vector3(ai_light->mPosition.x, ai_light->mPosition.y, ai_light->mPosition.z);
+			pos = factor * pos;
+			Transform xform;
+			xform.origin = pos;
+			Vector3 dir = Vector3(ai_light->mDirection.y, ai_light->mDirection.x, ai_light->mDirection.z);
+			dir.normalize();
+			Quat quat;
+			quat.set_euler(dir);
+			xform.basis = quat;
+			light->set_transform(xform);
+			// No idea for energy
+			light->set_param(Light::PARAM_ATTENUATION, 0.0f);
+		}
+		ERR_CONTINUE(light == NULL);
+		light->set_color(Color(ai_light->mColorDiffuse.r, ai_light->mColorDiffuse.g, ai_light->mColorDiffuse.b));
+		root->add_child(light);
+		light->set_name(_ai_string_to_string(ai_light->mName));
+		light->set_owner(root);
+		light_names.insert(_ai_string_to_string(scene->mLights[l]->mName));
+	}
+	for (size_t c = 0; c < scene->mNumCameras; c++) {
+		aiCamera *ai_camera = scene->mCameras[c];
+		Camera *camera = memnew(Camera);
+		float near = ai_camera->mClipPlaneNear;
+		if (Math::is_equal_approx(near, 0.0f)) {
+			near = 0.1f;
+		}
+		camera->set_perspective(Math::rad2deg(ai_camera->mHorizontalFOV) * 2.0f, near, ai_camera->mClipPlaneFar);
+		Vector3 pos = Vector3(ai_camera->mPosition.x, ai_camera->mPosition.y, ai_camera->mPosition.z);
+
+		Vector3 look_at = Vector3(ai_camera->mLookAt.y, ai_camera->mLookAt.x, ai_camera->mLookAt.z).normalized();
+		Quat quat;
+		quat.set_euler(look_at);
+		Transform xform;
+		xform.basis = quat;
+		xform.set_origin(pos);
+		root->add_child(camera);
+		camera->set_transform(xform);
+		camera->set_name(_ai_string_to_string(ai_camera->mName));
+		camera->set_owner(root);
+		camera_names.insert(_ai_string_to_string(scene->mCameras[c]->mName));
+	}
+	Map<Skeleton *, MeshInstance *> skeletons;
+	Map<String, Transform> bone_rests;
+	Vector<MeshInstance *> meshes;
+	int32_t mesh_count = 0;
+	Skeleton *s = memnew(Skeleton);
+	Set<String> removed_bones;
+	Map<String, Map<uint32_t, String> > path_morph_mesh_names;
+	_generate_node(p_path, scene, scene->mRootNode, root, root, bone_names, light_names, camera_names, skeletons, bone_rests, meshes, mesh_count, s, p_max_bone_weights, removed_bones, path_morph_mesh_names);
+	for (Map<Skeleton *, MeshInstance *>::Element *E = skeletons.front(); E; E = E->next()) {
+		E->key()->localize_rests();
+	}
+	Set<String> removed_nodes;
+	Set<Node *> keep_nodes;
+	_keep_node(p_path, root, root, keep_nodes);
+	_fill_kept_node(keep_nodes);
+	_filter_node(p_path, root, root, keep_nodes, removed_nodes);
+	if (p_flags & IMPORT_ANIMATION) {
+		for (size_t i = 0; i < scene->mNumAnimations; i++) {
+			_import_animation(p_path, meshes, scene, ap, i, p_bake_fps, skeletons, removed_nodes, removed_bones, path_morph_mesh_names);
+		}
+		List<StringName> animation_names;
+		ap->get_animation_list(&animation_names);
+		if (animation_names.empty()) {
+			root->remove_child(ap);
+			memdelete(ap);
+		}
+	}
+	return root;
+}
+
+void EditorSceneImporterAssimp::_fill_kept_node(Set<Node *> &keep_nodes) {
+	for (Set<Node *>::Element *E = keep_nodes.front(); E; E = E->next()) {
+		Node *node = E->get();
+		while (node != NULL) {
+			if (keep_nodes.has(node) == false) {
+				keep_nodes.insert(node);
+			}
+			node = node->get_parent();
+		}
+	}
+}
+
+String EditorSceneImporterAssimp::_find_skeleton_bone_root(Map<Skeleton *, MeshInstance *> &skeletons, Map<MeshInstance *, String> &meshes, Spatial *root) {
+	for (Map<Skeleton *, MeshInstance *>::Element *E = skeletons.front(); E; E = E->next()) {
+		if (meshes.has(E->get())) {
+			String name = meshes[E->get()];
+			if (name != "") {
+				return name;
+			}
+		}
+	}
+	return "";
+}
+
+void EditorSceneImporterAssimp::_set_bone_parent(Skeleton *s, Node *p_owner, aiNode *p_node) {
+	for (int32_t j = 0; j < s->get_bone_count(); j++) {
+		String bone_name = s->get_bone_name(j);
+		const aiNode *ai_bone_node = _ai_find_node(p_node, bone_name);
+		if (ai_bone_node == NULL) {
+			continue;
+		}
+		ai_bone_node = ai_bone_node->mParent;
+		while (ai_bone_node != NULL) {
+			int32_t node_parent_index = -1;
+			String parent_bone_name = _ai_string_to_string(ai_bone_node->mName);
+			node_parent_index = s->find_bone(parent_bone_name);
+			if (node_parent_index != -1) {
+				s->set_bone_parent(j, node_parent_index);
+				break;
+			}
+			ai_bone_node = ai_bone_node->mParent;
+		}
+	}
+}
+
+void EditorSceneImporterAssimp::_insert_animation_track(const aiScene *p_scene, const String p_path, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, float length, const Skeleton *sk, const aiNodeAnim *track, String node_name, NodePath node_path) {
+
+	if (track->mNumRotationKeys || track->mNumPositionKeys || track->mNumScalingKeys) {
+		//make transform track
+		int track_idx = animation->get_track_count();
+		animation->add_track(Animation::TYPE_TRANSFORM);
+		animation->track_set_path(track_idx, node_path);
+		//first determine animation length
+
+		for (size_t i = 0; i < track->mNumRotationKeys; i++) {
+			length = MAX(length, track->mRotationKeys[i].mTime / ticks_per_second);
+		}
+		for (size_t i = 0; i < track->mNumPositionKeys; i++) {
+			length = MAX(length, track->mPositionKeys[i].mTime / ticks_per_second);
+		}
+		for (size_t i = 0; i < track->mNumScalingKeys; i++) {
+			length = MAX(length, track->mScalingKeys[i].mTime / ticks_per_second);
+		}
+
+		float increment = 1.0 / float(p_bake_fps);
+		float time = 0.0;
+
+		Vector3 base_pos;
+		Quat base_rot;
+		Vector3 base_scale = Vector3(1, 1, 1);
+
+		if (track->mNumRotationKeys != 0) {
+			aiQuatKey key = track->mRotationKeys[0];
+			real_t x = key.mValue.x;
+			real_t y = key.mValue.y;
+			real_t z = key.mValue.z;
+			real_t w = key.mValue.w;
+			Quat q(x, y, z, w);
+			q = q.normalized();
+			base_rot = q;
+		}
+
+		if (track->mNumPositionKeys != 0) {
+			aiVectorKey key = track->mPositionKeys[0];
+			real_t x = key.mValue.x;
+			real_t y = key.mValue.y;
+			real_t z = key.mValue.z;
+			base_pos = Vector3(x, y, z);
+		}
+
+		if (track->mNumScalingKeys != 0) {
+			aiVectorKey key = track->mScalingKeys[0];
+			real_t x = key.mValue.x;
+			real_t y = key.mValue.y;
+			real_t z = key.mValue.z;
+			base_scale = Vector3(x, y, z);
+		}
+
+		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;
+
+		for (size_t p = 0; p < track->mNumPositionKeys; p++) {
+			aiVector3D pos = track->mPositionKeys[p].mValue;
+			pos_values.push_back(Vector3(pos.x, pos.y, pos.z));
+			pos_times.push_back(track->mPositionKeys[p].mTime / ticks_per_second);
+		}
+
+		for (size_t r = 0; r < track->mNumRotationKeys; r++) {
+			aiQuaternion quat = track->mRotationKeys[r].mValue;
+			rot_values.push_back(Quat(quat.x, quat.y, quat.z, quat.w).normalized());
+			rot_times.push_back(track->mRotationKeys[r].mTime / ticks_per_second);
+		}
+
+		for (size_t sc = 0; sc < track->mNumScalingKeys; sc++) {
+			aiVector3D scale = track->mScalingKeys[sc].mValue;
+			scale_values.push_back(Vector3(scale.x, scale.y, scale.z));
+			scale_times.push_back(track->mScalingKeys[sc].mTime / ticks_per_second);
+		}
+		while (true) {
+			Vector3 pos = base_pos;
+			Quat rot = base_rot;
+			Vector3 scale = base_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).normalized();
+			}
+
+			if (scale_values.size()) {
+				scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR);
+			}
+
+			if (sk != NULL && sk->find_bone(node_name) != -1) {
+				Transform xform;
+				xform.basis.set_quat_scale(rot, scale);
+				xform.origin = pos;
+
+				int bone = sk->find_bone(node_name);
+				Transform rest_xform = sk->get_bone_rest(bone);
+				xform = rest_xform.affine_inverse() * xform;
+				rot = xform.basis.get_rotation_quat();
+				scale = xform.basis.get_scale();
+				pos = xform.origin;
+			}
+			{
+				Transform xform;
+				xform.basis.set_quat_scale(rot, scale);
+				xform.origin = pos;
+				Transform anim_xform;
+				String ext = p_path.get_file().get_extension().to_lower();
+				if (ext == "fbx") {
+					real_t factor = 1.0f;
+					if (p_scene->mMetaData != NULL) {
+						p_scene->mMetaData->Get("UnitScaleFactor", factor);
+					}
+					anim_xform = anim_xform.scaled(Vector3(factor, factor, factor));
+				}
+				xform = anim_xform * xform;
+				rot = xform.basis.get_rotation_quat();
+				scale = xform.basis.get_scale();
+				pos = xform.origin;
+			}
+			rot.normalize();
+
+			animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR);
+			animation->transform_track_insert_key(track_idx, time, pos, rot, scale);
+
+			if (last) {
+				break;
+			}
+			time += increment;
+			if (time >= length) {
+				last = true;
+				time = length;
+			}
+		}
+	}
+}
+
+void EditorSceneImporterAssimp::_import_animation(const String p_path, const Vector<MeshInstance *> p_meshes, const aiScene *p_scene, AnimationPlayer *ap, int32_t p_index, int p_bake_fps, Map<Skeleton *, MeshInstance *> p_skeletons, const Set<String> p_removed_nodes, const Set<String> removed_bones, const Map<String, Map<uint32_t, String> > p_path_morph_mesh_names) {
+	String name = "Animation";
+	aiAnimation const *anim = NULL;
+	if (p_index != -1) {
+		anim = p_scene->mAnimations[p_index];
+		if (anim->mName.length > 0) {
+			name = _ai_anim_string_to_string(anim->mName);
+		}
+	}
+
+	Ref<Animation> animation;
+	animation.instance();
+	float length = 0.0f;
+	animation->set_name(name);
+	float ticks_per_second = p_scene->mAnimations[p_index]->mTicksPerSecond;
+
+	if (p_scene->mMetaData != NULL && Math::is_equal_approx(ticks_per_second, 0.0f)) {
+		int32_t time_mode = 0;
+		p_scene->mMetaData->Get("TimeMode", time_mode);
+		ticks_per_second = _get_fbx_fps(time_mode, p_scene);
+	}
+
+	if ((p_path.get_file().get_extension().to_lower() == "glb" || p_path.get_file().get_extension().to_lower() == "gltf") && Math::is_equal_approx(ticks_per_second, 0.0f)) {
+		ticks_per_second = 1000.0f;
+	}
+
+	if (Math::is_equal_approx(ticks_per_second, 0.0f)) {
+		ticks_per_second = 25.0f;
+	}
+
+	length = anim->mDuration / ticks_per_second;
+	if (anim) {
+		Map<String, Vector<const aiNodeAnim *> > node_tracks;
+		for (size_t i = 0; i < anim->mNumChannels; i++) {
+			const aiNodeAnim *track = anim->mChannels[i];
+			String node_name = _ai_string_to_string(track->mNodeName);
+			NodePath node_path = node_name;
+			bool is_bone = false;
+			if (node_name.split(ASSIMP_FBX_KEY).size() > 1) {
+				String p_track_type = node_name.split(ASSIMP_FBX_KEY)[1];
+				if (p_track_type == "_Translation" || p_track_type == "_Rotation" || p_track_type == "_Scaling") {
+					continue;
+				}
+			}
+			for (Map<Skeleton *, MeshInstance *>::Element *E = p_skeletons.front(); E; E = E->next()) {
+				Skeleton *sk = E->key();
+				const String path = ap->get_owner()->get_path_to(sk);
+				if (path.empty()) {
+					continue;
+				}
+				if (sk->find_bone(node_name) == -1) {
+					continue;
+				}
+				node_path = path + ":" + node_name;
+				ERR_CONTINUE(ap->get_owner()->has_node(node_path) == false);
+				_insert_animation_track(p_scene, p_path, p_bake_fps, animation, ticks_per_second, length, sk, track, node_name, node_path);
+				is_bone = true;
+			}
+			if (is_bone) {
+				continue;
+			}
+			Node *node = ap->get_owner()->find_node(node_name);
+			if (node == NULL) {
+				continue;
+			}
+			if (p_removed_nodes.has(node_name)) {
+				continue;
+			}
+			const String path = ap->get_owner()->get_path_to(node);
+			if (path.empty()) {
+				print_verbose("Can't animate path");
+				continue;
+			}
+			node_path = path;
+			if (ap->get_owner()->has_node(node_path) == false) {
+				continue;
+			}
+			_insert_animation_track(p_scene, p_path, p_bake_fps, animation, ticks_per_second, length, NULL, track, node_name, node_path);
+		}
+		for (size_t i = 0; i < anim->mNumChannels; i++) {
+			const aiNodeAnim *track = anim->mChannels[i];
+			String node_name = _ai_string_to_string(track->mNodeName);
+			Vector<String> split_name = node_name.split(ASSIMP_FBX_KEY);
+			String bare_name = split_name[0];
+			Node *node = ap->get_owner()->find_node(bare_name);
+			if (node != NULL && split_name.size() > 1) {
+				Map<String, Vector<const aiNodeAnim *> >::Element *E = node_tracks.find(bare_name);
+				Vector<const aiNodeAnim *> ai_tracks;
+				if (E) {
+					ai_tracks = E->get();
+					ai_tracks.push_back(anim->mChannels[i]);
+				} else {
+					ai_tracks.push_back(anim->mChannels[i]);
+				}
+				node_tracks.insert(bare_name, ai_tracks);
+			}
+		}
+		for (Map<Skeleton *, MeshInstance *>::Element *E = p_skeletons.front(); E; E = E->next()) {
+			Skeleton *sk = E->key();
+			Map<String, Vector<const aiNodeAnim *> > anim_tracks;
+			for (int32_t i = 0; i < sk->get_bone_count(); i++) {
+				String _bone_name = sk->get_bone_name(i);
+				Vector<const aiNodeAnim *> ai_tracks;
+
+				if (sk->find_bone(_bone_name) == -1) {
+					continue;
+				}
+				for (size_t j = 0; j < anim->mNumChannels; j++) {
+					if (_ai_string_to_string(anim->mChannels[j]->mNodeName).split(ASSIMP_FBX_KEY).size() == 1) {
+						continue;
+					}
+					String track_name = _ai_string_to_string(anim->mChannels[j]->mNodeName).split(ASSIMP_FBX_KEY)[0];
+					if (track_name != _bone_name) {
+						continue;
+					}
+					if (sk->find_bone(_bone_name) == -1) {
+						continue;
+					}
+					ai_tracks.push_back(anim->mChannels[j]);
+				}
+				if (ai_tracks.size() == 0) {
+					continue;
+				}
+				anim_tracks.insert(_bone_name, ai_tracks);
+			}
+			for (Map<String, Vector<const aiNodeAnim *> >::Element *F = anim_tracks.front(); F; F = F->next()) {
+				_insert_pivot_anim_track(p_meshes, F->key(), F->get(), ap, sk, length, ticks_per_second, animation, p_bake_fps, p_path, p_scene);
+			}
+		}
+		for (Map<String, Vector<const aiNodeAnim *> >::Element *E = node_tracks.front(); E; E = E->next()) {
+			if (p_removed_nodes.has(E->key())) {
+				continue;
+			}
+			if (removed_bones.find(E->key())) {
+				continue;
+			}
+			_insert_pivot_anim_track(p_meshes, E->key(), E->get(), ap, NULL, length, ticks_per_second, animation, p_bake_fps, p_path, p_scene);
+		}
+		for (size_t i = 0; i < anim->mNumMorphMeshChannels; i++) {
+			const aiMeshMorphAnim *anim_mesh = anim->mMorphMeshChannels[i];
+			const String prop_name = _ai_string_to_string(anim_mesh->mName);
+			const String mesh_name = prop_name.split("*")[0];
+			if (p_removed_nodes.has(mesh_name)) {
+				continue;
+			}
+			ERR_CONTINUE(prop_name.split("*").size() != 2);
+			const MeshInstance *mesh_instance = Object::cast_to<MeshInstance>(ap->get_owner()->find_node(mesh_name));
+			ERR_CONTINUE(mesh_instance == NULL);
+			if (ap->get_owner()->find_node(mesh_instance->get_name()) == NULL) {
+				print_verbose("Can't find mesh in scene: " + mesh_instance->get_name());
+				continue;
+			}
+			const String path = ap->get_owner()->get_path_to(mesh_instance);
+			if (path.empty()) {
+				print_verbose("Can't find mesh in scene");
+				continue;
+			}
+			Ref<Mesh> mesh = mesh_instance->get_mesh();
+			ERR_CONTINUE(mesh.is_null());
+			const Map<String, Map<uint32_t, String> >::Element *E = p_path_morph_mesh_names.find(mesh_name);
+			ERR_CONTINUE(E == NULL);
+			for (size_t k = 0; k < anim_mesh->mNumKeys; k++) {
+				for (size_t j = 0; j < anim_mesh->mKeys[k].mNumValuesAndWeights; j++) {
+					const Map<uint32_t, String>::Element *F = E->get().find(anim_mesh->mKeys[k].mValues[j]);
+					ERR_CONTINUE(F == NULL);
+					const String prop = "blend_shapes/" + F->get();
+					const NodePath node_path = String(path) + ":" + prop;
+					ERR_CONTINUE(ap->get_owner()->has_node(node_path) == false);
+					int32_t blend_track_idx = -1;
+					if (animation->find_track(node_path) == -1) {
+						blend_track_idx = animation->get_track_count();
+						animation->add_track(Animation::TYPE_VALUE);
+						animation->track_set_interpolation_type(blend_track_idx, Animation::INTERPOLATION_LINEAR);
+						animation->track_set_path(blend_track_idx, node_path);
+					} else {
+						blend_track_idx = animation->find_track(node_path);
+					}
+					float t = anim_mesh->mKeys[k].mTime / ticks_per_second;
+					float w = anim_mesh->mKeys[k].mWeights[j];
+					animation->track_insert_key(blend_track_idx, t, w);
+				}
+			}
+		}
+	}
+	animation->set_length(length);
+	if (animation->get_track_count()) {
+		ap->add_animation(name, animation);
+	}
+}
+
+void EditorSceneImporterAssimp::_insert_pivot_anim_track(const Vector<MeshInstance *> p_meshes, const String p_node_name, Vector<const aiNodeAnim *> F, AnimationPlayer *ap, Skeleton *sk, float &length, float ticks_per_second, Ref<Animation> animation, int p_bake_fps, const String &p_path, const aiScene *p_scene) {
+	NodePath node_path;
+	if (sk != NULL) {
+		const String path = ap->get_owner()->get_path_to(sk);
+		if (path.empty()) {
+			return;
+		}
+		if (sk->find_bone(p_node_name) == -1) {
+			return;
+		}
+		node_path = path + ":" + p_node_name;
+	} else {
+		Node *node = ap->get_owner()->find_node(p_node_name);
+		if (node == NULL) {
+			return;
+		}
+		const String path = ap->get_owner()->get_path_to(node);
+		node_path = path;
+	}
+	if (node_path.is_empty()) {
+		return;
+	}
+
+	Vector<Vector3> pos_values;
+	Vector<float> pos_times;
+	Vector<Vector3> scale_values;
+	Vector<float> scale_times;
+	Vector<Quat> rot_values;
+	Vector<float> rot_times;
+	Vector3 base_pos;
+	Quat base_rot;
+	Vector3 base_scale = Vector3(1, 1, 1);
+	bool is_translation = false;
+	bool is_rotation = false;
+	bool is_scaling = false;
+	for (int32_t k = 0; k < F.size(); k++) {
+		String p_track_type = _ai_string_to_string(F[k]->mNodeName).split(ASSIMP_FBX_KEY)[1];
+		if (p_track_type == "_Translation") {
+			is_translation = is_translation || true;
+		} else if (p_track_type == "_Rotation") {
+			is_rotation = is_rotation || true;
+		} else if (p_track_type == "_Scaling") {
+			is_scaling = is_scaling || true;
+		} else {
+			continue;
+		}
+		ERR_CONTINUE(ap->get_owner()->has_node(node_path) == false);
+
+		if (F[k]->mNumRotationKeys || F[k]->mNumPositionKeys || F[k]->mNumScalingKeys) {
+
+			if (is_rotation) {
+				for (size_t i = 0; i < F[k]->mNumRotationKeys; i++) {
+					length = MAX(length, F[k]->mRotationKeys[i].mTime / ticks_per_second);
+				}
+			}
+			if (is_translation) {
+				for (size_t i = 0; i < F[k]->mNumPositionKeys; i++) {
+					length = MAX(length, F[k]->mPositionKeys[i].mTime / ticks_per_second);
+				}
+			}
+			if (is_scaling) {
+				for (size_t i = 0; i < F[k]->mNumScalingKeys; i++) {
+					length = MAX(length, F[k]->mScalingKeys[i].mTime / ticks_per_second);
+				}
+			}
+
+			if (is_rotation == false && is_translation == false && is_scaling == false) {
+				return;
+			}
+
+			if (is_rotation) {
+				if (F[k]->mNumRotationKeys != 0) {
+					aiQuatKey key = F[k]->mRotationKeys[0];
+					real_t x = key.mValue.x;
+					real_t y = key.mValue.y;
+					real_t z = key.mValue.z;
+					real_t w = key.mValue.w;
+					Quat q(x, y, z, w);
+					q = q.normalized();
+					base_rot = q;
+				}
+			}
+
+			if (is_translation) {
+				if (F[k]->mNumPositionKeys != 0) {
+					aiVectorKey key = F[k]->mPositionKeys[0];
+					real_t x = key.mValue.x;
+					real_t y = key.mValue.y;
+					real_t z = key.mValue.z;
+					base_pos = Vector3(x, y, z);
+				}
+			}
+
+			if (is_scaling) {
+				if (F[k]->mNumScalingKeys != 0) {
+					aiVectorKey key = F[k]->mScalingKeys[0];
+					real_t x = key.mValue.x;
+					real_t y = key.mValue.y;
+					real_t z = key.mValue.z;
+					base_scale = Vector3(x, y, z);
+				}
+			}
+			if (is_translation) {
+				for (size_t p = 0; p < F[k]->mNumPositionKeys; p++) {
+					aiVector3D pos = F[k]->mPositionKeys[p].mValue;
+					pos_values.push_back(Vector3(pos.x, pos.y, pos.z));
+					pos_times.push_back(F[k]->mPositionKeys[p].mTime / ticks_per_second);
+				}
+			}
+
+			if (is_rotation) {
+				for (size_t r = 0; r < F[k]->mNumRotationKeys; r++) {
+					aiQuaternion quat = F[k]->mRotationKeys[r].mValue;
+					rot_values.push_back(Quat(quat.x, quat.y, quat.z, quat.w).normalized());
+					rot_times.push_back(F[k]->mRotationKeys[r].mTime / ticks_per_second);
+				}
+			}
+
+			if (is_scaling) {
+				for (size_t sc = 0; sc < F[k]->mNumScalingKeys; sc++) {
+					aiVector3D scale = F[k]->mScalingKeys[sc].mValue;
+					scale_values.push_back(Vector3(scale.x, scale.y, scale.z));
+					scale_times.push_back(F[k]->mScalingKeys[sc].mTime / ticks_per_second);
+				}
+			}
+		}
+	}
+	int32_t track_idx = animation->get_track_count();
+	animation->add_track(Animation::TYPE_TRANSFORM);
+	animation->track_set_path(track_idx, node_path);
+	float increment = 1.0 / float(p_bake_fps);
+	float time = 0.0;
+	bool last = false;
+	while (true) {
+		Vector3 pos = Vector3();
+		Quat rot = Quat();
+		Vector3 scale = Vector3(1.0f, 1.0f, 1.0f);
+		if (is_translation && pos_values.size()) {
+			pos = _interpolate_track<Vector3>(pos_times, pos_values, time, AssetImportAnimation::INTERP_LINEAR);
+			Transform anim_xform;
+			String ext = p_path.get_file().get_extension().to_lower();
+			if (ext == "fbx") {
+				aiNode *ai_node = _ai_find_node(p_scene->mRootNode, p_node_name);
+				Transform mesh_xform = _get_global_ai_node_transform(p_scene, ai_node);
+				pos = mesh_xform.origin + pos;
+				real_t factor = 1.0f;
+				if (p_scene->mMetaData != NULL) {
+					p_scene->mMetaData->Get("UnitScaleFactor", factor);
+					factor = factor * 0.01f;
+				}
+				pos = pos * factor;
+			}
+		}
+		if (is_rotation && rot_values.size()) {
+			rot = _interpolate_track<Quat>(rot_times, rot_values, time, AssetImportAnimation::INTERP_LINEAR).normalized();
+		}
+		if (is_scaling && scale_values.size()) {
+			scale = _interpolate_track<Vector3>(scale_times, scale_values, time, AssetImportAnimation::INTERP_LINEAR);
+		}
+		animation->track_set_interpolation_type(track_idx, Animation::INTERPOLATION_LINEAR);
+		animation->transform_track_insert_key(track_idx, time, pos, rot, scale);
+
+		if (last) {
+			break;
+		}
+		time += increment;
+		if (time >= length) {
+			last = true;
+			time = length;
+		}
+	}
+}
+
+float EditorSceneImporterAssimp::_get_fbx_fps(int32_t time_mode, const aiScene *p_scene) {
+	switch (time_mode) {
+		case AssetImportFbx::TIME_MODE_DEFAULT: return 24; //hack
+		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:
+			int32_t frame_rate;
+			p_scene->mMetaData->Get("FrameRate", frame_rate);
+			return frame_rate;
+	}
+	return 0;
+}
+
+Transform EditorSceneImporterAssimp::_get_global_ai_node_transform(const aiScene *p_scene, const aiNode *p_current_node) {
+	aiNode const *current_node = p_current_node;
+	Transform xform;
+	while (current_node != NULL) {
+		xform = _ai_matrix_transform(current_node->mTransformation) * xform;
+		current_node = current_node->mParent;
+	}
+	return xform;
+}
+
+void EditorSceneImporterAssimp::_generate_node_bone(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const String p_path, const int32_t p_max_bone_weights) {
+	for (size_t i = 0; i < p_node->mNumMeshes; i++) {
+		const unsigned int mesh_idx = p_node->mMeshes[i];
+		const aiMesh *ai_mesh = p_scene->mMeshes[mesh_idx];
+		for (size_t j = 0; j < ai_mesh->mNumBones; j++) {
+			String bone_name = _ai_string_to_string(ai_mesh->mBones[j]->mName);
+			if (p_skeleton->find_bone(bone_name) != -1) {
+				continue;
+			}
+			p_mesh_bones.insert(bone_name, true);
+			p_skeleton->add_bone(bone_name);
+			int32_t idx = p_skeleton->find_bone(bone_name);
+			Transform xform = _ai_matrix_transform(ai_mesh->mBones[j]->mOffsetMatrix);
+			String ext = p_path.get_file().get_extension().to_lower();
+			if (ext == "fbx") {
+				Transform mesh_xform = _get_global_ai_node_transform(p_scene, p_node);
+				mesh_xform.basis = Basis();
+				xform = mesh_xform.affine_inverse() * xform;
+			}
+			p_skeleton->set_bone_rest(idx, xform.affine_inverse());
+		}
+	}
+}
+
+void EditorSceneImporterAssimp::_generate_node_bone_parents(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const MeshInstance *p_mi) {
+	for (size_t i = 0; i < p_node->mNumMeshes; i++) {
+		const unsigned int mesh_idx = p_node->mMeshes[i];
+		const aiMesh *ai_mesh = p_scene->mMeshes[mesh_idx];
+
+		for (size_t j = 0; j < ai_mesh->mNumBones; j++) {
+			aiNode *bone_node = p_scene->mRootNode->FindNode(ai_mesh->mBones[j]->mName);
+			ERR_CONTINUE(bone_node == NULL);
+			aiNode *bone_node_parent = bone_node->mParent;
+			while (bone_node_parent != NULL) {
+				String bone_parent_name = _ai_string_to_string(bone_node_parent->mName);
+				bone_parent_name = bone_parent_name.split(ASSIMP_FBX_KEY)[0];
+				if (bone_parent_name == p_mi->get_name()) {
+					break;
+				}
+				if (p_mi->get_parent() == NULL) {
+					break;
+				}
+				if (bone_parent_name == p_mi->get_parent()->get_name()) {
+					break;
+				}
+				if (bone_node_parent->mParent == p_scene->mRootNode) {
+					break;
+				}
+				if (p_skeleton->find_bone(bone_parent_name) == -1) {
+					p_mesh_bones.insert(bone_parent_name, true);
+				}
+				bone_node_parent = bone_node_parent->mParent;
+			}
+		}
+	}
+}
+void EditorSceneImporterAssimp::_calculate_skeleton_root(Skeleton *s, const aiScene *p_scene, aiNode *&p_ai_skeleton_root, Map<String, bool> &mesh_bones, const aiNode *p_node) {
+	if (s->get_bone_count() > 0) {
+		String bone_name = s->get_bone_name(0);
+		p_ai_skeleton_root = _ai_find_node(p_scene->mRootNode, bone_name);
+		for (size_t i = 0; i < p_scene->mRootNode->mNumChildren; i++) {
+			if (p_ai_skeleton_root == NULL) {
+				break;
+			}
+			aiNode *found = p_scene->mRootNode->mChildren[i]->FindNode(p_ai_skeleton_root->mName);
+			if (found) {
+				p_ai_skeleton_root = p_scene->mRootNode->mChildren[i];
+				break;
+			}
+		}
+	}
+
+	if (p_ai_skeleton_root == NULL) {
+		p_ai_skeleton_root = p_scene->mRootNode->FindNode(p_node->mName);
+		while (p_ai_skeleton_root && p_ai_skeleton_root->mParent && p_ai_skeleton_root->mParent != p_scene->mRootNode) {
+			p_ai_skeleton_root = p_scene->mRootNode->FindNode(p_ai_skeleton_root->mName)->mParent;
+		}
+	}
+	p_ai_skeleton_root = _ai_find_node(p_scene->mRootNode, _ai_string_to_string(p_ai_skeleton_root->mName).split(ASSIMP_FBX_KEY)[0]);
+}
+
+void EditorSceneImporterAssimp::_fill_skeleton(const aiScene *p_scene, const aiNode *p_node, Spatial *p_current, Node *p_owner, Skeleton *p_skeleton, const Map<String, bool> p_mesh_bones, const Map<String, Transform> &p_bone_rests, Set<String> p_tracks, const String p_path, Set<String> &r_removed_bones) {
+	String node_name = _ai_string_to_string(p_node->mName);
+	if (p_mesh_bones.find(node_name) != NULL && p_skeleton->find_bone(node_name) == -1) {
+		r_removed_bones.insert(node_name);
+		p_skeleton->add_bone(node_name);
+		int32_t idx = p_skeleton->find_bone(node_name);
+		Transform xform = _get_global_ai_node_transform(p_scene, p_node);
+		xform = _format_rot_xform(p_path, p_scene) * xform;
+		p_skeleton->set_bone_rest(idx, xform);
+	}
+
+	for (size_t i = 0; i < p_node->mNumChildren; i++) {
+		_fill_skeleton(p_scene, p_node->mChildren[i], p_current, p_owner, p_skeleton, p_mesh_bones, p_bone_rests, p_tracks, p_path, r_removed_bones);
+	}
+}
+
+void EditorSceneImporterAssimp::_keep_node(const String &p_path, Node *p_current, Node *p_owner, Set<Node *> &r_keep_nodes) {
+	if (p_current == p_owner) {
+		r_keep_nodes.insert(p_current);
+	}
+
+	if (p_current->get_class() != Spatial().get_class()) {
+		r_keep_nodes.insert(p_current);
+	}
+
+	for (int i = 0; i < p_current->get_child_count(); i++) {
+		_keep_node(p_path, p_current->get_child(i), p_owner, r_keep_nodes);
+	}
+}
+
+void EditorSceneImporterAssimp::_filter_node(const String &p_path, Node *p_current, Node *p_owner, const Set<Node *> p_keep_nodes, Set<String> &r_removed_nodes) {
+	if (p_keep_nodes.has(p_current) == false) {
+		r_removed_nodes.insert(p_current->get_name());
+		p_current->queue_delete();
+	}
+	for (int i = 0; i < p_current->get_child_count(); i++) {
+		_filter_node(p_path, p_current->get_child(i), p_owner, p_keep_nodes, r_removed_nodes);
+	}
+}
+
+void EditorSceneImporterAssimp::_generate_node(const String &p_path, const aiScene *p_scene, const aiNode *p_node, Node *p_parent, Node *p_owner, Set<String> &r_bone_name, Set<String> p_light_names, Set<String> p_camera_names, Map<Skeleton *, MeshInstance *> &r_skeletons, const Map<String, Transform> &p_bone_rests, Vector<MeshInstance *> &r_mesh_instances, int32_t &r_mesh_count, Skeleton *p_skeleton, const int32_t p_max_bone_weights, Set<String> &r_removed_bones, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names) {
+	Spatial *child_node = NULL;
+	if (p_node == NULL) {
+		return;
+	}
+	String node_name = _ai_string_to_string(p_node->mName);
+	real_t factor = 1.0f;
+	String ext = p_path.get_file().get_extension().to_lower();
+	if (ext == "fbx") {
+		if (p_scene->mMetaData != NULL) {
+			p_scene->mMetaData->Get("UnitScaleFactor", factor);
+			factor = factor * 0.01f;
+		}
+	}
+	{
+		Transform xform = _ai_matrix_transform(p_node->mTransformation);
+
+		child_node = memnew(Spatial);
+		p_parent->add_child(child_node);
+		child_node->set_owner(p_owner);
+		if (p_node == p_scene->mRootNode) {
+			if ((ext == "fbx") && p_node == p_scene->mRootNode) {
+				xform = xform.scaled(Vector3(factor, factor, factor));
+				Transform format_xform = _format_rot_xform(p_path, p_scene);
+				xform = format_xform * xform;
+			}
+		}
+		child_node->set_transform(xform * child_node->get_transform());
+	}
+
+	if (p_node->mNumMeshes > 0) {
+		MeshInstance *mesh_node = memnew(MeshInstance);
+		p_parent->add_child(mesh_node);
+		mesh_node->set_owner(p_owner);
+		mesh_node->set_transform(child_node->get_transform());
+		{
+			Map<String, bool> mesh_bones;
+			p_skeleton->set_use_bones_in_world_transform(true);
+			_generate_node_bone(p_scene, p_node, mesh_bones, p_skeleton, p_path, p_max_bone_weights);
+			Set<String> tracks;
+			_get_track_set(p_scene, tracks);
+			aiNode *skeleton_root = NULL;
+			_calculate_skeleton_root(p_skeleton, p_scene, skeleton_root, mesh_bones, p_node);
+			_generate_node_bone_parents(p_scene, p_node, mesh_bones, p_skeleton, mesh_node);
+			if (p_skeleton->get_bone_count() > 0) {
+				_fill_skeleton(p_scene, skeleton_root, mesh_node, p_owner, p_skeleton, mesh_bones, p_bone_rests, tracks, p_path, r_removed_bones);
+				_set_bone_parent(p_skeleton, p_owner, p_scene->mRootNode);
+			}
+			MeshInstance *mi = Object::cast_to<MeshInstance>(mesh_node);
+			if (mi) {
+				r_mesh_instances.push_back(mi);
+			}
+			_add_mesh_to_mesh_instance(p_node, p_scene, p_skeleton, p_path, mesh_node, p_owner, r_bone_name, r_mesh_count, p_max_bone_weights, r_name_morph_mesh_names);
+		}
+		if (mesh_node != NULL && p_skeleton->get_bone_count() > 0 && p_owner->find_node(p_skeleton->get_name()) == NULL) {
+			Node *node = p_owner->find_node(_ai_string_to_string(p_scene->mRootNode->mName));
+			ERR_FAIL_COND(node == NULL);
+			node->add_child(p_skeleton);
+			p_skeleton->set_owner(p_owner);
+			if (ext == "fbx") {
+				Transform mesh_xform = _get_global_ai_node_transform(p_scene, p_node);
+				mesh_xform.origin = Vector3();
+				p_skeleton->set_transform(mesh_xform);
+			}
+			r_skeletons.insert(p_skeleton, mesh_node);
+		}
+		for (size_t i = 0; i < p_node->mNumMeshes; i++) {
+			if (p_scene->mMeshes[p_node->mMeshes[i]]->HasBones()) {
+				mesh_node->set_name(node_name);
+				// Meshes without skeletons must not have skeletons
+				mesh_node->set_skeleton_path(String(mesh_node->get_path_to(p_owner)) + "/" + p_owner->get_path_to(p_skeleton));
+			}
+		}
+		child_node->get_parent()->remove_child(child_node);
+		memdelete(child_node);
+		child_node = mesh_node;
+	} else if (p_light_names.has(node_name)) {
+		Spatial *light_node = Object::cast_to<Light>(p_owner->find_node(node_name));
+		ERR_FAIL_COND(light_node == NULL);
+		if (!p_parent->has_node(light_node->get_path())) {
+			p_parent->add_child(light_node);
+		}
+		light_node->set_owner(p_owner);
+		light_node->set_transform(child_node->get_transform().scaled(Vector3(factor, factor, factor)) *
+								  light_node->get_transform().scaled(Vector3(factor, factor, factor)));
+		child_node->get_parent()->remove_child(child_node);
+		memdelete(child_node);
+		child_node = light_node;
+	} else if (p_camera_names.has(node_name)) {
+		Spatial *camera_node = Object::cast_to<Camera>(p_owner->find_node(node_name));
+		ERR_FAIL_COND(camera_node == NULL);
+		if (!p_parent->has_node(camera_node->get_path())) {
+			p_parent->add_child(camera_node);
+		}
+		camera_node->set_owner(p_owner);
+		camera_node->set_transform(child_node->get_transform().scaled(Vector3(factor, factor, factor)) *
+								   camera_node->get_transform().scaled(Vector3(factor, factor, factor)));
+		camera_node->scale(Vector3(factor, factor, factor));
+		child_node->get_parent()->remove_child(child_node);
+		memdelete(child_node);
+		child_node = camera_node;
+	}
+	child_node->set_name(node_name);
+	for (size_t i = 0; i < p_node->mNumChildren; i++) {
+		_generate_node(p_path, p_scene, p_node->mChildren[i], child_node, p_owner, r_bone_name, p_light_names, p_camera_names, r_skeletons, p_bone_rests, r_mesh_instances, r_mesh_count, p_skeleton, p_max_bone_weights, r_removed_bones, r_name_morph_mesh_names);
+	}
+}
+
+aiNode *EditorSceneImporterAssimp::_ai_find_node(aiNode *ai_child_node, const String bone_name) {
+
+	if (_ai_string_to_string(ai_child_node->mName) == bone_name) {
+		return ai_child_node;
+	}
+	aiNode *target = NULL;
+	for (size_t i = 0; i < ai_child_node->mNumChildren; i++) {
+
+		target = _ai_find_node(ai_child_node->mChildren[i], bone_name);
+		if (target != NULL) {
+			return target;
+		}
+	}
+	return target;
+}
+
+Transform EditorSceneImporterAssimp::_format_rot_xform(const String p_path, const aiScene *p_scene) {
+	String ext = p_path.get_file().get_extension().to_lower();
+
+	Transform xform;
+	int32_t up_axis = 0;
+	Vector3 up_axis_vec3 = Vector3();
+
+	int32_t front_axis = 0;
+	Vector3 front_axis_vec3 = Vector3();
+
+	if (p_scene->mMetaData != NULL) {
+		p_scene->mMetaData->Get("UpAxis", up_axis);
+		if (up_axis == AssetImportFbx::UP_VECTOR_AXIS_X) {
+			if (p_scene->mMetaData != NULL) {
+				p_scene->mMetaData->Get("FrontAxis", front_axis);
+				if (front_axis == AssetImportFbx::FRONT_PARITY_EVEN) {
+					// y
+				} else if (front_axis == AssetImportFbx::FRONT_PARITY_ODD) {
+					// z
+					//front_axis_vec3 = Vector3(0.0f, Math::deg2rad(-180.f), 0.0f);
+				}
+			}
+		} else if (up_axis == AssetImportFbx::UP_VECTOR_AXIS_Y) {
+			up_axis_vec3 = Vector3(Math::deg2rad(-90.f), 0.0f, 0.0f);
+			if (p_scene->mMetaData != NULL) {
+				p_scene->mMetaData->Get("FrontAxis", front_axis);
+				if (front_axis == AssetImportFbx::FRONT_PARITY_EVEN) {
+					// x
+				} else if (front_axis == AssetImportFbx::FRONT_PARITY_ODD) {
+					// z
+				}
+			}
+		} else if (up_axis == AssetImportFbx::UP_VECTOR_AXIS_Z) {
+			up_axis_vec3 = Vector3(0.0f, Math ::deg2rad(90.f), 0.0f);
+			if (p_scene->mMetaData != NULL) {
+				p_scene->mMetaData->Get("FrontAxis", front_axis);
+				if (front_axis == AssetImportFbx::FRONT_PARITY_EVEN) {
+					// x
+				} else if (front_axis == AssetImportFbx::FRONT_PARITY_ODD) {
+					// y
+				}
+			}
+		}
+	}
+
+	int32_t up_axis_sign = 0;
+	if (p_scene->mMetaData != NULL) {
+		p_scene->mMetaData->Get("UpAxisSign", up_axis_sign);
+		up_axis_vec3 = up_axis_vec3 * up_axis_sign;
+	}
+
+	int32_t front_axis_sign = 0;
+	if (p_scene->mMetaData != NULL) {
+		p_scene->mMetaData->Get("FrontAxisSign", front_axis_sign);
+		front_axis_vec3 = front_axis_vec3 * front_axis_sign;
+	}
+
+	int32_t coord_axis = 0;
+	Vector3 coord_axis_vec3 = Vector3();
+	if (p_scene->mMetaData != NULL) {
+		p_scene->mMetaData->Get("CoordAxis", coord_axis);
+		if (coord_axis == AssetImportFbx::COORD_LEFT) {
+		} else if (coord_axis == AssetImportFbx::COORD_RIGHT) {
+		}
+	}
+
+	int32_t coord_axis_sign = 0;
+	if (p_scene->mMetaData != NULL) {
+		p_scene->mMetaData->Get("CoordAxisSign", coord_axis_sign);
+	}
+
+	Quat up_quat;
+	up_quat.set_euler(up_axis_vec3);
+
+	Quat coord_quat;
+	coord_quat.set_euler(coord_axis_vec3);
+
+	Quat front_quat;
+	front_quat.set_euler(front_axis_vec3);
+
+	xform.basis.set_quat(up_quat * coord_quat * front_quat);
+	return xform;
+}
+
+void EditorSceneImporterAssimp::_get_track_set(const aiScene *p_scene, Set<String> &tracks) {
+	for (size_t i = 0; i < p_scene->mNumAnimations; i++) {
+		for (size_t j = 0; j < p_scene->mAnimations[i]->mNumChannels; j++) {
+			aiString ai_name = p_scene->mAnimations[i]->mChannels[j]->mNodeName;
+			String name = _ai_string_to_string(ai_name);
+			tracks.insert(name);
+		}
+	}
+}
+
+void EditorSceneImporterAssimp::_add_mesh_to_mesh_instance(const aiNode *p_node, const aiScene *p_scene, Skeleton *s, const String &p_path, MeshInstance *p_mesh_instance, Node *p_owner, Set<String> &r_bone_name, int32_t &r_mesh_count, int32_t p_max_bone_weights, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names) {
+	Ref<ArrayMesh> mesh;
+	mesh.instance();
+	bool has_uvs = false;
+	for (size_t i = 0; i < p_node->mNumMeshes; i++) {
+		const unsigned int mesh_idx = p_node->mMeshes[i];
+		const aiMesh *ai_mesh = p_scene->mMeshes[mesh_idx];
+
+		Map<uint32_t, Vector<float> > vertex_weight;
+		Map<uint32_t, Vector<String> > vertex_bone_name;
+		for (size_t b = 0; b < ai_mesh->mNumBones; b++) {
+			aiBone *bone = ai_mesh->mBones[b];
+			for (size_t w = 0; w < bone->mNumWeights; w++) {
+				String name = _ai_string_to_string(bone->mName);
+				aiVertexWeight ai_weights = bone->mWeights[w];
+				uint32_t vertexId = ai_weights.mVertexId;
+				Map<uint32_t, Vector<float> >::Element *result = vertex_weight.find(vertexId);
+				Vector<float> weights;
+				if (result != NULL) {
+					weights.append_array(result->value());
+				}
+				weights.push_back(ai_weights.mWeight);
+				if (vertex_weight.has(vertexId)) {
+					vertex_weight[vertexId] = weights;
+				} else {
+					vertex_weight.insert(vertexId, weights);
+				}
+				Map<uint32_t, Vector<String> >::Element *bone_result = vertex_bone_name.find(vertexId);
+				Vector<String> bone_names;
+				if (bone_result != NULL) {
+					bone_names.append_array(bone_result->value());
+				}
+				bone_names.push_back(name);
+				if (vertex_bone_name.has(vertexId)) {
+					vertex_bone_name[vertexId] = bone_names;
+				} else {
+					vertex_bone_name.insert(vertexId, bone_names);
+				}
+			}
+		}
+
+		Ref<SurfaceTool> st;
+		st.instance();
+		st->begin(Mesh::PRIMITIVE_TRIANGLES);
+
+		for (size_t j = 0; j < ai_mesh->mNumVertices; j++) {
+			if (ai_mesh->HasTextureCoords(0)) {
+				has_uvs = true;
+				st->add_uv(Vector2(ai_mesh->mTextureCoords[0][j].x, 1.0f - ai_mesh->mTextureCoords[0][j].y));
+			}
+			if (ai_mesh->HasTextureCoords(1)) {
+				has_uvs = true;
+				st->add_uv2(Vector2(ai_mesh->mTextureCoords[1][j].x, 1.0f - ai_mesh->mTextureCoords[1][j].y));
+			}
+			if (ai_mesh->HasVertexColors(0)) {
+				Color color = Color(ai_mesh->mColors[0]->r, ai_mesh->mColors[0]->g, ai_mesh->mColors[0]->b, ai_mesh->mColors[0]->a);
+				st->add_color(color);
+			}
+			if (ai_mesh->mNormals != NULL) {
+				const aiVector3D normals = ai_mesh->mNormals[j];
+				const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z);
+				st->add_normal(godot_normal);
+				if (ai_mesh->HasTangentsAndBitangents()) {
+					const aiVector3D tangents = ai_mesh->mTangents[j];
+					const Vector3 godot_tangent = Vector3(tangents.x, tangents.y, tangents.z);
+					const aiVector3D bitangent = ai_mesh->mBitangents[j];
+					const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z);
+					float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f;
+					st->add_tangent(Plane(tangents.x, tangents.y, tangents.z, d));
+				}
+			}
+
+			if (s != NULL && s->get_bone_count() > 0) {
+				Map<uint32_t, Vector<String> >::Element *I = vertex_bone_name.find(j);
+				Vector<int32_t> bones;
+				if (I != NULL) {
+					Vector<String> bone_names;
+					bone_names.append_array(I->value());
+					for (int32_t f = 0; f < bone_names.size(); f++) {
+						int32_t bone = s->find_bone(bone_names[f]);
+						ERR_EXPLAIN("Asset Importer: Mesh can't find bone " + bone_names[f]);
+						ERR_FAIL_COND(bone == -1);
+						bones.push_back(bone);
+					}
+					if (s->get_bone_count()) {
+						int32_t add = CLAMP(p_max_bone_weights - bones.size(), 0, p_max_bone_weights);
+						for (int32_t f = 0; f < add; f++) {
+							bones.push_back(0);
+						}
+					}
+					st->add_bones(bones);
+					Map<uint32_t, Vector<float> >::Element *E = vertex_weight.find(j);
+					Vector<float> weights;
+					if (E != NULL) {
+						weights = E->value();
+						if (weights.size() != p_max_bone_weights) {
+							int32_t add = CLAMP(p_max_bone_weights - weights.size(), 0, p_max_bone_weights);
+							for (int32_t f = 0; f < add; f++) {
+								weights.push_back(0.0f);
+							}
+						}
+					}
+					ERR_CONTINUE(weights.size() == 0);
+					st->add_weights(weights);
+				}
+			}
+			const aiVector3D pos = ai_mesh->mVertices[j];
+			Vector3 godot_pos = Vector3(pos.x, pos.y, pos.z);
+			st->add_vertex(godot_pos);
+		}
+		for (size_t j = 0; j < ai_mesh->mNumFaces; j++) {
+			const aiFace face = ai_mesh->mFaces[j];
+			ERR_FAIL_COND(face.mNumIndices != 3);
+			Vector<size_t> order;
+			order.push_back(2);
+			order.push_back(1);
+			order.push_back(0);
+			for (int32_t k = 0; k < order.size(); k++) {
+				st->add_index(face.mIndices[order[k]]);
+			}
+		}
+		if (ai_mesh->HasTangentsAndBitangents() == false && has_uvs) {
+			st->generate_tangents();
+		}
+		aiMaterial *ai_material = p_scene->mMaterials[ai_mesh->mMaterialIndex];
+		Ref<SpatialMaterial> mat;
+		mat.instance();
+
+		int32_t mat_two_sided = 0;
+		if (AI_SUCCESS == ai_material->Get(AI_MATKEY_TWOSIDED, mat_two_sided)) {
+			if (mat_two_sided > 0) {
+				mat->set_cull_mode(SpatialMaterial::CULL_DISABLED);
+			}
+		}
+
+		const String mesh_name = _ai_string_to_string(ai_mesh->mName);
+		aiString mat_name;
+		if (AI_SUCCESS == ai_material->Get(AI_MATKEY_NAME, mat_name)) {
+			mat->set_name(_ai_string_to_string(mat_name));
+		}
+
+		aiTextureType tex_normal = aiTextureType_NORMALS;
+		{
+			aiString ai_filename = aiString();
+			String filename = "";
+			aiTextureMapMode map_mode[2];
+
+			if (AI_SUCCESS == ai_material->GetTexture(tex_normal, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) {
+				filename = _ai_raw_string_to_string(ai_filename);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+
+					if (texture != NULL) {
+						if (map_mode != NULL) {
+							_set_texture_mapping_mode(map_mode, texture);
+						}
+						mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+						mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
+					}
+				}
+			}
+		}
+
+		{
+			aiString ai_filename = aiString();
+			String filename = "";
+
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_NORMAL_TEXTURE, ai_filename)) {
+				filename = _ai_raw_string_to_string(ai_filename);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					if (texture != NULL) {
+						mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+						mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
+					}
+				}
+			}
+		}
+
+		aiTextureType tex_emissive = aiTextureType_EMISSIVE;
+
+		if (ai_material->GetTextureCount(tex_emissive) > 0) {
+
+			aiString ai_filename = aiString();
+			String filename = "";
+			aiTextureMapMode map_mode[2];
+
+			if (AI_SUCCESS == ai_material->GetTexture(tex_emissive, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) {
+				filename = _ai_raw_string_to_string(ai_filename);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					if (texture != NULL) {
+						_set_texture_mapping_mode(map_mode, texture);
+						mat->set_feature(SpatialMaterial::FEATURE_EMISSION, true);
+						mat->set_texture(SpatialMaterial::TEXTURE_EMISSION, texture);
+					}
+				}
+			}
+		}
+
+		aiTextureType tex_albedo = aiTextureType_DIFFUSE;
+		if (ai_material->GetTextureCount(tex_albedo) > 0) {
+
+			aiString ai_filename = aiString();
+			String filename = "";
+			aiTextureMapMode map_mode[2];
+			if (AI_SUCCESS == ai_material->GetTexture(tex_albedo, 0, &ai_filename, NULL, NULL, NULL, NULL, map_mode)) {
+				filename = _ai_raw_string_to_string(ai_filename);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					if (texture != NULL) {
+						if (texture->get_data()->detect_alpha() != Image::ALPHA_NONE) {
+							_set_texture_mapping_mode(map_mode, texture);
+							mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+							mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+						}
+						mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
+					}
+				}
+			}
+		} else {
+			aiColor4D clr_diffuse;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_COLOR_DIFFUSE, clr_diffuse)) {
+				if (Math::is_equal_approx(clr_diffuse.a, 1.0f) == false) {
+					mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+					mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+				}
+				mat->set_albedo(Color(clr_diffuse.r, clr_diffuse.g, clr_diffuse.b, clr_diffuse.a));
+			}
+		}
+
+		aiString tex_gltf_base_color_path = aiString();
+		aiTextureMapMode map_mode[2];
+		if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_TEXTURE, &tex_gltf_base_color_path, NULL, NULL, NULL, NULL, map_mode)) {
+			String filename = _ai_raw_string_to_string(tex_gltf_base_color_path);
+			String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+			bool found = false;
+			_find_texture_path(p_path, path, found);
+			if (found) {
+				Ref<Texture> texture = _load_texture(p_scene, path);
+				_find_texture_path(p_path, path, found);
+				if (texture != NULL) {
+					if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
+						_set_texture_mapping_mode(map_mode, texture);
+						mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+						mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+					}
+					mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
+				}
+			}
+		} else {
+			aiColor4D pbr_base_color;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_BASE_COLOR_FACTOR, pbr_base_color)) {
+				if (Math::is_equal_approx(pbr_base_color.a, 1.0f) == false) {
+					mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+					mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+				}
+				mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a));
+			}
+		}
+		{
+			aiString tex_fbx_pbs_base_color_path = aiString();
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_base_color_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					_find_texture_path(p_path, path, found);
+					if (texture != NULL) {
+						if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
+							mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+							mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+						}
+						mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
+					}
+				}
+			} else {
+				aiColor4D pbr_base_color;
+				if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR, pbr_base_color)) {
+					mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a));
+				}
+			}
+
+			aiUVTransform pbr_base_color_uv_xform;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM, pbr_base_color_uv_xform)) {
+				mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f));
+				mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f));
+			}
+		}
+
+		{
+			aiString tex_fbx_pbs_normal_path = aiString();
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE, tex_fbx_pbs_normal_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_normal_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					_find_texture_path(p_path, path, found);
+					if (texture != NULL) {
+						mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+						mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
+					}
+				}
+			}
+		}
+
+		aiString cull_mode;
+		if (p_node->mMetaData) {
+			p_node->mMetaData->Get("Culling", cull_mode);
+		}
+		if (cull_mode.length != 0 && cull_mode == aiString("CullingOff")) {
+			mat->set_cull_mode(SpatialMaterial::CULL_DISABLED);
+		}
+
+		{
+			aiString tex_fbx_stingray_normal_path = aiString();
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE, tex_fbx_stingray_normal_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_stingray_normal_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					_find_texture_path(p_path, path, found);
+					if (texture != NULL) {
+						mat->set_feature(SpatialMaterial::Feature::FEATURE_NORMAL_MAPPING, true);
+						mat->set_texture(SpatialMaterial::TEXTURE_NORMAL, texture);
+					}
+				}
+			}
+		}
+
+		{
+			aiString tex_fbx_pbs_base_color_path = aiString();
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE, tex_fbx_pbs_base_color_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_base_color_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					_find_texture_path(p_path, path, found);
+					if (texture != NULL) {
+						if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
+							mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+							mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+						}
+						mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
+					}
+				}
+			} else {
+				aiColor4D pbr_base_color;
+				if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR, pbr_base_color)) {
+					mat->set_albedo(Color(pbr_base_color.r, pbr_base_color.g, pbr_base_color.b, pbr_base_color.a));
+				}
+			}
+
+			aiUVTransform pbr_base_color_uv_xform;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM, pbr_base_color_uv_xform)) {
+				mat->set_uv1_offset(Vector3(pbr_base_color_uv_xform.mTranslation.x, pbr_base_color_uv_xform.mTranslation.y, 0.0f));
+				mat->set_uv1_scale(Vector3(pbr_base_color_uv_xform.mScaling.x, pbr_base_color_uv_xform.mScaling.y, 1.0f));
+			}
+		}
+
+		{
+			aiString tex_fbx_pbs_emissive_path = aiString();
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE, tex_fbx_pbs_emissive_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_emissive_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					_find_texture_path(p_path, path, found);
+					if (texture != NULL) {
+						if (texture->get_data()->detect_alpha() == Image::ALPHA_BLEND) {
+							mat->set_feature(SpatialMaterial::FEATURE_TRANSPARENT, true);
+							mat->set_depth_draw_mode(SpatialMaterial::DepthDrawMode::DEPTH_DRAW_ALPHA_OPAQUE_PREPASS);
+						}
+						mat->set_texture(SpatialMaterial::TEXTURE_ALBEDO, texture);
+					}
+				}
+			} else {
+				aiColor4D pbr_emmissive_color;
+				if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR, pbr_emmissive_color)) {
+					mat->set_emission(Color(pbr_emmissive_color.r, pbr_emmissive_color.g, pbr_emmissive_color.b, pbr_emmissive_color.a));
+				}
+			}
+
+			real_t pbr_emission_intensity;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR, pbr_emission_intensity)) {
+				mat->set_emission_energy(pbr_emission_intensity);
+			}
+		}
+
+		aiString tex_gltf_pbr_metallicroughness_path;
+		if (AI_SUCCESS == ai_material->GetTexture(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLICROUGHNESS_TEXTURE, &tex_gltf_pbr_metallicroughness_path)) {
+			String filename = _ai_raw_string_to_string(tex_gltf_pbr_metallicroughness_path);
+			String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+			bool found = false;
+			_find_texture_path(p_path, path, found);
+			if (found) {
+				Ref<Texture> texture = _load_texture(p_scene, path);
+				if (texture != NULL) {
+					mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture);
+					mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_BLUE);
+					mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture);
+					mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GREEN);
+				}
+			}
+		} else {
+			float pbr_roughness = 0.0f;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_ROUGHNESS_FACTOR, pbr_roughness)) {
+				mat->set_roughness(pbr_roughness);
+			}
+			float pbr_metallic = 0.0f;
+
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_GLTF_PBRMETALLICROUGHNESS_METALLIC_FACTOR, pbr_metallic)) {
+				mat->set_metallic(pbr_metallic);
+			}
+		}
+		{
+			aiString tex_fbx_pbs_metallic_path;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE, tex_fbx_pbs_metallic_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_metallic_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					if (texture != NULL) {
+						mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture);
+						mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
+					}
+				}
+			} else {
+				float pbr_metallic = 0.0f;
+				if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR, pbr_metallic)) {
+					mat->set_metallic(pbr_metallic);
+				}
+			}
+
+			aiString tex_fbx_pbs_rough_path;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_rough_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					if (texture != NULL) {
+						mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture);
+						mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
+					}
+				}
+			} else {
+				float pbr_roughness = 0.04f;
+
+				if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR, pbr_roughness)) {
+					mat->set_roughness(pbr_roughness);
+				}
+			}
+		}
+
+		{
+			aiString tex_fbx_pbs_metallic_path;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE, tex_fbx_pbs_metallic_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_metallic_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					if (texture != NULL) {
+						mat->set_texture(SpatialMaterial::TEXTURE_METALLIC, texture);
+						mat->set_metallic_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
+					}
+				}
+			} else {
+				float pbr_metallic = 0.0f;
+				if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_METALNESS_FACTOR, pbr_metallic)) {
+					mat->set_metallic(pbr_metallic);
+				}
+			}
+
+			aiString tex_fbx_pbs_rough_path;
+			if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE, tex_fbx_pbs_rough_path)) {
+				String filename = _ai_raw_string_to_string(tex_fbx_pbs_rough_path);
+				String path = p_path.get_base_dir() + "/" + filename.replace("\\", "/");
+				bool found = false;
+				_find_texture_path(p_path, path, found);
+				if (found) {
+					Ref<Texture> texture = _load_texture(p_scene, path);
+					if (texture != NULL) {
+						mat->set_texture(SpatialMaterial::TEXTURE_ROUGHNESS, texture);
+						mat->set_roughness_texture_channel(SpatialMaterial::TEXTURE_CHANNEL_GRAYSCALE);
+					}
+				}
+			} else {
+				float pbr_roughness = 0.04f;
+
+				if (AI_SUCCESS == ai_material->Get(AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR, pbr_roughness)) {
+					mat->set_roughness(pbr_roughness);
+				}
+			}
+		}
+
+		Array array_mesh = st->commit_to_arrays();
+		Array morphs;
+		morphs.resize(ai_mesh->mNumAnimMeshes);
+		Mesh::PrimitiveType primitive = Mesh::PRIMITIVE_TRIANGLES;
+		Map<uint32_t, String> morph_mesh_idx_names;
+		for (size_t j = 0; j < ai_mesh->mNumAnimMeshes; j++) {
+
+			String ai_anim_mesh_name = _ai_string_to_string(ai_mesh->mAnimMeshes[j]->mName);
+			mesh->set_blend_shape_mode(Mesh::BLEND_SHAPE_MODE_NORMALIZED);
+			if (ai_anim_mesh_name.empty()) {
+				ai_anim_mesh_name = String("morph_") + itos(j);
+			}
+			mesh->add_blend_shape(ai_anim_mesh_name);
+			morph_mesh_idx_names.insert(j, ai_anim_mesh_name);
+			Array array_copy;
+			array_copy.resize(VisualServer::ARRAY_MAX);
+
+			for (int l = 0; l < VisualServer::ARRAY_MAX; l++) {
+				array_copy[l] = array_mesh[l].duplicate(true);
+			}
+
+			const size_t num_vertices = ai_mesh->mAnimMeshes[j]->mNumVertices;
+			array_copy[Mesh::ARRAY_INDEX] = Variant();
+			if (ai_mesh->mAnimMeshes[j]->HasPositions()) {
+				PoolVector3Array vertices;
+				vertices.resize(num_vertices);
+				for (size_t l = 0; l < num_vertices; l++) {
+					const aiVector3D ai_pos = ai_mesh->mAnimMeshes[j]->mVertices[l];
+					Vector3 position = Vector3(ai_pos.x, ai_pos.y, ai_pos.z);
+					vertices.write()[l] = position;
+				}
+				PoolVector3Array new_vertices = array_copy[VisualServer::ARRAY_VERTEX].duplicate(true);
+
+				for (int32_t l = 0; l < vertices.size(); l++) {
+					PoolVector3Array::Write w = new_vertices.write();
+					w[l] = vertices[l];
+				}
+				ERR_CONTINUE(vertices.size() != new_vertices.size());
+				array_copy[VisualServer::ARRAY_VERTEX] = new_vertices;
+			}
+
+			int32_t color_set = 0;
+			if (ai_mesh->mAnimMeshes[j]->HasVertexColors(color_set)) {
+				PoolColorArray colors;
+				colors.resize(num_vertices);
+				for (size_t l = 0; l < num_vertices; l++) {
+					const aiColor4D ai_color = ai_mesh->mAnimMeshes[j]->mColors[color_set][l];
+					Color color = Color(ai_color.r, ai_color.g, ai_color.b, ai_color.a);
+					colors.write()[l] = color;
+				}
+				PoolColorArray new_colors = array_copy[VisualServer::ARRAY_COLOR].duplicate(true);
+
+				for (int32_t l = 0; l < colors.size(); l++) {
+					PoolColorArray::Write w = new_colors.write();
+					w[l] = colors[l];
+				}
+				array_copy[VisualServer::ARRAY_COLOR] = new_colors;
+			}
+
+			if (ai_mesh->mAnimMeshes[j]->HasNormals()) {
+				PoolVector3Array normals;
+				normals.resize(num_vertices);
+				for (size_t l = 0; l < num_vertices; l++) {
+					const aiVector3D ai_normal = ai_mesh->mAnimMeshes[i]->mNormals[l];
+					Vector3 normal = Vector3(ai_normal.x, ai_normal.y, ai_normal.z);
+					normals.write()[l] = normal;
+				}
+				PoolVector3Array new_normals = array_copy[VisualServer::ARRAY_NORMAL].duplicate(true);
+
+				for (int l = 0; l < normals.size(); l++) {
+					PoolVector3Array::Write w = new_normals.write();
+					w[l] = normals[l];
+				}
+				array_copy[VisualServer::ARRAY_NORMAL] = new_normals;
+			}
+
+			if (ai_mesh->mAnimMeshes[j]->HasTangentsAndBitangents()) {
+				PoolColorArray tangents;
+				tangents.resize(num_vertices);
+				PoolColorArray::Write w = tangents.write();
+				for (size_t l = 0; l < num_vertices; l++) {
+					_calc_tangent_from_mesh(ai_mesh, j, l, l, w);
+				}
+				PoolRealArray new_tangents = array_copy[VisualServer::ARRAY_TANGENT].duplicate(true);
+				ERR_CONTINUE(new_tangents.size() != tangents.size() * 4);
+				for (int32_t l = 0; l < tangents.size(); l++) {
+					new_tangents.write()[l + 0] = tangents[l].r;
+					new_tangents.write()[l + 1] = tangents[l].g;
+					new_tangents.write()[l + 2] = tangents[l].b;
+					new_tangents.write()[l + 3] = tangents[l].a;
+				}
+
+				array_copy[VisualServer::ARRAY_TANGENT] = new_tangents;
+			}
+
+			morphs[j] = array_copy;
+		}
+		r_name_morph_mesh_names.insert(_ai_raw_string_to_string(p_node->mName), morph_mesh_idx_names);
+		mesh->add_surface_from_arrays(primitive, array_mesh, morphs);
+		mesh->surface_set_material(i, mat);
+		mesh->surface_set_name(i, _ai_string_to_string(ai_mesh->mName));
+		r_mesh_count++;
+		print_line(String("Open Asset Import: Created mesh (including instances) ") + _ai_string_to_string(ai_mesh->mName) + " " + itos(r_mesh_count) + " of " + itos(p_scene->mNumMeshes));
+	}
+	p_mesh_instance->set_mesh(mesh);
+}
+
+Ref<Texture> EditorSceneImporterAssimp::_load_texture(const aiScene *p_scene, String p_path) {
+	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<Texture>());
+		aiTexture *tex = p_scene->mTextures[texture_idx];
+		String filename = _ai_raw_string_to_string(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<Texture>());
+
+				Ref<ImageTexture> t;
+				t.instance();
+				t->create_from_image(img);
+				t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
+				return t;
+			} 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<Texture>());
+				Ref<ImageTexture> t;
+				t.instance();
+				t->create_from_image(img);
+				t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
+				return t;
+			} else if (tex->CheckFormat("dds")) {
+				ERR_EXPLAIN("Open Asset Import: Embedded dds not implemented");
+				ERR_FAIL_COND_V(true, Ref<Texture>());
+				//Ref<Image> img = Image::_dds_mem_loader_func((uint8_t *)tex->pcData, tex->mWidth);
+				//ERR_FAIL_COND_V(img.is_null(), Ref<Texture>());
+				//Ref<ImageTexture> t;
+				//t.instance();
+				//t->create_from_image(img);
+				//t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
+				//return t;
+			}
+		} 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<Texture>());
+			//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<Texture>());
+
+			Ref<ImageTexture> t;
+			t.instance();
+			t->create_from_image(img);
+			t->set_storage(ImageTexture::STORAGE_COMPRESS_LOSSY);
+			return t;
+		}
+		return Ref<Texture>();
+	}
+	Ref<Texture> p_texture = ResourceLoader::load(p_path, "Texture");
+	return p_texture;
+}
+
+void EditorSceneImporterAssimp::_calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w) {
+	const aiVector3D normals = ai_mesh->mAnimMeshes[i]->mNormals[tri_index];
+	const Vector3 godot_normal = Vector3(normals.x, normals.y, normals.z);
+	const aiVector3D tangent = ai_mesh->mAnimMeshes[i]->mTangents[tri_index];
+	const Vector3 godot_tangent = Vector3(tangent.x, tangent.y, tangent.z);
+	const aiVector3D bitangent = ai_mesh->mAnimMeshes[i]->mBitangents[tri_index];
+	const Vector3 godot_bitangent = Vector3(bitangent.x, bitangent.y, bitangent.z);
+	float d = godot_normal.cross(godot_tangent).dot(godot_bitangent) > 0.0f ? 1.0f : -1.0f;
+	Color plane_tangent = Color(tangent.x, tangent.y, tangent.z, d);
+	w[index] = plane_tangent;
+}
+
+void EditorSceneImporterAssimp::_set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture) {
+	ERR_FAIL_COND(map_mode == NULL);
+	aiTextureMapMode tex_mode = aiTextureMapMode::aiTextureMapMode_Wrap;
+	//for (size_t i = 0; i < 3; i++) {
+	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);
+}
+
+void EditorSceneImporterAssimp::_find_texture_path(const String &r_p_path, String &r_path, bool &r_found) {
+
+	_Directory dir;
+
+	List<String> exts;
+	ImageLoader::get_recognized_extensions(&exts);
+
+	Vector<String> split_path = r_path.get_basename().split("*");
+	if (split_path.size() == 2) {
+		r_found = true;
+		return;
+	}
+
+	if (dir.file_exists(r_p_path.get_base_dir() + r_path.get_file())) {
+		r_path = r_p_path.get_base_dir() + r_path.get_file();
+		r_found = true;
+		return;
+	}
+
+	for (int32_t i = 0; i < exts.size(); i++) {
+		if (r_found) {
+			return;
+		}
+		if (r_found == false) {
+			_find_texture_path(r_p_path, dir, r_path, r_found, "." + exts[i]);
+		}
+	}
+}
+
+void EditorSceneImporterAssimp::_find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension) {
+	String name = path.get_basename() + extension;
+	if (dir.file_exists(name)) {
+		found = true;
+		path = name;
+		return;
+	}
+	String name_ignore_sub_directory = p_path.get_base_dir() + "/" + path.get_file().get_basename() + extension;
+	if (dir.file_exists(name_ignore_sub_directory)) {
+		found = true;
+		path = name_ignore_sub_directory;
+		return;
+	}
+
+	String name_find_texture_sub_directory = p_path.get_base_dir() + "/textures/" + path.get_file().get_basename() + extension;
+	if (dir.file_exists(name_find_texture_sub_directory)) {
+		found = true;
+		path = name_find_texture_sub_directory;
+		return;
+	}
+	String name_find_texture_upper_sub_directory = p_path.get_base_dir() + "/Textures/" + path.get_file().get_basename() + extension;
+	if (dir.file_exists(name_find_texture_upper_sub_directory)) {
+		found = true;
+		path = name_find_texture_upper_sub_directory;
+		return;
+	}
+	String name_find_texture_outside_sub_directory = p_path.get_base_dir() + "/../textures/" + path.get_file().get_basename() + extension;
+	if (dir.file_exists(name_find_texture_outside_sub_directory)) {
+		found = true;
+		path = name_find_texture_outside_sub_directory;
+		return;
+	}
+
+	String name_find_upper_texture_outside_sub_directory = p_path.get_base_dir() + "/../Textures/" + path.get_file().get_basename() + extension;
+	if (dir.file_exists(name_find_upper_texture_outside_sub_directory)) {
+		found = true;
+		path = name_find_upper_texture_outside_sub_directory;
+		return;
+	}
+}
+
+String EditorSceneImporterAssimp::_ai_string_to_string(const aiString p_string) const {
+	Vector<char> raw_name;
+	raw_name.resize(p_string.length);
+	memcpy(raw_name.ptrw(), p_string.C_Str(), p_string.length);
+	String name;
+	name.parse_utf8(raw_name.ptrw(), raw_name.size());
+	if (name.find(":") != -1) {
+		String replaced_name = name.split(":")[1];
+		print_verbose("Replacing " + name + " containing : with " + replaced_name);
+		name = replaced_name;
+	}
+	if (name.find(".") != -1) {
+		String replaced_name = name.replace(".", "");
+		print_verbose("Replacing " + name + " containing . with " + replaced_name);
+		name = replaced_name;
+	}
+	return name;
+}
+
+String EditorSceneImporterAssimp::_ai_anim_string_to_string(const aiString p_string) const {
+	Vector<char> raw_name;
+	raw_name.resize(p_string.length);
+	memcpy(raw_name.ptrw(), p_string.C_Str(), p_string.length);
+	String name;
+	name.parse_utf8(raw_name.ptrw(), raw_name.size());
+	if (name.find(":") != -1) {
+		String replaced_name = name.split(":")[1];
+		print_verbose("Replacing " + name + " containing : with " + replaced_name);
+		name = replaced_name;
+	}
+	return name;
+}
+
+String EditorSceneImporterAssimp::_ai_raw_string_to_string(const aiString p_string) const {
+	Vector<char> raw_name;
+	raw_name.resize(p_string.length);
+	memcpy(raw_name.ptrw(), p_string.C_Str(), p_string.length);
+	String name;
+	name.parse_utf8(raw_name.ptrw(), raw_name.size());
+	return name;
+}
+
+Ref<Animation> EditorSceneImporterAssimp::import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps) {
+	return Ref<Animation>();
+}
+
+const Transform EditorSceneImporterAssimp::_ai_matrix_transform(const aiMatrix4x4 p_matrix) {
+	aiMatrix4x4 matrix = p_matrix;
+	Transform xform;
+	xform.set(matrix.a1, matrix.b1, matrix.c1, matrix.a2, matrix.b2, matrix.c2, matrix.a3, matrix.b3, matrix.c3, matrix.a4, matrix.b4, matrix.c4);
+	xform.basis.inverse();
+	xform.basis.transpose();
+	Vector3 scale = xform.basis.get_scale();
+	Quat rot = xform.basis.get_rotation_quat();
+	xform.basis.set_quat_scale(rot, scale);
+	return xform;
+}

+ 206 - 0
modules/assimp/editor_scene_importer_assimp.h

@@ -0,0 +1,206 @@
+/*************************************************************************/
+/*  editor_scene_importer_assimp.h                                       */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 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_ASSIMP_H
+#define EDITOR_SCENE_IMPORTER_ASSIMP_H
+
+#ifdef TOOLS_ENABLED
+#include "core/bind/core_bind.h"
+#include "core/io/resource_importer.h"
+#include "core/vector.h"
+#include "editor/import/resource_importer_scene.h"
+#include "editor/project_settings_editor.h"
+#include "scene/3d/mesh_instance.h"
+#include "scene/3d/skeleton.h"
+#include "scene/3d/spatial.h"
+#include "scene/animation/animation_player.h"
+#include "scene/resources/animation.h"
+#include "scene/resources/surface_tool.h"
+
+#include "assimp/DefaultLogger.hpp"
+#include "assimp/LogStream.hpp"
+#include "assimp/Logger.hpp"
+#include "assimp/matrix4x4.h"
+#include "assimp/scene.h"
+#include "assimp/types.h"
+
+class AssimpStream : public Assimp::LogStream {
+public:
+	// Constructor
+	AssimpStream();
+
+	// Destructor
+	~AssimpStream();
+	// Write something using your own functionality
+	void write(const char *message);
+};
+
+#define AI_MATKEY_FBX_MAYA_BASE_COLOR_FACTOR "$raw.Maya|baseColor", 0, 0
+#define AI_MATKEY_FBX_MAYA_METALNESS_FACTOR "$raw.Maya|metalness", 0, 0
+#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_FACTOR "$raw.Maya|diffuseRoughness", 0, 0
+
+#define AI_MATKEY_FBX_MAYA_METALNESS_TEXTURE "$raw.Maya|metalness|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_METALNESS_UV_XFORM "$raw.Maya|metalness|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_TEXTURE "$raw.Maya|diffuseRoughness|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_DIFFUSE_ROUGHNESS_UV_XFORM "$raw.Maya|diffuseRoughness|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_BASE_COLOR_TEXTURE "$raw.Maya|baseColor|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_BASE_COLOR_UV_XFORM "$raw.Maya|baseColor|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0
+
+#define AI_MATKEY_FBX_NORMAL_TEXTURE "$raw.Maya|normalCamera|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_NORMAL_UV_XFORM "$raw.Maya|normalCamera|uvtrafo", aiTextureType_UNKNOWN, 0
+
+#define AI_MATKEY_FBX_MAYA_STINGRAY_DISPLACEMENT_SCALING_FACTOR "$raw.Maya|displacementscaling", 0, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_BASE_COLOR_FACTOR "$raw.Maya|base_color", 0, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_FACTOR "$raw.Maya|emissive", 0, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_FACTOR "$raw.Maya|metallic", 0, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_FACTOR "$raw.Maya|roughness", 0, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_INTENSITY_FACTOR "$raw.Maya|emissive_intensity", 0, 0
+
+#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_TEXTURE "$raw.Maya|TEX_normal_map|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_NORMAL_UV_XFORM "$raw.Maya|TEX_normal_map|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_TEXTURE "$raw.Maya|TEX_color_map|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_COLOR_UV_XFORM "$raw.Maya|TEX_color_map|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_TEXTURE "$raw.Maya|TEX_metallic_map|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_METALLIC_UV_XFORM "$raw.Maya|TEX_metallic_map|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_TEXTURE "$raw.Maya|TEX_roughness_map|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_ROUGHNESS_UV_XFORM "$raw.Maya|TEX_roughness_map|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_TEXTURE "$raw.Maya|TEX_emissive_map|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_EMISSIVE_UV_XFORM "$raw.Maya|TEX_emissive_map|uvtrafo", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_TEXTURE "$raw.Maya|TEX_ao_map|file", aiTextureType_UNKNOWN, 0
+#define AI_MATKEY_FBX_MAYA_STINGRAY_AO_UV_XFORM "$raw.Maya|TEX_ao_map|uvtrafo", aiTextureType_UNKNOWN, 0
+
+class EditorSceneImporterAssimp : public EditorSceneImporter {
+private:
+	GDCLASS(EditorSceneImporterAssimp, EditorSceneImporter);
+	const String ASSIMP_FBX_KEY = "_$AssimpFbx$";
+
+	struct AssetImportAnimation {
+		enum Interpolation {
+			INTERP_LINEAR,
+			INTERP_STEP,
+			INTERP_CATMULLROMSPLINE,
+			INTERP_CUBIC_SPLINE
+		};
+	};
+
+	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
+		};
+	};
+	Spatial *_generate_scene(const String &p_path, const aiScene *scene, const uint32_t p_flags, int p_bake_fps, const int32_t p_max_bone_weights);
+	void _fill_kept_node(Set<Node *> &keep_nodes);
+	String _find_skeleton_bone_root(Map<Skeleton *, MeshInstance *> &skeletons, Map<MeshInstance *, String> &meshes, Spatial *root);
+	void _set_bone_parent(Skeleton *s, Node *p_owner, aiNode *p_node);
+	Transform _get_global_ai_node_transform(const aiScene *p_scene, const aiNode *p_current_node);
+	void _generate_node_bone(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const String p_path, const int32_t p_max_bone_weights);
+	void _generate_node_bone_parents(const aiScene *p_scene, const aiNode *p_node, Map<String, bool> &p_mesh_bones, Skeleton *p_skeleton, const MeshInstance *p_mi);
+	void _calculate_skeleton_root(Skeleton *s, const aiScene *p_scene, aiNode *&p_ai_skeleton_root, Map<String, bool> &mesh_bones, const aiNode *p_node);
+	void _fill_skeleton(const aiScene *p_scene, const aiNode *p_node, Spatial *p_current, Node *p_owner, Skeleton *p_skeleton, const Map<String, bool> p_mesh_bones, const Map<String, Transform> &p_bone_rests, Set<String> p_tracks, const String p_path, Set<String> &r_removed_bones);
+	void _keep_node(const String &p_path, Node *p_current, Node *p_owner, Set<Node *> &r_keep_nodes);
+	void _filter_node(const String &p_path, Node *p_current, Node *p_owner, const Set<Node *> p_keep_nodes, Set<String> &r_removed_nodes);
+	void _generate_node(const String &p_path, const aiScene *p_scene, const aiNode *p_node, Node *p_parent, Node *p_owner, Set<String> &r_bone_name, Set<String> p_light_names, Set<String> p_camera_names, Map<Skeleton *, MeshInstance *> &r_skeletons, const Map<String, Transform> &p_bone_rests, Vector<MeshInstance *> &r_mesh_instances, int32_t &r_mesh_count, Skeleton *p_skeleton, const int32_t p_max_bone_weights, Set<String> &r_removed_bones, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names);
+	aiNode *_ai_find_node(aiNode *ai_child_node, const String bone_name);
+	Transform _format_rot_xform(const String p_path, const aiScene *p_scene);
+	void _get_track_set(const aiScene *p_scene, Set<String> &tracks);
+	void _insert_animation_track(const aiScene *p_scene, const String p_path, int p_bake_fps, Ref<Animation> animation, float ticks_per_second, float length, const Skeleton *sk, const aiNodeAnim *track, String node_name, NodePath node_path);
+	void _add_mesh_to_mesh_instance(const aiNode *p_node, const aiScene *p_scene, Skeleton *s, const String &p_path, MeshInstance *p_mesh_instance, Node *p_owner, Set<String> &r_bone_name, int32_t &r_mesh_count, int32_t p_max_bone_weights, Map<String, Map<uint32_t, String> > &r_name_morph_mesh_names);
+	Ref<Texture> _load_texture(const aiScene *p_scene, String p_path);
+	void _calc_tangent_from_mesh(const aiMesh *ai_mesh, int i, int tri_index, int index, PoolColorArray::Write &w);
+	void _set_texture_mapping_mode(aiTextureMapMode *map_mode, Ref<Texture> texture);
+	void _find_texture_path(const String &p_path, String &path, bool &r_found);
+	void _find_texture_path(const String &p_path, _Directory &dir, String &path, bool &found, String extension);
+	String _ai_string_to_string(const aiString p_string) const;
+	String _ai_anim_string_to_string(const aiString p_string) const;
+	String _ai_raw_string_to_string(const aiString p_string) const;
+	void _import_animation(const String p_path, const Vector<MeshInstance *> p_meshes, const aiScene *p_scene, AnimationPlayer *ap, int32_t p_index, int p_bake_fps, Map<Skeleton *, MeshInstance *> p_skeletons, const Set<String> p_removed_nodes, const Set<String> removed_bones, const Map<String, Map<uint32_t, String> > p_path_morph_mesh_names);
+	void _insert_pivot_anim_track(const Vector<MeshInstance *> p_meshes, const String p_node_name, Vector<const aiNodeAnim *> F, AnimationPlayer *ap, Skeleton *sk, float &length, float ticks_per_second, Ref<Animation> animation, int p_bake_fps, const String &p_path, const aiScene *p_scene);
+	float _get_fbx_fps(int32_t time_mode, const aiScene *p_scene);
+	template <class T>
+	T _interpolate_track(const Vector<float> &p_times, const Vector<T> &p_values, float p_time, AssetImportAnimation::Interpolation p_interp);
+	const Transform _ai_matrix_transform(const aiMatrix4x4 p_matrix);
+	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;
+
+	struct ImportFormat {
+		Vector<String> extensions;
+		bool is_default;
+	};
+
+protected:
+	static void _bind_methods();
+
+public:
+	EditorSceneImporterAssimp() {
+		Assimp::DefaultLogger::create("", Assimp::Logger::VERBOSE);
+		unsigned int severity = Assimp::Logger::Info | Assimp::Logger::Err | Assimp::Logger::Warn;
+		Assimp::DefaultLogger::get()->attachStream(new AssimpStream(), severity);
+	}
+	~EditorSceneImporterAssimp() {
+		Assimp::DefaultLogger::kill();
+	}
+
+	virtual void get_extensions(List<String> *r_extensions) const;
+	virtual uint32_t get_import_flags() const;
+	virtual Node *import_scene(const String &p_path, uint32_t p_flags, int p_bake_fps, List<String> *r_missing_deps, Error *r_err = NULL);
+	virtual Ref<Animation> import_animation(const String &p_path, uint32_t p_flags, int p_bake_fps);
+};
+#endif
+#endif

+ 261 - 0
modules/assimp/godot_update_assimp.sh

@@ -0,0 +1,261 @@
+rm -rf ../../thirdparty/assimp
+cd ../../thirdparty/
+git clone https://github.com/assimp/assimp.git
+cd assimp
+rm -rf code/3DSExporter.h
+rm -rf code/3DSLoader.h
+rm -rf code/3MFXmlTags.h
+rm -rf code/ABCImporter.h
+rm -rf code/ACLoader.h
+rm -rf code/AMFImporter_Macro.hpp
+rm -rf code/ASELoader.h
+rm -rf code/assbin_chunks.h
+rm -rf code/AssbinExporter.h
+rm -rf code/AssbinLoader.h
+rm -rf code/AssimpCExport.cpp
+rm -rf code/AssxmlExporter.h
+rm -rf code/B3DImporter.h
+# rm -rf code/BaseProcess.cpp
+# rm -rf code/BaseProcess.h
+# rm -rf code/Bitmap.cpp
+rm -rf code/BlenderBMesh.cpp
+rm -rf code/BlenderBMesh.h
+rm -rf code/BlenderCustomData.cpp
+rm -rf code/BlenderCustomData.h
+rm -rf code/BlenderIntermediate.h
+rm -rf code/BlenderLoader.h
+rm -rf code/BlenderModifier.h
+rm -rf code/BlenderSceneGen.h
+rm -rf code/BlenderTessellator.h
+rm -rf code/BVHLoader.h
+rm -rf code/C4DImporter.h
+# rm -rf code/CalcTangentsProcess.h
+# rm -rf code/CInterfaceIOWrapper.cpp
+# rm -rf code/CInterfaceIOWrapper.h
+rm -rf code/COBLoader.h
+rm -rf code/COBScene.h
+rm -rf code/ColladaExporter.h
+rm -rf code/ColladaLoader.h
+# rm -rf code/ComputeUVMappingProcess.h
+# rm -rf code/ConvertToLHProcess.h
+# rm -rf code/CreateAnimMesh.cpp
+rm -rf code/CSMLoader.h
+rm -rf code/D3MFExporter.h
+rm -rf code/D3MFImporter.h
+rm -rf code/D3MFOpcPackage.h
+# rm -rf code/DeboneProcess.h
+# rm -rf code/DefaultIOStream.cpp
+# rm -rf code/DefaultIOSystem.cpp
+# rm -rf code/DefaultProgressHandler.h
+# rm -rf code/DropFaceNormalsProcess.cpp
+# rm -rf code/DropFaceNormalsProcess.h
+rm -rf code/DXFHelper.h
+rm -rf code/DXFLoader.h
+# rm -rf code/EmbedTexturesProcess.cpp
+# rm -rf code/EmbedTexturesProcess.h
+# rm -rf code/FBXCommon.h
+# rm -rf code/FBXCompileConfig.h
+# rm -rf code/FBXDeformer.cpp
+# rm -rf code/FBXDocumentUtil.cpp
+# rm -rf code/FBXDocumentUtil.h
+# rm -rf code/FBXExporter.h
+# rm -rf code/FBXExportNode.h
+# rm -rf code/FBXExportProperty.h
+# rm -rf code/FBXImporter.cpp
+# rm -rf code/FBXImporter.h
+# rm -rf code/FBXImportSettings.h
+# rm -rf code/FBXMeshGeometry.h
+# rm -rf code/FBXModel.cpp
+# rm -rf code/FBXNodeAttribute.cpp
+# rm -rf code/FBXParser.h
+# rm -rf code/FBXProperties.cpp
+# rm -rf code/FBXProperties.h
+# rm -rf code/FBXTokenizer.cpp
+# rm -rf code/FBXTokenizer.h
+# rm -rf code/FBXUtil.cpp
+# rm -rf code/FBXUtil.h
+# rm -rf code/FileLogStream.h
+# rm -rf code/FindDegenerates.h
+# rm -rf code/FindInstancesProcess.h
+# rm -rf code/FindInvalidDataProcess.h
+rm -rf code/FIReader.hpp
+# rm -rf code/FixNormalsStep.cpp
+# rm -rf code/FixNormalsStep.h
+# rm -rf code/GenFaceNormalsProcess.cpp
+# rm -rf code/GenFaceNormalsProcess.h
+# rm -rf code/GenVertexNormalsProcess.cpp
+# rm -rf code/GenVertexNormalsProcess.h
+rm -rf code/glTF2Asset.h
+rm -rf code/glTF2Asset.inl
+rm -rf code/glTF2AssetWriter.inl
+rm -rf code/glTF2Exporter.cpp
+rm -rf code/glTF2Importer.cpp
+rm -rf code/glTF2AssetWriter.h
+rm -rf code/glTFAsset.h
+rm -rf code/glTFAsset.inl
+rm -rf code/glTFAssetWriter.inl
+rm -rf code/glTFExporter.cpp
+rm -rf code/glTFImporter.cpp
+rm -rf code/glTF2Exporter.h
+rm -rf code/glTF2Importer.h
+rm -rf code/glTFAssetWriter.h
+rm -rf code/glTFExporter.h
+rm -rf code/glTFImporter.h
+rm -rf code/HalfLifeFileData.h
+rm -rf code/HMPFileData.h
+rm -rf code/HMPLoader.h
+rm -rf code/HMPLoader.cpp
+rm -rf code/IFF.h
+# rm -rf code/Importer.h
+# rm -rf code/ImproveCacheLocality.h
+rm -rf code/IRRLoader.h
+rm -rf code/IRRMeshLoader.h
+rm -rf code/IRRShared.h
+# rm -rf code/JoinVerticesProcess.h
+# rm -rf code/LimitBoneWeightsProcess.cpp
+# rm -rf code/LimitBoneWeightsProcess.h
+rm -rf code/LWSLoader.h
+rm -rf code/makefile.mingw
+# rm -rf code/MakeVerboseFormat.cpp
+# rm -rf code/MakeVerboseFormat.h
+# rm -rf code/MaterialSystem.h
+rm -rf code/MD2FileData.h
+rm -rf code/MD2Loader.h
+rm -rf code/MD2NormalTable.h
+rm -rf code/MD3FileData.h
+rm -rf code/MD3Loader.h
+rm -rf code/MD4FileData.h
+rm -rf code/MD5Loader.h
+rm -rf code/MD5Parser.cpp
+rm -rf code/MDCFileData.h
+rm -rf code/MDCLoader.h
+rm -rf code/MDLDefaultColorMap.h
+# rm -rf code/MMDCpp14.h
+# rm -rf code/MMDImporter.h
+rm -rf code/MS3DLoader.h
+rm -rf code/NDOLoader.h
+rm -rf code/NFFLoader.h
+rm -rf code/ObjExporter.h
+rm -rf code/ObjFileImporter.h
+rm -rf code/ObjFileMtlImporter.h
+rm -rf code/ObjFileParser.h
+rm -rf code/ObjTools.h
+rm -rf code/ObjExporter.cpp
+rm -rf code/ObjFileImporter.cpp
+rm -rf code/ObjFileMtlImporter.cpp
+rm -rf code/ObjFileParser.cpp
+rm -rf code/OFFLoader.h
+rm -rf code/OFFLoader.cpp
+rm -rf code/OgreImporter.cpp
+rm -rf code/OgreImporter.h
+rm -rf code/OgreParsingUtils.h
+rm -rf code/OgreXmlSerializer.h
+rm -rf code/OgreXmlSerializer.cpp
+rm -rf code/OgreBinarySerializer.cpp
+rm -rf code/OpenGEXExporter.cpp
+rm -rf code/OpenGEXExporter.h
+rm -rf code/OpenGEXImporter.h
+rm -rf code/OpenGEXStructs.h
+rm -rf code/OpenGEXImporter.cpp
+# rm -rf code/OptimizeGraph.h
+# rm -rf code/OptimizeMeshes.cpp
+# rm -rf code/OptimizeMeshes.h
+rm -rf code/PlyExporter.h
+rm -rf code/PlyLoader.h
+# rm -rf code/PolyTools.h
+# rm -rf code/PostStepRegistry.cpp
+# rm -rf code/PretransformVertices.h
+rm -rf code/Q3BSPFileData.h
+rm -rf code/Q3BSPFileImporter.h
+rm -rf code/Q3BSPFileParser.cpp
+rm -rf code/Q3BSPFileParser.h
+rm -rf code/Q3BSPZipArchive.cpp
+rm -rf code/Q3BSPZipArchive.h
+rm -rf code/Q3DLoader.h
+rm -rf code/Q3DLoader.cpp
+rm -rf code/Q3BSPFileImporter.cpp
+rm -rf code/RawLoader.h
+# rm -rf code/RemoveComments.cpp
+# rm -rf code/RemoveRedundantMaterials.cpp
+# rm -rf code/RemoveRedundantMaterials.h
+# rm -rf code/RemoveVCProcess.h
+# rm -rf code/ScaleProcess.cpp
+# rm -rf code/ScaleProcess.h
+# rm -rf code/scene.cpp
+# rm -rf code/ScenePreprocessor.cpp
+# rm -rf code/ScenePreprocessor.h
+# rm -rf code/ScenePrivate.h
+# rm -rf code/SGSpatialSort.cpp
+rm -rf code/SIBImporter.h
+rm -rf code/SMDLoader.cpp
+# rm -rf code/simd.cpp
+# rm -rf code/simd.h
+# rm -rf code/SortByPTypeProcess.h
+# rm -rf code/SplitByBoneCountProcess.h
+# rm -rf code/SplitLargeMeshes.h
+# rm -rf code/StdOStreamLogStream.h
+rm -rf code/StepExporter.h
+rm -rf code/StepExporter.cpp
+rm -rf code/STLExporter.cpp
+rm -rf code/STLExporter.h
+rm -rf code/STLLoader.h
+rm -rf code/STLLoader.cpp
+# rm -rf code/TargetAnimation.cpp
+# rm -rf code/TargetAnimation.h
+rm -rf code/TerragenLoader.h
+rm -rf code/TerragenLoader.cpp
+# rm -rf code/TextureTransform.h
+# rm -rf code/TriangulateProcess.h
+rm -rf code/UnrealLoader.h
+# rm -rf code/ValidateDataStructure.h
+# rm -rf code/Version.cpp
+# rm -rf code/VertexTriangleAdjacency.cpp
+# rm -rf code/VertexTriangleAdjacency.h
+# rm -rf code/Win32DebugLogStream.h
+rm -rf code/X3DImporter_Macro.hpp
+rm -rf code/X3DImporter_Metadata.cpp
+rm -rf code/X3DImporter_Networking.cpp
+rm -rf code/X3DImporter_Texturing.cpp
+rm -rf code/X3DImporter_Shape.cpp
+rm -rf code/X3DImporter_Rendering.cpp
+rm -rf code/X3DImporter_Postprocess.cpp
+rm -rf code/X3DImporter_Light.cpp
+rm -rf code/X3DImporter_Group.cpp
+rm -rf code/X3DImporter_Geometry3D.cpp
+rm -rf code/X3DImporter_Geometry2D.cpp
+rm -rf code/X3DImporter.cpp
+rm -rf code/X3DExporter.cpp
+rm -rf code/X3DVocabulary.cpp
+rm -rf code/XFileExporter.h
+rm -rf code/XFileExporter.cpp
+rm -rf code/XFileHelper.h
+rm -rf code/XFileHelper.cpp
+rm -rf code/XFileImporter.h
+rm -rf code/XFileImporter.cpp
+rm -rf code/XFileParser.h
+rm -rf code/XFileParser.cpp
+rm -rf code/XGLLoader.h
+rm -rf code/XGLLoader.cpp
+rm -rf code/Importer
+rm -rf .git
+rm -rf cmake-modules
+rm -rf doc
+rm -rf packaging
+rm -rf port
+rm -rf samples
+rm -rf scripts
+rm -rf test
+rm -rf tools
+rm -rf contrib/zlib
+rm -rf contrib/android-cmake
+rm -rf contrib/gtest
+rm -rf contrib/clipper
+rm -rf contrib/irrXML
+rm -rf contrib/Open3DGC
+rm -rf contrib/openddlparser
+rm -rf contrib/poly2tri
+rm -rf contrib/rapidjson
+rm -rf contrib/unzip
+rm -rf contrib/zip
+rm -rf contrib/stb_image
+rm .travis*

+ 53 - 0
modules/assimp/register_types.cpp

@@ -0,0 +1,53 @@
+/*************************************************************************/
+/*  register_types.cpp                                                   */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 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_assimp.h"
+
+#ifdef TOOLS_ENABLED
+static void _editor_init() {
+	Ref<EditorSceneImporterAssimp> import_assimp;
+	import_assimp.instance();
+	ResourceImporterScene::get_singleton()->add_importer(import_assimp);
+}
+#endif
+
+void register_assimp_types() {
+
+#ifdef TOOLS_ENABLED
+	ClassDB::register_class<EditorSceneImporterAssimp>();
+	EditorNode::add_init_callback(_editor_init);
+#endif
+}
+
+void unregister_assimp_types() {
+}

+ 32 - 0
modules/assimp/register_types.h

@@ -0,0 +1,32 @@
+/*************************************************************************/
+/*  register_types.h                                                     */
+/*************************************************************************/
+/*                       This file is part of:                           */
+/*                           GODOT ENGINE                                */
+/*                      https://godotengine.org                          */
+/*************************************************************************/
+/* Copyright (c) 2007-2019 Juan Linietsky, Ariel Manzur.                 */
+/* Copyright (c) 2014-2019 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.                */
+/*************************************************************************/
+
+void register_assimp_types();
+void unregister_assimp_types();

+ 1 - 1
scene/3d/mesh_instance.cpp

@@ -96,7 +96,7 @@ void MeshInstance::_get_property_list(List<PropertyInfo> *p_list) const {
 	ls.sort();
 
 	for (List<String>::Element *E = ls.front(); E; E = E->next()) {
-		p_list->push_back(PropertyInfo(Variant::REAL, E->get(), PROPERTY_HINT_RANGE, "0,1,0.01"));
+		p_list->push_back(PropertyInfo(Variant::REAL, E->get(), PROPERTY_HINT_RANGE, "0,1,0.00001"));
 	}
 
 	if (mesh.is_valid()) {

+ 6 - 0
thirdparty/README.md

@@ -1,5 +1,11 @@
 # Third party libraries
 
+## assimp
+
+- Upstream: http://github.com/assimp/assimp
+- Version: git (d2b45377e4b09a1f43be95e45553afcc06b03f4b)
+- License: BSD-3-Clause
+
 
 ## b2d_convexdecomp
 

+ 183 - 0
thirdparty/assimp/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

+ 78 - 0
thirdparty/assimp/LICENSE

@@ -0,0 +1,78 @@
+Open Asset Import Library (assimp)
+
+Copyright (c) 2006-2016, 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.
+
+
+
+******************************************************************************
+
+AN EXCEPTION applies to all files in the ./test/models-nonbsd folder.
+These are 3d models for testing purposes, from various free sources
+on the internet. They are - unless otherwise stated - copyright of
+their respective creators, which may impose additional requirements
+on the use of their work. For any of these models, see
+<model-name>.source.txt for more legal information. Contact us if you
+are a copyright holder and believe that we credited you inproperly or
+if you don't want your files to appear in the repository.
+
+
+******************************************************************************
+
+Poly2Tri Copyright (c) 2009-2010, Poly2Tri Contributors
+http://code.google.com/p/poly2tri/
+
+All rights reserved.
+Redistribution and use 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 Poly2Tri nor the names of its contributors may be
+  used to endorse or promote products derived from this software without specific
+  prior written permission.
+
+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.

+ 980 - 0
thirdparty/assimp/assimp/config.h

@@ -0,0 +1,980 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2018, 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 config.h
+ *  @brief Defines constants for configurable properties for the library
+ *
+ *  Typically these properties are set via
+ *  #Assimp::Importer::SetPropertyFloat,
+ *  #Assimp::Importer::SetPropertyInteger or
+ *  #Assimp::Importer::SetPropertyString,
+ *  depending on the data type of a property. All properties have a
+ *  default value. See the doc for the mentioned methods for more details.
+ *
+ *  <br><br>
+ *  The corresponding functions for use with the plain-c API are:
+ *  #aiSetImportPropertyInteger,
+ *  #aiSetImportPropertyFloat,
+ *  #aiSetImportPropertyString
+ */
+#pragma once
+#ifndef AI_CONFIG_H_INC
+#define AI_CONFIG_H_INC
+
+// ###########################################################################
+// LIBRARY SETTINGS
+// General, global settings
+// ###########################################################################
+
+// ---------------------------------------------------------------------------
+/** @brief Enables time measurements.
+ *
+ *  If enabled, measures the time needed for each part of the loading
+ *  process (i.e. IO time, importing, postprocessing, ..) and dumps
+ *  these timings to the DefaultLogger. See the @link perf Performance
+ *  Page@endlink for more information on this topic.
+ *
+ * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_GLOB_MEASURE_TIME \
+	"GLOB_MEASURE_TIME"
+
+// ---------------------------------------------------------------------------
+/** @brief Global setting to disable generation of skeleton dummy meshes
+ *
+ * Skeleton dummy meshes are generated as a visualization aid in cases which
+ * the input data contains no geometry, but only animation data.
+ * Property data type: bool. Default value: false
+ */
+// ---------------------------------------------------------------------------
+#define AI_CONFIG_IMPORT_NO_SKELETON_MESHES \
+	"IMPORT_NO_SKELETON_MESHES"
+
+#if 0 // not implemented yet
+// ---------------------------------------------------------------------------
+/** @brief Set Assimp's multithreading policy.
+ *
+ * This setting is ignored if Assimp was built without boost.thread
+ * support (ASSIMP_BUILD_NO_THREADING, which is implied by ASSIMP_BUILD_BOOST_WORKAROUND).
+ * Possible values are: -1 to let Assimp decide what to do, 0 to disable
+ * multithreading entirely and any number larger than 0 to force a specific
+ * number of threads. Assimp is always free to ignore this settings, which is
+ * merely a hint. Usually, the default value (-1) will be fine. However, if
+ * Assimp is used concurrently from multiple user threads, it might be useful
+ * to limit each Importer instance to a specific number of cores.
+ *
+ * For more information, see the @link threading Threading page@endlink.
+ * Property type: int, default value: -1.
+ */
+#define AI_CONFIG_GLOB_MULTITHREADING \
+	"GLOB_MULTITHREADING"
+#endif
+
+// ###########################################################################
+// POST PROCESSING SETTINGS
+// Various stuff to fine-tune the behavior of a specific post processing step.
+// ###########################################################################
+
+// ---------------------------------------------------------------------------
+/** @brief Maximum bone count per mesh for the SplitbyBoneCount step.
+ *
+ * Meshes are split until the maximum number of bones is reached. The default
+ * value is AI_SBBC_DEFAULT_MAX_BONES, which may be altered at
+ * compile-time.
+ * Property data type: integer.
+ */
+// ---------------------------------------------------------------------------
+#define AI_CONFIG_PP_SBBC_MAX_BONES \
+	"PP_SBBC_MAX_BONES"
+
+// default limit for bone count
+#if (!defined AI_SBBC_DEFAULT_MAX_BONES)
+#define AI_SBBC_DEFAULT_MAX_BONES 60
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief  Specifies the maximum angle that may be between two vertex tangents
+ *         that their tangents and bi-tangents are smoothed.
+ *
+ * This applies to the CalcTangentSpace-Step. The angle is specified
+ * in degrees. The maximum value is 175.
+ * Property type: float. Default value: 45 degrees
+ */
+#define AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE \
+	"PP_CT_MAX_SMOOTHING_ANGLE"
+
+// ---------------------------------------------------------------------------
+/** @brief Source UV channel for tangent space computation.
+ *
+ * The specified channel must exist or an error will be raised.
+ * Property type: integer. Default value: 0
+ */
+// ---------------------------------------------------------------------------
+#define AI_CONFIG_PP_CT_TEXTURE_CHANNEL_INDEX \
+	"PP_CT_TEXTURE_CHANNEL_INDEX"
+
+// ---------------------------------------------------------------------------
+/** @brief  Specifies the maximum angle that may be between two face normals
+ *          at the same vertex position that their are smoothed together.
+ *
+ * Sometimes referred to as 'crease angle'.
+ * This applies to the GenSmoothNormals-Step. The angle is specified
+ * in degrees, so 180 is PI. The default value is 175 degrees (all vertex
+ * normals are smoothed). The maximum value is 175, too. Property type: float.
+ * Warning: setting this option may cause a severe loss of performance. The
+ * performance is unaffected if the #AI_CONFIG_FAVOUR_SPEED flag is set but
+ * the output quality may be reduced.
+ */
+#define AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE \
+	"PP_GSN_MAX_SMOOTHING_ANGLE"
+
+// ---------------------------------------------------------------------------
+/** @brief Sets the colormap (= palette) to be used to decode embedded
+ *         textures in MDL (Quake or 3DGS) files.
+ *
+ * This must be a valid path to a file. The file is 768 (256*3) bytes
+ * large and contains RGB triplets for each of the 256 palette entries.
+ * The default value is colormap.lmp. If the file is not found,
+ * a default palette (from Quake 1) is used.
+ * Property type: string.
+ */
+#define AI_CONFIG_IMPORT_MDL_COLORMAP \
+	"IMPORT_MDL_COLORMAP"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_RemoveRedundantMaterials step to
+ *  keep materials matching a name in a given list.
+ *
+ * This is a list of 1 to n strings, ' ' serves as delimiter character.
+ * Identifiers containing whitespaces must be enclosed in *single*
+ * quotation marks. For example:<tt>
+ * "keep-me and_me_to anotherMaterialToBeKept \'name with whitespace\'"</tt>.
+ * If a material matches on of these names, it will not be modified or
+ * removed by the postprocessing step nor will other materials be replaced
+ * by a reference to it. <br>
+ * This option might be useful if you are using some magic material names
+ * to pass additional semantics through the content pipeline. This ensures
+ * they won't be optimized away, but a general optimization is still
+ * performed for materials not contained in the list.
+ * Property type: String. Default value: n/a
+ * @note Linefeeds, tabs or carriage returns are treated as whitespace.
+ *   Material names are case sensitive.
+ */
+#define AI_CONFIG_PP_RRM_EXCLUDE_LIST \
+	"PP_RRM_EXCLUDE_LIST"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to
+ *  keep the scene hierarchy. Meshes are moved to worldspace, but
+ *  no optimization is performed (read: meshes with equal materials are not
+ *  joined. The total number of meshes won't change).
+ *
+ * This option could be of use for you if the scene hierarchy contains
+ * important additional information which you intend to parse.
+ * For rendering, you can still render all meshes in the scene without
+ * any transformations.
+ * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_PP_PTV_KEEP_HIERARCHY \
+	"PP_PTV_KEEP_HIERARCHY"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to normalize
+ *  all vertex components into the [-1,1] range. That is, a bounding box
+ *  for the whole scene is computed, the maximum component is taken and all
+ *  meshes are scaled appropriately (uniformly of course!).
+ *  This might be useful if you don't know the spatial dimension of the input
+ *  data*/
+#define AI_CONFIG_PP_PTV_NORMALIZE \
+	"PP_PTV_NORMALIZE"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to use
+ *  a users defined matrix as the scene root node transformation before
+ *  transforming vertices.
+ *  Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_PP_PTV_ADD_ROOT_TRANSFORMATION \
+	"PP_PTV_ADD_ROOT_TRANSFORMATION"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_PreTransformVertices step to use
+ *  a users defined matrix as the scene root node transformation before
+ *  transforming vertices. This property correspond to the 'a1' component
+ *  of the transformation matrix.
+ *  Property type: aiMatrix4x4.
+ */
+#define AI_CONFIG_PP_PTV_ROOT_TRANSFORMATION \
+	"PP_PTV_ROOT_TRANSFORMATION"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_FindDegenerates step to
+ *  remove degenerated primitives from the import - immediately.
+ *
+ * The default behaviour converts degenerated triangles to lines and
+ * degenerated lines to points. See the documentation to the
+ * #aiProcess_FindDegenerates step for a detailed example of the various ways
+ * to get rid of these lines and points if you don't want them.
+ * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_PP_FD_REMOVE \
+	"PP_FD_REMOVE"
+
+// ---------------------------------------------------------------------------
+/**
+ *  @brief  Configures the #aiProcess_FindDegenerates to check the area of a
+ *  trinagle to be greates than e-6. If this is not the case the triangle will
+ *  be removed if #AI_CONFIG_PP_FD_REMOVE is set to true.
+ */
+#define AI_CONFIG_PP_FD_CHECKAREA \
+	"PP_FD_CHECKAREA"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the #aiProcess_OptimizeGraph step to preserve nodes
+ * matching a name in a given list.
+ *
+ * This is a list of 1 to n strings, ' ' serves as delimiter character.
+ * Identifiers containing whitespaces must be enclosed in *single*
+ * quotation marks. For example:<tt>
+ * "keep-me and_me_to anotherNodeToBeKept \'name with whitespace\'"</tt>.
+ * If a node matches on of these names, it will not be modified or
+ * removed by the postprocessing step.<br>
+ * This option might be useful if you are using some magic node names
+ * to pass additional semantics through the content pipeline. This ensures
+ * they won't be optimized away, but a general optimization is still
+ * performed for nodes not contained in the list.
+ * Property type: String. Default value: n/a
+ * @note Linefeeds, tabs or carriage returns are treated as whitespace.
+ *   Node names are case sensitive.
+ */
+#define AI_CONFIG_PP_OG_EXCLUDE_LIST \
+	"PP_OG_EXCLUDE_LIST"
+
+// ---------------------------------------------------------------------------
+/** @brief  Set the maximum number of triangles in a mesh.
+ *
+ * This is used by the "SplitLargeMeshes" PostProcess-Step to determine
+ * whether a mesh must be split or not.
+ * @note The default value is AI_SLM_DEFAULT_MAX_TRIANGLES
+ * Property type: integer.
+ */
+#define AI_CONFIG_PP_SLM_TRIANGLE_LIMIT \
+	"PP_SLM_TRIANGLE_LIMIT"
+
+// default value for AI_CONFIG_PP_SLM_TRIANGLE_LIMIT
+#if (!defined AI_SLM_DEFAULT_MAX_TRIANGLES)
+#define AI_SLM_DEFAULT_MAX_TRIANGLES 1000000
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief  Set the maximum number of vertices in a mesh.
+ *
+ * This is used by the "SplitLargeMeshes" PostProcess-Step to determine
+ * whether a mesh must be split or not.
+ * @note The default value is AI_SLM_DEFAULT_MAX_VERTICES
+ * Property type: integer.
+ */
+#define AI_CONFIG_PP_SLM_VERTEX_LIMIT \
+	"PP_SLM_VERTEX_LIMIT"
+
+// default value for AI_CONFIG_PP_SLM_VERTEX_LIMIT
+#if (!defined AI_SLM_DEFAULT_MAX_VERTICES)
+#define AI_SLM_DEFAULT_MAX_VERTICES 1000000
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Set the maximum number of bones affecting a single vertex
+ *
+ * This is used by the #aiProcess_LimitBoneWeights PostProcess-Step.
+ * @note The default value is AI_LMW_MAX_WEIGHTS
+ * Property type: integer.*/
+#define AI_CONFIG_PP_LBW_MAX_WEIGHTS \
+	"PP_LBW_MAX_WEIGHTS"
+
+// default value for AI_CONFIG_PP_LBW_MAX_WEIGHTS
+#if (!defined AI_LMW_MAX_WEIGHTS)
+#define AI_LMW_MAX_WEIGHTS 0x4
+#endif // !! AI_LMW_MAX_WEIGHTS
+
+// ---------------------------------------------------------------------------
+/** @brief Lower the deboning threshold in order to remove more bones.
+ *
+ * This is used by the #aiProcess_Debone PostProcess-Step.
+ * @note The default value is AI_DEBONE_THRESHOLD
+ * Property type: float.*/
+#define AI_CONFIG_PP_DB_THRESHOLD \
+	"PP_DB_THRESHOLD"
+
+// default value for AI_CONFIG_PP_LBW_MAX_WEIGHTS
+#if (!defined AI_DEBONE_THRESHOLD)
+#define AI_DEBONE_THRESHOLD 1.0f
+#endif // !! AI_DEBONE_THRESHOLD
+
+// ---------------------------------------------------------------------------
+/** @brief Require all bones qualify for deboning before removing any
+ *
+ * This is used by the #aiProcess_Debone PostProcess-Step.
+ * @note The default value is 0
+ * Property type: bool.*/
+#define AI_CONFIG_PP_DB_ALL_OR_NONE \
+	"PP_DB_ALL_OR_NONE"
+
+/** @brief Default value for the #AI_CONFIG_PP_ICL_PTCACHE_SIZE property
+ */
+#ifndef PP_ICL_PTCACHE_SIZE
+#define PP_ICL_PTCACHE_SIZE 12
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Set the size of the post-transform vertex cache to optimize the
+ *    vertices for. This configures the #aiProcess_ImproveCacheLocality step.
+ *
+ * The size is given in vertices. Of course you can't know how the vertex
+ * format will exactly look like after the import returns, but you can still
+ * guess what your meshes will probably have.
+ * @note The default value is #PP_ICL_PTCACHE_SIZE. That results in slight
+ * performance improvements for most nVidia/AMD cards since 2002.
+ * Property type: integer.
+ */
+#define AI_CONFIG_PP_ICL_PTCACHE_SIZE "PP_ICL_PTCACHE_SIZE"
+
+// ---------------------------------------------------------------------------
+/** @brief Enumerates components of the aiScene and aiMesh data structures
+ *  that can be excluded from the import using the #aiProcess_RemoveComponent step.
+ *
+ *  See the documentation to #aiProcess_RemoveComponent for more details.
+ */
+enum aiComponent {
+/** Normal vectors */
+#ifdef SWIG
+    aiComponent_NORMALS = 0x2,
+#else
+	aiComponent_NORMALS = 0x2u,
+#endif
+
+/** Tangents and bitangents go always together ... */
+#ifdef SWIG
+    aiComponent_TANGENTS_AND_BITANGENTS = 0x4,
+#else
+	aiComponent_TANGENTS_AND_BITANGENTS = 0x4u,
+#endif
+
+	/** ALL color sets
+     * Use aiComponent_COLORn(N) to specify the N'th set */
+	aiComponent_COLORS = 0x8,
+
+	/** ALL texture UV sets
+     * aiComponent_TEXCOORDn(N) to specify the N'th set  */
+	aiComponent_TEXCOORDS = 0x10,
+
+	/** Removes all bone weights from all meshes.
+     * The scenegraph nodes corresponding to the bones are NOT removed.
+     * use the #aiProcess_OptimizeGraph step to do this */
+	aiComponent_BONEWEIGHTS = 0x20,
+
+	/** Removes all node animations (aiScene::mAnimations).
+     * The corresponding scenegraph nodes are NOT removed.
+     * use the #aiProcess_OptimizeGraph step to do this */
+	aiComponent_ANIMATIONS = 0x40,
+
+	/** Removes all embedded textures (aiScene::mTextures) */
+	aiComponent_TEXTURES = 0x80,
+
+	/** Removes all light sources (aiScene::mLights).
+     * The corresponding scenegraph nodes are NOT removed.
+     * use the #aiProcess_OptimizeGraph step to do this */
+	aiComponent_LIGHTS = 0x100,
+
+	/** Removes all cameras (aiScene::mCameras).
+     * The corresponding scenegraph nodes are NOT removed.
+     * use the #aiProcess_OptimizeGraph step to do this */
+	aiComponent_CAMERAS = 0x200,
+
+	/** Removes all meshes (aiScene::mMeshes). */
+	aiComponent_MESHES = 0x400,
+
+	/** Removes all materials. One default material will
+     * be generated, so aiScene::mNumMaterials will be 1. */
+	aiComponent_MATERIALS = 0x800,
+
+/** This value is not used. It is just there to force the
+     *  compiler to map this enum to a 32 Bit integer. */
+#ifndef SWIG
+	_aiComponent_Force32Bit = 0x9fffffff
+#endif
+};
+
+// Remove a specific color channel 'n'
+#define aiComponent_COLORSn(n) (1u << (n + 20u))
+
+// Remove a specific UV channel 'n'
+#define aiComponent_TEXCOORDSn(n) (1u << (n + 25u))
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_RemoveComponent step:
+ *  Specifies the parts of the data structure to be removed.
+ *
+ * See the documentation to this step for further details. The property
+ * is expected to be an integer, a bitwise combination of the
+ * #aiComponent flags defined above in this header. The default
+ * value is 0. Important: if no valid mesh is remaining after the
+ * step has been executed (e.g you thought it was funny to specify ALL
+ * of the flags defined above) the import FAILS. Mainly because there is
+ * no data to work on anymore ...
+ */
+#define AI_CONFIG_PP_RVC_FLAGS \
+	"PP_RVC_FLAGS"
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_SortByPType step:
+ *  Specifies which primitive types are removed by the step.
+ *
+ *  This is a bitwise combination of the aiPrimitiveType flags.
+ *  Specifying all of them is illegal, of course. A typical use would
+ *  be to exclude all line and point meshes from the import. This
+ *  is an integer property, its default value is 0.
+ */
+#define AI_CONFIG_PP_SBP_REMOVE \
+	"PP_SBP_REMOVE"
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_FindInvalidData step:
+ *  Specifies the floating-point accuracy for animation values. The step
+ *  checks for animation tracks where all frame values are absolutely equal
+ *  and removes them. This tweakable controls the epsilon for floating-point
+ *  comparisons - two keys are considered equal if the invariant
+ *  abs(n0-n1)>epsilon holds true for all vector respectively quaternion
+ *  components. The default value is 0.f - comparisons are exact then.
+ */
+#define AI_CONFIG_PP_FID_ANIM_ACCURACY \
+	"PP_FID_ANIM_ACCURACY"
+
+// TransformUVCoords evaluates UV scalings
+#define AI_UVTRAFO_SCALING 0x1
+
+// TransformUVCoords evaluates UV rotations
+#define AI_UVTRAFO_ROTATION 0x2
+
+// TransformUVCoords evaluates UV translation
+#define AI_UVTRAFO_TRANSLATION 0x4
+
+// Everything baked together -> default value
+#define AI_UVTRAFO_ALL (AI_UVTRAFO_SCALING | AI_UVTRAFO_ROTATION | AI_UVTRAFO_TRANSLATION)
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_FindInvalidData step:
+ *  Set to true to ignore texture coordinates. This may be useful if you have
+ *  to assign different kind of textures like one for the summer or one for the winter.
+ */
+#define AI_CONFIG_PP_FID_IGNORE_TEXTURECOORDS \
+	"PP_FID_IGNORE_TEXTURECOORDS"
+
+// ---------------------------------------------------------------------------
+/** @brief Input parameter to the #aiProcess_TransformUVCoords step:
+ *  Specifies which UV transformations are evaluated.
+ *
+ *  This is a bitwise combination of the AI_UVTRAFO_XXX flags (integer
+ *  property, of course). By default all transformations are enabled
+ * (AI_UVTRAFO_ALL).
+ */
+#define AI_CONFIG_PP_TUV_EVALUATE \
+	"PP_TUV_EVALUATE"
+
+// ---------------------------------------------------------------------------
+/** @brief A hint to assimp to favour speed against import quality.
+ *
+ * Enabling this option may result in faster loading, but it needn't.
+ * It represents just a hint to loaders and post-processing steps to use
+ * faster code paths, if possible.
+ * This property is expected to be an integer, != 0 stands for true.
+ * The default value is 0.
+ */
+#define AI_CONFIG_FAVOUR_SPEED \
+	"FAVOUR_SPEED"
+
+// ###########################################################################
+// IMPORTER SETTINGS
+// Various stuff to fine-tune the behaviour of specific importer plugins.
+// ###########################################################################
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will merge all geometry layers present
+ *    in the source file or take only the first.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS \
+	"IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read all materials present in the
+ *    source file or take only the referenced materials.
+ *
+ * This is void unless IMPORT_FBX_READ_MATERIALS=1.
+ *
+ * The default value is false (0)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS \
+	"IMPORT_FBX_READ_ALL_MATERIALS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read materials.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_MATERIALS \
+	"IMPORT_FBX_READ_MATERIALS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read embedded textures.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_TEXTURES \
+	"IMPORT_FBX_READ_TEXTURES"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read cameras.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_CAMERAS \
+	"IMPORT_FBX_READ_CAMERAS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read light sources.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_LIGHTS \
+	"IMPORT_FBX_READ_LIGHTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will read animations.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS \
+	"IMPORT_FBX_READ_ANIMATIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will act in strict mode in which only
+ *    FBX 2013 is supported and any other sub formats are rejected. FBX 2013
+ *    is the primary target for the importer, so this format is best
+ *    supported and well-tested.
+ *
+ * The default value is false (0)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_STRICT_MODE \
+	"IMPORT_FBX_STRICT_MODE"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will preserve pivot points for
+ *    transformations (as extra nodes). If set to false, pivots and offsets
+ *    will be evaluated whenever possible.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS \
+	"IMPORT_FBX_PRESERVE_PIVOTS"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the importer will drop empty animation curves or
+ *    animation curves which match the bind pose transformation over their
+ *    entire defined range.
+ *
+ * The default value is true (1)
+ * Property type: bool
+ */
+#define AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES \
+	"IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES"
+
+// ---------------------------------------------------------------------------
+/** @brief Set whether the fbx importer will use the legacy embedded texture naming.
+*
+* The default value is false (0)
+* Property type: bool
+*/
+#define AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING \
+	"AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING"
+
+// ---------------------------------------------------------------------------
+/** @brief  Set the vertex animation keyframe to be imported
+ *
+ * ASSIMP does not support vertex keyframes (only bone animation is supported).
+ * The library reads only one frame of models with vertex animations.
+ * By default this is the first frame.
+ * \note The default value is 0. This option applies to all importers.
+ *   However, it is also possible to override the global setting
+ *   for a specific loader. You can use the AI_CONFIG_IMPORT_XXX_KEYFRAME
+ *   options (where XXX is a placeholder for the file format for which you
+ *   want to override the global setting).
+ * Property type: integer.
+ */
+#define AI_CONFIG_IMPORT_GLOBAL_KEYFRAME "IMPORT_GLOBAL_KEYFRAME"
+
+#define AI_CONFIG_IMPORT_MD3_KEYFRAME "IMPORT_MD3_KEYFRAME"
+#define AI_CONFIG_IMPORT_MD2_KEYFRAME "IMPORT_MD2_KEYFRAME"
+#define AI_CONFIG_IMPORT_MDL_KEYFRAME "IMPORT_MDL_KEYFRAME"
+#define AI_CONFIG_IMPORT_MDC_KEYFRAME "IMPORT_MDC_KEYFRAME"
+#define AI_CONFIG_IMPORT_SMD_KEYFRAME "IMPORT_SMD_KEYFRAME"
+#define AI_CONFIG_IMPORT_UNREAL_KEYFRAME "IMPORT_UNREAL_KEYFRAME"
+
+// ---------------------------------------------------------------------------
+/** Smd load multiple animations
+ *
+ *  Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_SMD_LOAD_ANIMATION_LIST "IMPORT_SMD_LOAD_ANIMATION_LIST"
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures the AC loader to collect all surfaces which have the
+ *    "Backface cull" flag set in separate meshes.
+ *
+ *  Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_AC_SEPARATE_BFCULL \
+	"IMPORT_AC_SEPARATE_BFCULL"
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures whether the AC loader evaluates subdivision surfaces (
+ *  indicated by the presence of the 'subdiv' attribute in the file). By
+ *  default, Assimp performs the subdivision using the standard
+ *  Catmull-Clark algorithm
+ *
+ * * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_AC_EVAL_SUBDIVISION \
+	"IMPORT_AC_EVAL_SUBDIVISION"
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures the UNREAL 3D loader to separate faces with different
+ *    surface flags (e.g. two-sided vs. single-sided).
+ *
+ * * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_UNREAL_HANDLE_FLAGS \
+	"UNREAL_HANDLE_FLAGS"
+
+// ---------------------------------------------------------------------------
+/** @brief Configures the terragen import plugin to compute uv's for
+ *  terrains, if not given. Furthermore a default texture is assigned.
+ *
+ * UV coordinates for terrains are so simple to compute that you'll usually
+ * want to compute them on your own, if you need them. This option is intended
+ * for model viewers which want to offer an easy way to apply textures to
+ * terrains.
+ * * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_TER_MAKE_UVS \
+	"IMPORT_TER_MAKE_UVS"
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures the ASE loader to always reconstruct normal vectors
+ *  basing on the smoothing groups loaded from the file.
+ *
+ * Some ASE files have carry invalid normals, other don't.
+ * * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_ASE_RECONSTRUCT_NORMALS \
+	"IMPORT_ASE_RECONSTRUCT_NORMALS"
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures the M3D loader to detect and process multi-part
+ *    Quake player models.
+ *
+ * These models usually consist of 3 files, lower.md3, upper.md3 and
+ * head.md3. If this property is set to true, Assimp will try to load and
+ * combine all three files if one of them is loaded.
+ * Property type: bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_MD3_HANDLE_MULTIPART \
+	"IMPORT_MD3_HANDLE_MULTIPART"
+
+// ---------------------------------------------------------------------------
+/** @brief  Tells the MD3 loader which skin files to load.
+ *
+ * When loading MD3 files, Assimp checks whether a file
+ * [md3_file_name]_[skin_name].skin is existing. These files are used by
+ * Quake III to be able to assign different skins (e.g. red and blue team)
+ * to models. 'default', 'red', 'blue' are typical skin names.
+ * Property type: String. Default value: "default".
+ */
+#define AI_CONFIG_IMPORT_MD3_SKIN_NAME \
+	"IMPORT_MD3_SKIN_NAME"
+
+// ---------------------------------------------------------------------------
+/** @brief  Specify the Quake 3 shader file to be used for a particular
+ *  MD3 file. This can also be a search path.
+ *
+ * By default Assimp's behaviour is as follows: If a MD3 file
+ * <tt>any_path/models/any_q3_subdir/model_name/file_name.md3</tt> is
+ * loaded, the library tries to locate the corresponding shader file in
+ * <tt>any_path/scripts/model_name.shader</tt>. This property overrides this
+ * behaviour. It can either specify a full path to the shader to be loaded
+ * or alternatively the path (relative or absolute) to the directory where
+ * the shaders for all MD3s to be loaded reside. Assimp attempts to open
+ * <tt>IMPORT_MD3_SHADER_SRC/model_name.shader</tt> first, <tt>IMPORT_MD3_SHADER_SRC/file_name.shader</tt>
+ * is the fallback file. Note that IMPORT_MD3_SHADER_SRC should have a terminal (back)slash.
+ * Property type: String. Default value: n/a.
+ */
+#define AI_CONFIG_IMPORT_MD3_SHADER_SRC \
+	"IMPORT_MD3_SHADER_SRC"
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures the LWO loader to load just one layer from the model.
+ *
+ * LWO files consist of layers and in some cases it could be useful to load
+ * only one of them. This property can be either a string - which specifies
+ * the name of the layer - or an integer - the index of the layer. If the
+ * property is not set the whole LWO model is loaded. Loading fails if the
+ * requested layer is not available. The layer index is zero-based and the
+ * layer name may not be empty.<br>
+ * Property type: Integer. Default value: all layers are loaded.
+ */
+#define AI_CONFIG_IMPORT_LWO_ONE_LAYER_ONLY \
+	"IMPORT_LWO_ONE_LAYER_ONLY"
+
+// ---------------------------------------------------------------------------
+/** @brief  Configures the MD5 loader to not load the MD5ANIM file for
+ *  a MD5MESH file automatically.
+ *
+ * The default strategy is to look for a file with the same name but the
+ * MD5ANIM extension in the same directory. If it is found, it is loaded
+ * and combined with the MD5MESH file. This configuration option can be
+ * used to disable this behaviour.
+ *
+ * * Property type: bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_MD5_NO_ANIM_AUTOLOAD \
+	"IMPORT_MD5_NO_ANIM_AUTOLOAD"
+
+// ---------------------------------------------------------------------------
+/** @brief Defines the begin of the time range for which the LWS loader
+ *    evaluates animations and computes aiNodeAnim's.
+ *
+ * Assimp provides full conversion of LightWave's envelope system, including
+ * pre and post conditions. The loader computes linearly subsampled animation
+ * chanels with the frame rate given in the LWS file. This property defines
+ * the start time. Note: animation channels are only generated if a node
+ * has at least one envelope with more tan one key assigned. This property.
+ * is given in frames, '0' is the first frame. By default, if this property
+ * is not set, the importer takes the animation start from the input LWS
+ * file ('FirstFrame' line)<br>
+ * Property type: Integer. Default value: taken from file.
+ *
+ * @see AI_CONFIG_IMPORT_LWS_ANIM_END - end of the imported time range
+ */
+#define AI_CONFIG_IMPORT_LWS_ANIM_START \
+	"IMPORT_LWS_ANIM_START"
+#define AI_CONFIG_IMPORT_LWS_ANIM_END \
+	"IMPORT_LWS_ANIM_END"
+
+// ---------------------------------------------------------------------------
+/** @brief Defines the output frame rate of the IRR loader.
+ *
+ * IRR animations are difficult to convert for Assimp and there will
+ * always be a loss of quality. This setting defines how many keys per second
+ * are returned by the converter.<br>
+ * Property type: integer. Default value: 100
+ */
+#define AI_CONFIG_IMPORT_IRR_ANIM_FPS \
+	"IMPORT_IRR_ANIM_FPS"
+
+// ---------------------------------------------------------------------------
+/** @brief Ogre Importer will try to find referenced materials from this file.
+ *
+ * Ogre meshes reference with material names, this does not tell Assimp the file
+ * where it is located in. Assimp will try to find the source file in the following
+ * order: <material-name>.material, <mesh-filename-base>.material and
+ * lastly the material name defined by this config property.
+ * <br>
+ * Property type: String. Default value: Scene.material.
+ */
+#define AI_CONFIG_IMPORT_OGRE_MATERIAL_FILE \
+	"IMPORT_OGRE_MATERIAL_FILE"
+
+// ---------------------------------------------------------------------------
+/** @brief Ogre Importer detect the texture usage from its filename.
+ *
+ * Ogre material texture units do not define texture type, the textures usage
+ * depends on the used shader or Ogre's fixed pipeline. If this config property
+ * is true Assimp will try to detect the type from the textures filename postfix:
+ * _n, _nrm, _nrml, _normal, _normals and _normalmap for normal map, _s, _spec,
+ * _specular and _specularmap for specular map, _l, _light, _lightmap, _occ
+ * and _occlusion for light map, _disp and _displacement for displacement map.
+ * The matching is case insensitive. Post fix is taken between the last
+ * underscore and the last period.
+ * Default behavior is to detect type from lower cased texture unit name by
+ * matching against: normalmap, specularmap, lightmap and displacementmap.
+ * For both cases if no match is found aiTextureType_DIFFUSE is used.
+ * <br>
+ * Property type: Bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME \
+	"IMPORT_OGRE_TEXTURETYPE_FROM_FILENAME"
+
+/** @brief Specifies whether the Android JNI asset extraction is supported.
+  *
+  * Turn on this option if you want to manage assets in native
+  * Android application without having to keep the internal directory and asset
+  * manager pointer.
+  */
+#define AI_CONFIG_ANDROID_JNI_ASSIMP_MANAGER_SUPPORT "AI_CONFIG_ANDROID_JNI_ASSIMP_MANAGER_SUPPORT"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the IFC loader skips over IfcSpace elements.
+ *
+ * IfcSpace elements (and their geometric representations) are used to
+ * represent, well, free space in a building storey.<br>
+ * Property type: Bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS "IMPORT_IFC_SKIP_SPACE_REPRESENTATIONS"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the IFC loader will use its own, custom triangulation
+ *   algorithm to triangulate wall and floor meshes.
+ *
+ * If this property is set to false, walls will be either triangulated by
+ * #aiProcess_Triangulate or will be passed through as huge polygons with
+ * faked holes (i.e. holes that are connected with the outer boundary using
+ * a dummy edge). It is highly recommended to set this property to true
+ * if you want triangulated data because #aiProcess_Triangulate is known to
+ * have problems with the kind of polygons that the IFC loader spits out for
+ * complicated meshes.
+ * Property type: Bool. Default value: true.
+ */
+#define AI_CONFIG_IMPORT_IFC_CUSTOM_TRIANGULATION "IMPORT_IFC_CUSTOM_TRIANGULATION"
+
+// ---------------------------------------------------------------------------
+/** @brief  Set the tessellation conic angle for IFC smoothing curves.
+ *
+ * This is used by the IFC importer to determine the tessellation parameter
+ * for smoothing curves.
+ * @note The default value is AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE and the
+ * accepted values are in range [5.0, 120.0].
+ * Property type: Float.
+ */
+#define AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE "IMPORT_IFC_SMOOTHING_ANGLE"
+
+// default value for AI_CONFIG_IMPORT_IFC_SMOOTHING_ANGLE
+#if (!defined AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE)
+#define AI_IMPORT_IFC_DEFAULT_SMOOTHING_ANGLE 10.0f
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief  Set the tessellation for IFC cylindrical shapes.
+ *
+ * This is used by the IFC importer to determine the tessellation parameter
+ * for cylindrical shapes, i.e. the number of segments used to approximate a circle.
+ * @note The default value is AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION and the
+ * accepted values are in range [3, 180].
+ * Property type: Integer.
+ */
+#define AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION "IMPORT_IFC_CYLINDRICAL_TESSELLATION"
+
+// default value for AI_CONFIG_IMPORT_IFC_CYLINDRICAL_TESSELLATION
+#if (!defined AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION)
+#define AI_IMPORT_IFC_DEFAULT_CYLINDRICAL_TESSELLATION 32
+#endif
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the Collada loader will ignore the provided up direction.
+ *
+ * If this property is set to true, the up direction provided in the file header will
+ * be ignored and the file will be loaded as is.
+ * Property type: Bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_COLLADA_IGNORE_UP_DIRECTION "IMPORT_COLLADA_IGNORE_UP_DIRECTION"
+
+// ---------------------------------------------------------------------------
+/** @brief Specifies whether the Collada loader should use Collada names as node names.
+ *
+ * If this property is set to true, the Collada names will be used as the
+ * node name. The default is to use the id tag (resp. sid tag, if no id tag is present)
+ * instead.
+ * Property type: Bool. Default value: false.
+ */
+#define AI_CONFIG_IMPORT_COLLADA_USE_COLLADA_NAMES "IMPORT_COLLADA_USE_COLLADA_NAMES"
+
+// ---------- All the Export defines ------------
+
+/** @brief Specifies the xfile use double for real values of float
+ *
+ * Property type: Bool. Default value: false.
+ */
+
+#define AI_CONFIG_EXPORT_XFILE_64BIT "EXPORT_XFILE_64BIT"
+
+/**
+ *
+ */
+#define AI_CONFIG_EXPORT_POINT_CLOUDS "EXPORT_POINT_CLOUDS"
+
+/**
+ *  @brief  Specifies a gobal key factor for scale, float value
+ */
+#define AI_CONFIG_GLOBAL_SCALE_FACTOR_KEY "GLOBAL_SCALE_FACTOR"
+
+#if (!defined AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT)
+#define AI_CONFIG_GLOBAL_SCALE_FACTOR_DEFAULT 1.0f
+#endif // !! AI_DEBONE_THRESHOLD
+
+// ---------- All the Build/Compile-time defines ------------
+
+/** @brief Specifies if double precision is supported inside assimp
+ *
+ * Property type: Bool. Default value: undefined.
+ */
+
+/* #cmakedefine ASSIMP_DOUBLE_PRECISION 1 */
+
+#endif // !! AI_CONFIG_H_INC

+ 616 - 0
thirdparty/assimp/code/BaseImporter.cpp

@@ -0,0 +1,616 @@
+/*
+---------------------------------------------------------------------------
+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  BaseImporter.cpp
+ *  @brief Implementation of BaseImporter
+ */
+
+#include <assimp/BaseImporter.h>
+#include <assimp/ParsingUtils.h>
+#include "FileSystemFilter.h"
+#include "Importer.h"
+#include <assimp/ByteSwapper.h>
+#include <assimp/scene.h>
+#include <assimp/Importer.hpp>
+#include <assimp/postprocess.h>
+#include <assimp/importerdesc.h>
+
+#include <ios>
+#include <list>
+#include <memory>
+#include <sstream>
+#include <cctype>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+BaseImporter::BaseImporter() AI_NO_EXCEPT
+: m_progress() {
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+BaseImporter::~BaseImporter() {
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file and returns the imported data.
+aiScene* BaseImporter::ReadFile(const Importer* pImp, const std::string& pFile, IOSystem* pIOHandler) {
+    m_progress = pImp->GetProgressHandler();
+    if (nullptr == m_progress) {
+        return nullptr;
+    }
+
+    ai_assert(m_progress);
+
+    // Gather configuration properties for this run
+    SetupProperties( pImp );
+
+    // Construct a file system filter to improve our success ratio at reading external files
+    FileSystemFilter filter(pFile,pIOHandler);
+
+    // create a scene object to hold the data
+    std::unique_ptr<aiScene> sc(new aiScene());
+
+    // dispatch importing
+    try
+    {
+        InternReadFile( pFile, sc.get(), &filter);
+
+    } catch( const std::exception& err )    {
+        // extract error description
+        m_ErrorText = err.what();
+        ASSIMP_LOG_ERROR(m_ErrorText);
+        return nullptr;
+    }
+
+    // return what we gathered from the import.
+    return sc.release();
+}
+
+// ------------------------------------------------------------------------------------------------
+void BaseImporter::SetupProperties(const Importer* /*pImp*/)
+{
+    // the default implementation does nothing
+}
+
+// ------------------------------------------------------------------------------------------------
+void BaseImporter::GetExtensionList(std::set<std::string>& extensions) {
+    const aiImporterDesc* desc = GetInfo();
+    ai_assert(desc != nullptr);
+
+    const char* ext = desc->mFileExtensions;
+    ai_assert(ext != nullptr );
+
+    const char* last = ext;
+    do {
+        if (!*ext || *ext == ' ') {
+            extensions.insert(std::string(last,ext-last));
+            ai_assert(ext-last > 0);
+            last = ext;
+            while(*last == ' ') {
+                ++last;
+            }
+        }
+    }
+    while(*ext++);
+}
+
+// ------------------------------------------------------------------------------------------------
+/*static*/ bool BaseImporter::SearchFileHeaderForToken( IOSystem* pIOHandler,
+    const std::string&  pFile,
+    const char**        tokens,
+    unsigned int        numTokens,
+    unsigned int        searchBytes /* = 200 */,
+    bool                tokensSol /* false */,
+    bool                noAlphaBeforeTokens /* false */)
+{
+    ai_assert( nullptr != tokens );
+    ai_assert( 0 != numTokens );
+    ai_assert( 0 != searchBytes);
+
+    if ( nullptr == pIOHandler ) {
+        return false;
+    }
+
+    std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile));
+    if (pStream.get() ) {
+        // read 200 characters from the file
+        std::unique_ptr<char[]> _buffer (new char[searchBytes+1 /* for the '\0' */]);
+        char *buffer( _buffer.get() );
+        const size_t read( pStream->Read(buffer,1,searchBytes) );
+        if( 0 == read ) {
+            return false;
+        }
+
+        for( size_t i = 0; i < read; ++i ) {
+            buffer[ i ] = static_cast<char>( ::tolower( buffer[ i ] ) );
+        }
+
+        // It is not a proper handling of unicode files here ...
+        // ehm ... but it works in most cases.
+        char* cur = buffer,*cur2 = buffer,*end = &buffer[read];
+        while (cur != end)  {
+            if( *cur ) {
+                *cur2++ = *cur;
+            }
+            ++cur;
+        }
+        *cur2 = '\0';
+
+        std::string token;
+        for (unsigned int i = 0; i < numTokens; ++i ) {
+            ai_assert( nullptr != tokens[i] );
+            const size_t len( strlen( tokens[ i ] ) );
+            token.clear();
+            const char *ptr( tokens[ i ] );
+            for ( size_t tokIdx = 0; tokIdx < len; ++tokIdx ) {
+                token.push_back( static_cast<char>( tolower( *ptr ) ) );
+                ++ptr;
+            }
+            const char* r = strstr( buffer, token.c_str() );
+            if( !r ) {
+                continue;
+            }
+            // We need to make sure that we didn't accidentially identify the end of another token as our token,
+            // e.g. in a previous version the "gltf " present in some gltf files was detected as "f "
+            if (noAlphaBeforeTokens && (r != buffer && isalpha(r[-1]))) {
+                continue;
+            }
+            // We got a match, either we don't care where it is, or it happens to
+            // be in the beginning of the file / line
+            if (!tokensSol || r == buffer || r[-1] == '\r' || r[-1] == '\n') {
+                ASSIMP_LOG_DEBUG_F( "Found positive match for header keyword: ", tokens[i] );
+                return true;
+            }
+        }
+    }
+
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Simple check for file extension
+/*static*/ bool BaseImporter::SimpleExtensionCheck (const std::string& pFile,
+    const char* ext0,
+    const char* ext1,
+    const char* ext2)
+{
+    std::string::size_type pos = pFile.find_last_of('.');
+
+    // no file extension - can't read
+    if( pos == std::string::npos)
+        return false;
+
+    const char* ext_real = & pFile[ pos+1 ];
+    if( !ASSIMP_stricmp(ext_real,ext0) )
+        return true;
+
+    // check for other, optional, file extensions
+    if (ext1 && !ASSIMP_stricmp(ext_real,ext1))
+        return true;
+
+    if (ext2 && !ASSIMP_stricmp(ext_real,ext2))
+        return true;
+
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get file extension from path
+std::string BaseImporter::GetExtension( const std::string& file ) {
+    std::string::size_type pos = file.find_last_of('.');
+
+    // no file extension at all
+    if (pos == std::string::npos) {
+        return "";
+    }
+
+
+    // thanks to Andy Maloney for the hint
+    std::string ret = file.substr( pos + 1 );
+    std::transform( ret.begin(), ret.end(), ret.begin(), ToLower<char>);
+
+    return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check for magic bytes at the beginning of the file.
+/* static */ bool BaseImporter::CheckMagicToken(IOSystem* pIOHandler, const std::string& pFile,
+    const void* _magic, unsigned int num, unsigned int offset, unsigned int size)
+{
+    ai_assert( size <= 16 );
+    ai_assert( _magic );
+
+    if (!pIOHandler) {
+        return false;
+    }
+    union {
+        const char* magic;
+        const uint16_t* magic_u16;
+        const uint32_t* magic_u32;
+    };
+    magic = reinterpret_cast<const char*>(_magic);
+    std::unique_ptr<IOStream> pStream (pIOHandler->Open(pFile));
+    if (pStream.get() ) {
+
+        // skip to offset
+        pStream->Seek(offset,aiOrigin_SET);
+
+        // read 'size' characters from the file
+        union {
+            char data[16];
+            uint16_t data_u16[8];
+            uint32_t data_u32[4];
+        };
+        if(size != pStream->Read(data,1,size)) {
+            return false;
+        }
+
+        for (unsigned int i = 0; i < num; ++i) {
+            // also check against big endian versions of tokens with size 2,4
+            // that's just for convenience, the chance that we cause conflicts
+            // is quite low and it can save some lines and prevent nasty bugs
+            if (2 == size) {
+                uint16_t rev = *magic_u16;
+                ByteSwap::Swap(&rev);
+                if (data_u16[0] == *magic_u16 || data_u16[0] == rev) {
+                    return true;
+                }
+            }
+            else if (4 == size) {
+                uint32_t rev = *magic_u32;
+                ByteSwap::Swap(&rev);
+                if (data_u32[0] == *magic_u32 || data_u32[0] == rev) {
+                    return true;
+                }
+            }
+            else {
+                // any length ... just compare
+                if(!memcmp(magic,data,size)) {
+                    return true;
+                }
+            }
+            magic += size;
+        }
+    }
+    return false;
+}
+
+#include "../contrib/utf8cpp/source/utf8.h"
+
+// ------------------------------------------------------------------------------------------------
+// Convert to UTF8 data
+void BaseImporter::ConvertToUTF8(std::vector<char>& data)
+{
+    //ConversionResult result;
+    if(data.size() < 8) {
+        throw DeadlyImportError("File is too small");
+    }
+
+    // UTF 8 with BOM
+    if((uint8_t)data[0] == 0xEF && (uint8_t)data[1] == 0xBB && (uint8_t)data[2] == 0xBF) {
+        ASSIMP_LOG_DEBUG("Found UTF-8 BOM ...");
+
+        std::copy(data.begin()+3,data.end(),data.begin());
+        data.resize(data.size()-3);
+        return;
+    }
+    
+    
+    // UTF 32 BE with BOM
+    if(*((uint32_t*)&data.front()) == 0xFFFE0000) {
+
+        // swap the endianness ..
+        for(uint32_t* p = (uint32_t*)&data.front(), *end = (uint32_t*)&data.back(); p <= end; ++p) {
+            AI_SWAP4P(p);
+        }
+    }
+
+    // UTF 32 LE with BOM
+    if(*((uint32_t*)&data.front()) == 0x0000FFFE) {
+        ASSIMP_LOG_DEBUG("Found UTF-32 BOM ...");
+
+        std::vector<char> output;
+        int *ptr = (int*)&data[ 0 ];
+        int *end = ptr + ( data.size() / sizeof(int) ) +1;
+        utf8::utf32to8( ptr, end, back_inserter(output));
+        return;
+    }
+
+    // UTF 16 BE with BOM
+    if(*((uint16_t*)&data.front()) == 0xFFFE) {
+
+        // swap the endianness ..
+        for(uint16_t* p = (uint16_t*)&data.front(), *end = (uint16_t*)&data.back(); p <= end; ++p) {
+            ByteSwap::Swap2(p);
+        }
+    }
+
+    // UTF 16 LE with BOM
+    if(*((uint16_t*)&data.front()) == 0xFEFF) {
+        ASSIMP_LOG_DEBUG("Found UTF-16 BOM ...");
+
+        std::vector<unsigned char> output;
+        utf8::utf16to8(data.begin(), data.end(), back_inserter(output));
+        return;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert to UTF8 data to ISO-8859-1
+void BaseImporter::ConvertUTF8toISO8859_1(std::string& data)
+{
+    size_t size = data.size();
+    size_t i = 0, j = 0;
+
+    while(i < size) {
+        if ((unsigned char) data[i] < (size_t) 0x80) {
+            data[j] = data[i];
+        } else if(i < size - 1) {
+            if((unsigned char) data[i] == 0xC2) {
+                data[j] = data[++i];
+            } else if((unsigned char) data[i] == 0xC3) {
+                data[j] = ((unsigned char) data[++i] + 0x40);
+            } else {
+                std::stringstream stream;
+                stream << "UTF8 code " << std::hex << data[i] << data[i + 1] << " can not be converted into ISA-8859-1.";
+                ASSIMP_LOG_ERROR( stream.str() );
+
+                data[j++] = data[i++];
+                data[j] = data[i];
+            }
+        } else {
+            ASSIMP_LOG_ERROR("UTF8 code but only one character remaining");
+
+            data[j] = data[i];
+        }
+
+        i++; j++;
+    }
+
+    data.resize(j);
+}
+
+// ------------------------------------------------------------------------------------------------
+void BaseImporter::TextFileToBuffer(IOStream* stream,
+    std::vector<char>& data,
+    TextFileMode mode)
+{
+    ai_assert(nullptr != stream);
+
+    const size_t fileSize = stream->FileSize();
+    if (mode == FORBID_EMPTY) {
+        if(!fileSize) {
+            throw DeadlyImportError("File is empty");
+        }
+    }
+
+    data.reserve(fileSize+1);
+    data.resize(fileSize);
+    if(fileSize > 0) {
+        if(fileSize != stream->Read( &data[0], 1, fileSize)) {
+            throw DeadlyImportError("File read error");
+        }
+
+        ConvertToUTF8(data);
+    }
+
+    // append a binary zero to simplify string parsing
+    data.push_back(0);
+}
+
+// ------------------------------------------------------------------------------------------------
+namespace Assimp {
+    // Represents an import request
+    struct LoadRequest {
+        LoadRequest(const std::string& _file, unsigned int _flags,const BatchLoader::PropertyMap* _map, unsigned int _id)
+        : file(_file)
+        , flags(_flags)
+        , refCnt(1)
+        , scene(NULL)
+        , loaded(false)
+        , id(_id) {
+            if ( _map ) {
+                map = *_map;
+            }
+        }
+
+        bool operator== ( const std::string& f ) const {
+            return file == f;
+        }
+
+        const std::string        file;
+        unsigned int             flags;
+        unsigned int             refCnt;
+        aiScene                 *scene;
+        bool                     loaded;
+        BatchLoader::PropertyMap map;
+        unsigned int             id;
+    };
+}
+
+// ------------------------------------------------------------------------------------------------
+// BatchLoader::pimpl data structure
+struct Assimp::BatchData {
+    BatchData( IOSystem* pIO, bool validate )
+    : pIOSystem( pIO )
+    , pImporter( nullptr )
+    , next_id(0xffff)
+    , validate( validate ) {
+        ai_assert( nullptr != pIO );
+        
+        pImporter = new Importer();
+        pImporter->SetIOHandler( pIO );
+    }
+
+    ~BatchData() {
+        pImporter->SetIOHandler( nullptr ); /* get pointer back into our possession */
+        delete pImporter;
+    }
+
+    // IO system to be used for all imports
+    IOSystem* pIOSystem;
+
+    // Importer used to load all meshes
+    Importer* pImporter;
+
+    // List of all imports
+    std::list<LoadRequest> requests;
+
+    // Base path
+    std::string pathBase;
+
+    // Id for next item
+    unsigned int next_id;
+
+    // Validation enabled state
+    bool validate;
+};
+
+typedef std::list<LoadRequest>::iterator LoadReqIt;
+
+// ------------------------------------------------------------------------------------------------
+BatchLoader::BatchLoader(IOSystem* pIO, bool validate ) {
+    ai_assert(nullptr != pIO);
+
+    m_data = new BatchData( pIO, validate );
+}
+
+// ------------------------------------------------------------------------------------------------
+BatchLoader::~BatchLoader()
+{
+    // delete all scenes what have not been polled by the user
+    for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) {
+        delete (*it).scene;
+    }
+    delete m_data;
+}
+
+// ------------------------------------------------------------------------------------------------
+void BatchLoader::setValidation( bool enabled ) {
+    m_data->validate = enabled;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool BatchLoader::getValidation() const {
+    return m_data->validate;
+}
+
+// ------------------------------------------------------------------------------------------------
+unsigned int BatchLoader::AddLoadRequest(const std::string& file,
+    unsigned int steps /*= 0*/, const PropertyMap* map /*= NULL*/)
+{
+    ai_assert(!file.empty());
+
+    // check whether we have this loading request already
+    for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it)  {
+        // Call IOSystem's path comparison function here
+        if ( m_data->pIOSystem->ComparePaths((*it).file,file)) {
+            if (map) {
+                if ( !( ( *it ).map == *map ) ) {
+                    continue;
+                }
+            }
+            else if ( !( *it ).map.empty() ) {
+                continue;
+            }
+
+            (*it).refCnt++;
+            return (*it).id;
+        }
+    }
+
+    // no, we don't have it. So add it to the queue ...
+    m_data->requests.push_back(LoadRequest(file,steps,map, m_data->next_id));
+    return m_data->next_id++;
+}
+
+// ------------------------------------------------------------------------------------------------
+aiScene* BatchLoader::GetImport( unsigned int which )
+{
+    for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) {
+        if ((*it).id == which && (*it).loaded)  {
+            aiScene* sc = (*it).scene;
+            if (!(--(*it).refCnt))  {
+                m_data->requests.erase(it);
+            }
+            return sc;
+        }
+    }
+    return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+void BatchLoader::LoadAll()
+{
+    // no threaded implementation for the moment
+    for ( LoadReqIt it = m_data->requests.begin();it != m_data->requests.end(); ++it) {
+        // force validation in debug builds
+        unsigned int pp = (*it).flags;
+        if ( m_data->validate ) {
+            pp |= aiProcess_ValidateDataStructure;
+        }
+
+        // setup config properties if necessary
+        ImporterPimpl* pimpl = m_data->pImporter->Pimpl();
+        pimpl->mFloatProperties  = (*it).map.floats;
+        pimpl->mIntProperties    = (*it).map.ints;
+        pimpl->mStringProperties = (*it).map.strings;
+        pimpl->mMatrixProperties = (*it).map.matrices;
+
+        if (!DefaultLogger::isNullLogger())
+        {
+            ASSIMP_LOG_INFO("%%% BEGIN EXTERNAL FILE %%%");
+            ASSIMP_LOG_INFO_F("File: ", (*it).file);
+        }
+        m_data->pImporter->ReadFile((*it).file,pp);
+        (*it).scene = m_data->pImporter->GetOrphanedScene();
+        (*it).loaded = true;
+
+        ASSIMP_LOG_INFO("%%% END EXTERNAL FILE %%%");
+    }
+}

+ 107 - 0
thirdparty/assimp/code/BaseProcess.cpp

@@ -0,0 +1,107 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of BaseProcess */
+
+#include <assimp/BaseImporter.h>
+#include "BaseProcess.h"
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/scene.h>
+#include "Importer.h"
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+BaseProcess::BaseProcess() AI_NO_EXCEPT
+: shared()
+, progress()
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+BaseProcess::~BaseProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+void BaseProcess::ExecuteOnScene( Importer* pImp)
+{
+    ai_assert(NULL != pImp && NULL != pImp->Pimpl()->mScene);
+
+    progress = pImp->GetProgressHandler();
+    ai_assert(progress);
+
+    SetupProperties( pImp );
+
+    // catch exceptions thrown inside the PostProcess-Step
+    try
+    {
+        Execute(pImp->Pimpl()->mScene);
+
+    } catch( const std::exception& err )    {
+
+        // extract error description
+        pImp->Pimpl()->mErrorString = err.what();
+        ASSIMP_LOG_ERROR(pImp->Pimpl()->mErrorString);
+
+        // and kill the partially imported data
+        delete pImp->Pimpl()->mScene;
+        pImp->Pimpl()->mScene = NULL;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void BaseProcess::SetupProperties(const Importer* /*pImp*/)
+{
+    // the default implementation does nothing
+}
+
+// ------------------------------------------------------------------------------------------------
+bool BaseProcess::RequireVerboseFormat() const
+{
+    return true;
+}
+

+ 290 - 0
thirdparty/assimp/code/BaseProcess.h

@@ -0,0 +1,290 @@
+/*
+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 Base class of all import post processing steps */
+#ifndef INCLUDED_AI_BASEPROCESS_H
+#define INCLUDED_AI_BASEPROCESS_H
+
+#include <map>
+#include <assimp/GenericProperty.h>
+
+struct aiScene;
+
+namespace Assimp    {
+
+class Importer;
+
+// ---------------------------------------------------------------------------
+/** Helper class to allow post-processing steps to interact with each other.
+ *
+ *  The class maintains a simple property list that can be used by pp-steps
+ *  to provide additional information to other steps. This is primarily
+ *  intended for cross-step optimizations.
+ */
+class SharedPostProcessInfo
+{
+public:
+
+    struct Base
+    {
+        virtual ~Base()
+        {}
+    };
+
+    //! Represents data that is allocated on the heap, thus needs to be deleted
+    template <typename T>
+    struct THeapData : public Base
+    {
+        explicit THeapData(T* in)
+            : data (in)
+        {}
+
+        ~THeapData()
+        {
+            delete data;
+        }
+        T* data;
+    };
+
+    //! Represents static, by-value data not allocated on the heap
+    template <typename T>
+    struct TStaticData : public Base
+    {
+        explicit TStaticData(T in)
+            : data (in)
+        {}
+
+        ~TStaticData()
+        {}
+
+        T data;
+    };
+
+    // some typedefs for cleaner code
+    typedef unsigned int KeyType;
+    typedef std::map<KeyType, Base*>  PropertyMap;
+
+public:
+
+    //! Destructor
+    ~SharedPostProcessInfo()
+    {
+        Clean();
+    }
+
+    //! Remove all stored properties from the table
+    void Clean()
+    {
+        // invoke the virtual destructor for all stored properties
+        for (PropertyMap::iterator it = pmap.begin(), end = pmap.end();
+             it != end; ++it)
+        {
+            delete (*it).second;
+        }
+        pmap.clear();
+    }
+
+    //! Add a heap property to the list
+    template <typename T>
+    void AddProperty( const char* name, T* in ){
+        AddProperty(name,(Base*)new THeapData<T>(in));
+    }
+
+    //! Add a static by-value property to the list
+    template <typename T>
+    void AddProperty( const char* name, T in ){
+        AddProperty(name,(Base*)new TStaticData<T>(in));
+    }
+
+
+    //! Get a heap property
+    template <typename T>
+    bool GetProperty( const char* name, T*& out ) const
+    {
+        THeapData<T>* t = (THeapData<T>*)GetPropertyInternal(name);
+        if(!t)
+        {
+            out = NULL;
+            return false;
+        }
+        out = t->data;
+        return true;
+    }
+
+    //! Get a static, by-value property
+    template <typename T>
+    bool GetProperty( const char* name, T& out ) const
+    {
+        TStaticData<T>* t = (TStaticData<T>*)GetPropertyInternal(name);
+        if(!t)return false;
+        out = t->data;
+        return true;
+    }
+
+    //! Remove a property of a specific type
+    void RemoveProperty( const char* name)  {
+        SetGenericPropertyPtr<Base>(pmap,name,NULL);
+    }
+
+private:
+
+    void AddProperty( const char* name, Base* data) {
+        SetGenericPropertyPtr<Base>(pmap,name,data);
+    }
+
+    Base* GetPropertyInternal( const char* name) const  {
+        return GetGenericProperty<Base*>(pmap,name,NULL);
+    }
+
+private:
+
+    //! Map of all stored properties
+    PropertyMap pmap;
+};
+
+#if 0
+
+// ---------------------------------------------------------------------------
+/** @brief Represents a dependency table for a postprocessing steps.
+ *
+ *  For future use.
+ */
+ struct PPDependencyTable
+ {
+     unsigned int execute_me_before_these;
+     unsigned int execute_me_after_these;
+     unsigned int only_if_these_are_not_specified;
+     unsigned int mutually_exclusive_with;
+ };
+
+#endif
+
+
+#define AI_SPP_SPATIAL_SORT "$Spat"
+
+// ---------------------------------------------------------------------------
+/** The BaseProcess defines a common interface for all post processing steps.
+ * A post processing step is run after a successful import if the caller
+ * specified the corresponding flag when calling ReadFile().
+ * Enum #aiPostProcessSteps defines which flags are available.
+ * After a successful import the Importer iterates over its internal array
+ * of processes and calls IsActive() on each process to evaluate if the step
+ * should be executed. If the function returns true, the class' Execute()
+ * function is called subsequently.
+ */
+class ASSIMP_API_WINONLY BaseProcess {
+    friend class Importer;
+
+public:
+    /** Constructor to be privately used by Importer */
+    BaseProcess() AI_NO_EXCEPT;
+
+    /** Destructor, private as well */
+    virtual ~BaseProcess();
+
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag.
+     * @param pFlags The processing flags the importer was called with. A
+     *   bitwise combination of #aiPostProcessSteps.
+     * @return true if the process is present in this flag fields,
+     *   false if not.
+    */
+    virtual bool IsActive( unsigned int pFlags) const = 0;
+
+    // -------------------------------------------------------------------
+    /** Check whether this step expects its input vertex data to be
+     *  in verbose format. */
+    virtual bool RequireVerboseFormat() const;
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * The function deletes the scene if the postprocess step fails (
+    * the object pointer will be set to NULL).
+    * @param pImp Importer instance (pImp->mScene must be valid)
+    */
+    void ExecuteOnScene( Importer* pImp);
+
+    // -------------------------------------------------------------------
+    /** Called prior to ExecuteOnScene().
+    * The function is a request to the process to update its configuration
+    * basing on the Importer's configuration property list.
+    */
+    virtual void SetupProperties(const Importer* pImp);
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * A process should throw an ImportErrorException* if it fails.
+    * This method must be implemented by deriving classes.
+    * @param pScene The imported data to work at.
+    */
+    virtual void Execute( aiScene* pScene) = 0;
+
+
+    // -------------------------------------------------------------------
+    /** Assign a new SharedPostProcessInfo to the step. This object
+     *  allows multiple postprocess steps to share data.
+     * @param sh May be NULL
+    */
+    inline void SetSharedData(SharedPostProcessInfo* sh)    {
+        shared = sh;
+    }
+
+    // -------------------------------------------------------------------
+    /** Get the shared data that is assigned to the step.
+    */
+    inline SharedPostProcessInfo* GetSharedData()   {
+        return shared;
+    }
+
+protected:
+
+    /** See the doc of #SharedPostProcessInfo for more details */
+    SharedPostProcessInfo* shared;
+
+    /** Currently active progress handler */
+    ProgressHandler* progress;
+};
+
+
+} // end of namespace Assimp
+
+#endif // AI_BASEPROCESS_H_INC

+ 155 - 0
thirdparty/assimp/code/Bitmap.cpp

@@ -0,0 +1,155 @@
+/*
+---------------------------------------------------------------------------
+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 Bitmap.cpp
+ *  @brief Defines bitmap format helper for textures
+ *
+ * Used for file formats which embed their textures into the model file.
+ */
+
+
+#include <assimp/Bitmap.h>
+#include <assimp/texture.h>
+#include <assimp/IOStream.hpp>
+#include <assimp/ByteSwapper.h>
+
+namespace Assimp {
+
+    void Bitmap::Save(aiTexture* texture, IOStream* file) {
+        if(file != NULL) {
+            Header header;
+            DIB dib;
+
+            dib.size = DIB::dib_size;
+            dib.width = texture->mWidth;
+            dib.height = texture->mHeight;
+            dib.planes = 1;
+            dib.bits_per_pixel = 8 * mBytesPerPixel;
+            dib.compression = 0;
+            dib.image_size = (((dib.width * mBytesPerPixel) + 3) & 0x0000FFFC) * dib.height;
+            dib.x_resolution = 0;
+            dib.y_resolution = 0;
+            dib.nb_colors = 0;
+            dib.nb_important_colors = 0;
+
+            header.type = 0x4D42; // 'BM'
+            header.offset = Header::header_size + DIB::dib_size;
+            header.size = header.offset + dib.image_size;
+            header.reserved1 = 0;
+            header.reserved2 = 0;
+
+            WriteHeader(header, file);
+            WriteDIB(dib, file);
+            WriteData(texture, file);
+        }
+    }
+
+    template<typename T>
+    inline 
+    std::size_t Copy(uint8_t* data, const T &field) {
+#ifdef AI_BUILD_BIG_ENDIAN
+        T field_swapped=AI_BE(field);
+        std::memcpy(data, &field_swapped, sizeof(field)); return sizeof(field);
+#else
+        std::memcpy(data, &AI_BE(field), sizeof(field)); return sizeof(field);
+#endif
+    }
+
+    void Bitmap::WriteHeader(Header& header, IOStream* file) {
+        uint8_t data[Header::header_size];
+
+        std::size_t offset = 0;
+
+        offset += Copy(&data[offset], header.type);
+        offset += Copy(&data[offset], header.size);
+        offset += Copy(&data[offset], header.reserved1);
+        offset += Copy(&data[offset], header.reserved2);
+                  Copy(&data[offset], header.offset);
+
+        file->Write(data, Header::header_size, 1);
+    }
+
+    void Bitmap::WriteDIB(DIB& dib, IOStream* file) {
+        uint8_t data[DIB::dib_size];
+
+        std::size_t offset = 0;
+
+        offset += Copy(&data[offset], dib.size);
+        offset += Copy(&data[offset], dib.width);
+        offset += Copy(&data[offset], dib.height);
+        offset += Copy(&data[offset], dib.planes);
+        offset += Copy(&data[offset], dib.bits_per_pixel);
+        offset += Copy(&data[offset], dib.compression);
+        offset += Copy(&data[offset], dib.image_size);
+        offset += Copy(&data[offset], dib.x_resolution);
+        offset += Copy(&data[offset], dib.y_resolution);
+        offset += Copy(&data[offset], dib.nb_colors);
+                  Copy(&data[offset], dib.nb_important_colors);
+
+        file->Write(data, DIB::dib_size, 1);
+    }
+
+    void Bitmap::WriteData(aiTexture* texture, IOStream* file) {
+        static const std::size_t padding_offset = 4;
+        static const uint8_t padding_data[padding_offset] = {0x0, 0x0, 0x0, 0x0};
+
+        unsigned int padding = (padding_offset - ((mBytesPerPixel * texture->mWidth) % padding_offset)) % padding_offset;
+        uint8_t pixel[mBytesPerPixel];
+
+        for(std::size_t i = 0; i < texture->mHeight; ++i) {
+            for(std::size_t j = 0; j < texture->mWidth; ++j) {
+                const aiTexel& texel = texture->pcData[(texture->mHeight - i - 1) * texture->mWidth + j]; // Bitmap files are stored in bottom-up format
+
+                pixel[0] = texel.r;
+                pixel[1] = texel.g;
+                pixel[2] = texel.b;
+                pixel[3] = texel.a;
+
+                file->Write(pixel, mBytesPerPixel, 1);
+            }
+
+            file->Write(padding_data, padding, 1);
+        }
+    }
+
+}

+ 136 - 0
thirdparty/assimp/code/CInterfaceIOWrapper.cpp

@@ -0,0 +1,136 @@
+/*
+---------------------------------------------------------------------------
+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 aiFileIO -> IOSystem wrapper*/
+
+#include "CInterfaceIOWrapper.h"
+
+namespace Assimp    {
+
+CIOStreamWrapper::~CIOStreamWrapper(void)
+{
+    /* Various places depend on this destructor to close the file */
+    if (mFile) {
+        mIO->mFileSystem->CloseProc(mIO->mFileSystem, mFile);
+        mFile = nullptr;
+    }
+}
+
+// ...................................................................
+size_t CIOStreamWrapper::Read(void* pvBuffer,
+    size_t pSize,
+    size_t pCount
+){
+    // need to typecast here as C has no void*
+    return mFile->ReadProc(mFile,(char*)pvBuffer,pSize,pCount);
+}
+
+// ...................................................................
+size_t CIOStreamWrapper::Write(const void* pvBuffer,
+    size_t pSize,
+    size_t pCount
+){
+    // need to typecast here as C has no void*
+    return mFile->WriteProc(mFile,(const char*)pvBuffer,pSize,pCount);
+}
+
+// ...................................................................
+aiReturn CIOStreamWrapper::Seek(size_t pOffset,
+    aiOrigin pOrigin
+){
+    return mFile->SeekProc(mFile,pOffset,pOrigin);
+}
+
+// ...................................................................
+size_t CIOStreamWrapper::Tell(void) const {
+    return mFile->TellProc(mFile);
+}
+
+// ...................................................................
+size_t CIOStreamWrapper::FileSize() const {
+    return mFile->FileSizeProc(mFile);
+}
+
+// ...................................................................
+void CIOStreamWrapper::Flush () {
+    return mFile->FlushProc(mFile);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Custom IOStream implementation for the C-API
+bool CIOSystemWrapper::Exists( const char* pFile) const {
+    aiFile* p = mFileSystem->OpenProc(mFileSystem,pFile,"rb");
+    if (p){
+        mFileSystem->CloseProc(mFileSystem,p);
+        return true;
+    }
+    return false;
+}
+
+// ...................................................................
+char CIOSystemWrapper::getOsSeparator() const {
+#ifndef _WIN32
+    return '/';
+#else
+    return '\\';
+#endif
+}
+
+// ...................................................................
+IOStream* CIOSystemWrapper::Open(const char* pFile,const char* pMode) {
+    aiFile* p = mFileSystem->OpenProc(mFileSystem,pFile,pMode);
+    if (!p) {
+        return NULL;
+    }
+    return new CIOStreamWrapper(p, this);
+}
+
+// ...................................................................
+void CIOSystemWrapper::Close( IOStream* pFile) {
+    if (!pFile) {
+        return;
+    }
+    delete pFile;
+}
+
+}

+ 99 - 0
thirdparty/assimp/code/CInterfaceIOWrapper.h

@@ -0,0 +1,99 @@
+/*
+---------------------------------------------------------------------------
+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 aiFileIO -> IOSystem wrapper*/
+
+#ifndef AI_CIOSYSTEM_H_INCLUDED
+#define AI_CIOSYSTEM_H_INCLUDED
+
+#include <assimp/cfileio.h>
+#include <assimp/IOStream.hpp>
+#include <assimp/IOSystem.hpp>
+
+namespace Assimp    {
+
+class CIOSystemWrapper;
+
+// ------------------------------------------------------------------------------------------------
+// Custom IOStream implementation for the C-API
+class CIOStreamWrapper : public IOStream
+{
+public:
+    explicit CIOStreamWrapper(aiFile* pFile, CIOSystemWrapper* io)
+        : mFile(pFile),
+        mIO(io)
+    {}
+    ~CIOStreamWrapper(void);
+
+    size_t Read(void* pvBuffer, size_t pSize, size_t pCount);
+    size_t Write(const void* pvBuffer, size_t pSize, size_t pCount);
+    aiReturn Seek(size_t pOffset, aiOrigin pOrigin);
+    size_t Tell(void) const;
+    size_t FileSize() const;
+    void Flush();
+
+private:
+    aiFile* mFile;
+    CIOSystemWrapper* mIO;
+};
+
+class CIOSystemWrapper : public IOSystem
+{
+    friend class CIOStreamWrapper;
+public:
+    explicit CIOSystemWrapper(aiFileIO* pFile)
+        : mFileSystem(pFile)
+    {}
+
+    bool Exists( const char* pFile) const;
+    char getOsSeparator() const;
+    IOStream* Open(const char* pFile,const char* pMode = "rb");
+    void Close( IOStream* pFile);
+private:
+    aiFileIO* mFileSystem;
+};
+
+}
+
+#endif
+

+ 319 - 0
thirdparty/assimp/code/CalcTangentsProcess.cpp

@@ -0,0 +1,319 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of the post processing step to calculate
+ *  tangents and bitangents for all imported meshes
+ */
+
+// internal headers
+#include "CalcTangentsProcess.h"
+#include "ProcessHelper.h"
+#include <assimp/TinyFormatter.h>
+#include <assimp/qnan.h>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+CalcTangentsProcess::CalcTangentsProcess()
+: configMaxAngle( AI_DEG_TO_RAD(45.f) )
+, configSourceUV( 0 ) {
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+CalcTangentsProcess::~CalcTangentsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool CalcTangentsProcess::IsActive( unsigned int pFlags) const
+{
+    return (pFlags & aiProcess_CalcTangentSpace) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void CalcTangentsProcess::SetupProperties(const Importer* pImp)
+{
+    ai_assert( NULL != pImp );
+
+    // get the current value of the property
+    configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_CT_MAX_SMOOTHING_ANGLE,45.f);
+    configMaxAngle = std::max(std::min(configMaxAngle,45.0f),0.0f);
+    configMaxAngle = AI_DEG_TO_RAD(configMaxAngle);
+
+    configSourceUV = pImp->GetPropertyInteger(AI_CONFIG_PP_CT_TEXTURE_CHANNEL_INDEX,0);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void CalcTangentsProcess::Execute( aiScene* pScene)
+{
+    ai_assert( NULL != pScene );
+
+    ASSIMP_LOG_DEBUG("CalcTangentsProcess begin");
+
+    bool bHas = false;
+    for ( unsigned int a = 0; a < pScene->mNumMeshes; a++ ) {
+        if(ProcessMesh( pScene->mMeshes[a],a))bHas = true;
+    }
+
+    if ( bHas ) {
+        ASSIMP_LOG_INFO("CalcTangentsProcess finished. Tangents have been calculated");
+    } else {
+        ASSIMP_LOG_DEBUG("CalcTangentsProcess finished");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Calculates tangents and bi-tangents for the given mesh
+bool CalcTangentsProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
+{
+    // we assume that the mesh is still in the verbose vertex format where each face has its own set
+    // of vertices and no vertices are shared between faces. Sadly I don't know any quick test to
+    // assert() it here.
+    // assert( must be verbose, dammit);
+
+    if (pMesh->mTangents) // this implies that mBitangents is also there
+        return false;
+
+    // If the mesh consists of lines and/or points but not of
+    // triangles or higher-order polygons the normal vectors
+    // are undefined.
+    if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON)))
+    {
+        ASSIMP_LOG_INFO("Tangents are undefined for line and point meshes");
+        return false;
+    }
+
+    // what we can check, though, is if the mesh has normals and texture coordinates. That's a requirement
+    if( pMesh->mNormals == NULL)
+    {
+        ASSIMP_LOG_ERROR("Failed to compute tangents; need normals");
+        return false;
+    }
+    if( configSourceUV >= AI_MAX_NUMBER_OF_TEXTURECOORDS || !pMesh->mTextureCoords[configSourceUV] )
+    {
+        ASSIMP_LOG_ERROR((Formatter::format("Failed to compute tangents; need UV data in channel"),configSourceUV));
+        return false;
+    }
+
+    const float angleEpsilon = 0.9999f;
+
+    std::vector<bool> vertexDone( pMesh->mNumVertices, false);
+    const float qnan = get_qnan();
+
+    // create space for the tangents and bitangents
+    pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
+    pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices];
+
+    const aiVector3D* meshPos = pMesh->mVertices;
+    const aiVector3D* meshNorm = pMesh->mNormals;
+    const aiVector3D* meshTex = pMesh->mTextureCoords[configSourceUV];
+    aiVector3D* meshTang = pMesh->mTangents;
+    aiVector3D* meshBitang = pMesh->mBitangents;
+
+    // calculate the tangent and bitangent for every face
+    for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
+    {
+        const aiFace& face = pMesh->mFaces[a];
+        if (face.mNumIndices < 3)
+        {
+            // There are less than three indices, thus the tangent vector
+            // is not defined. We are finished with these vertices now,
+            // their tangent vectors are set to qnan.
+            for (unsigned int i = 0; i < face.mNumIndices;++i)
+            {
+                unsigned int idx = face.mIndices[i];
+                vertexDone  [idx] = true;
+                meshTang    [idx] = aiVector3D(qnan);
+                meshBitang  [idx] = aiVector3D(qnan);
+            }
+
+            continue;
+        }
+
+        // triangle or polygon... we always use only the first three indices. A polygon
+        // is supposed to be planar anyways....
+        // FIXME: (thom) create correct calculation for multi-vertex polygons maybe?
+        const unsigned int p0 = face.mIndices[0], p1 = face.mIndices[1], p2 = face.mIndices[2];
+
+        // position differences p1->p2 and p1->p3
+        aiVector3D v = meshPos[p1] - meshPos[p0], w = meshPos[p2] - meshPos[p0];
+
+        // texture offset p1->p2 and p1->p3
+        float sx = meshTex[p1].x - meshTex[p0].x, sy = meshTex[p1].y - meshTex[p0].y;
+        float tx = meshTex[p2].x - meshTex[p0].x, ty = meshTex[p2].y - meshTex[p0].y;
+        float dirCorrection = (tx * sy - ty * sx) < 0.0f ? -1.0f : 1.0f;
+        // when t1, t2, t3 in same position in UV space, just use default UV direction.
+        if (  sx * ty == sy * tx ) {
+            sx = 0.0; sy = 1.0;
+            tx = 1.0; ty = 0.0;
+        }
+
+        // tangent points in the direction where to positive X axis of the texture coord's would point in model space
+        // bitangent's points along the positive Y axis of the texture coord's, respectively
+        aiVector3D tangent, bitangent;
+        tangent.x = (w.x * sy - v.x * ty) * dirCorrection;
+        tangent.y = (w.y * sy - v.y * ty) * dirCorrection;
+        tangent.z = (w.z * sy - v.z * ty) * dirCorrection;
+        bitangent.x = (w.x * sx - v.x * tx) * dirCorrection;
+        bitangent.y = (w.y * sx - v.y * tx) * dirCorrection;
+        bitangent.z = (w.z * sx - v.z * tx) * dirCorrection;
+
+        // store for every vertex of that face
+        for( unsigned int b = 0; b < face.mNumIndices; ++b ) {
+            unsigned int p = face.mIndices[b];
+
+            // project tangent and bitangent into the plane formed by the vertex' normal
+            aiVector3D localTangent = tangent - meshNorm[p] * (tangent * meshNorm[p]);
+            aiVector3D localBitangent = bitangent - meshNorm[p] * (bitangent * meshNorm[p]);
+            localTangent.Normalize(); localBitangent.Normalize();
+
+            // reconstruct tangent/bitangent according to normal and bitangent/tangent when it's infinite or NaN.
+            bool invalid_tangent = is_special_float(localTangent.x) || is_special_float(localTangent.y) || is_special_float(localTangent.z);
+            bool invalid_bitangent = is_special_float(localBitangent.x) || is_special_float(localBitangent.y) || is_special_float(localBitangent.z);
+            if (invalid_tangent != invalid_bitangent) {
+                if (invalid_tangent) {
+                    localTangent = meshNorm[p] ^ localBitangent;
+                    localTangent.Normalize();
+                } else {
+                    localBitangent = localTangent ^ meshNorm[p];
+                    localBitangent.Normalize();
+                }
+            }
+
+            // and write it into the mesh.
+            meshTang[ p ]   = localTangent;
+            meshBitang[ p ] = localBitangent;
+        }
+    }
+
+
+    // create a helper to quickly find locally close vertices among the vertex array
+    // FIX: check whether we can reuse the SpatialSort of a previous step
+    SpatialSort* vertexFinder = NULL;
+    SpatialSort  _vertexFinder;
+    float posEpsilon;
+    if (shared)
+    {
+        std::vector<std::pair<SpatialSort,float> >* avf;
+        shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
+        if (avf)
+        {
+            std::pair<SpatialSort,float>& blubb = avf->operator [] (meshIndex);
+            vertexFinder = &blubb.first;
+            posEpsilon = blubb.second;;
+        }
+    }
+    if (!vertexFinder)
+    {
+        _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
+        vertexFinder = &_vertexFinder;
+        posEpsilon = ComputePositionEpsilon(pMesh);
+    }
+    std::vector<unsigned int> verticesFound;
+
+    const float fLimit = std::cos(configMaxAngle);
+    std::vector<unsigned int> closeVertices;
+
+    // in the second pass we now smooth out all tangents and bitangents at the same local position
+    // if they are not too far off.
+    for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
+    {
+        if( vertexDone[a])
+            continue;
+
+        const aiVector3D& origPos = pMesh->mVertices[a];
+        const aiVector3D& origNorm = pMesh->mNormals[a];
+        const aiVector3D& origTang = pMesh->mTangents[a];
+        const aiVector3D& origBitang = pMesh->mBitangents[a];
+        closeVertices.resize( 0 );
+
+        // find all vertices close to that position
+        vertexFinder->FindPositions( origPos, posEpsilon, verticesFound);
+
+        closeVertices.reserve (verticesFound.size()+5);
+        closeVertices.push_back( a);
+
+        // look among them for other vertices sharing the same normal and a close-enough tangent/bitangent
+        for( unsigned int b = 0; b < verticesFound.size(); b++)
+        {
+            unsigned int idx = verticesFound[b];
+            if( vertexDone[idx])
+                continue;
+            if( meshNorm[idx] * origNorm < angleEpsilon)
+                continue;
+            if(  meshTang[idx] * origTang < fLimit)
+                continue;
+            if( meshBitang[idx] * origBitang < fLimit)
+                continue;
+
+            // it's similar enough -> add it to the smoothing group
+            closeVertices.push_back( idx);
+            vertexDone[idx] = true;
+        }
+
+        // smooth the tangents and bitangents of all vertices that were found to be close enough
+        aiVector3D smoothTangent( 0, 0, 0), smoothBitangent( 0, 0, 0);
+        for( unsigned int b = 0; b < closeVertices.size(); ++b)
+        {
+            smoothTangent += meshTang[ closeVertices[b] ];
+            smoothBitangent += meshBitang[ closeVertices[b] ];
+        }
+        smoothTangent.Normalize();
+        smoothBitangent.Normalize();
+
+        // and write it back into all affected tangents
+        for( unsigned int b = 0; b < closeVertices.size(); ++b)
+        {
+            meshTang[ closeVertices[b] ] = smoothTangent;
+            meshBitang[ closeVertices[b] ] = smoothBitangent;
+        }
+    }
+    return true;
+}

+ 117 - 0
thirdparty/assimp/code/CalcTangentsProcess.h

@@ -0,0 +1,117 @@
+/*
+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 Defines a post processing step to calculate tangents and
+    bitangents on all imported meshes.*/
+#ifndef AI_CALCTANGENTSPROCESS_H_INC
+#define AI_CALCTANGENTSPROCESS_H_INC
+
+#include "BaseProcess.h"
+
+struct aiMesh;
+
+namespace Assimp
+{
+
+// ---------------------------------------------------------------------------
+/** The CalcTangentsProcess calculates the tangent and bitangent for any vertex
+ * of all meshes. It is expected to be run before the JoinVerticesProcess runs
+ * because the joining of vertices also considers tangents and bitangents for
+ * uniqueness.
+ */
+class ASSIMP_API_WINONLY CalcTangentsProcess : public BaseProcess
+{
+public:
+
+    CalcTangentsProcess();
+    ~CalcTangentsProcess();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag.
+    * @param pFlags The processing flags the importer was called with.
+    *   A bitwise combination of #aiPostProcessSteps.
+    * @return true if the process is present in this flag fields,
+    *   false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Called prior to ExecuteOnScene().
+    * The function is a request to the process to update its configuration
+    * basing on the Importer's configuration property list.
+    */
+    void SetupProperties(const Importer* pImp);
+
+
+    // setter for configMaxAngle
+    inline void SetMaxSmoothAngle(float f)
+    {
+        configMaxAngle =f;
+    }
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Calculates tangents and bitangents for a specific mesh.
+    * @param pMesh The mesh to process.
+    * @param meshIndex Index of the mesh
+    */
+    bool ProcessMesh( aiMesh* pMesh, unsigned int meshIndex);
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+private:
+
+    /** Configuration option: maximum smoothing angle, in radians*/
+    float configMaxAngle;
+    unsigned int configSourceUV;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_CALCTANGENTSPROCESS_H_INC

+ 506 - 0
thirdparty/assimp/code/ComputeUVMappingProcess.cpp

@@ -0,0 +1,506 @@
+/*
+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 GenUVCoords step */
+
+
+#include "ComputeUVMappingProcess.h"
+#include "ProcessHelper.h"
+#include <assimp/Exceptional.h>
+
+using namespace Assimp;
+
+namespace {
+
+    const static aiVector3D base_axis_y(0.0,1.0,0.0);
+    const static aiVector3D base_axis_x(1.0,0.0,0.0);
+    const static aiVector3D base_axis_z(0.0,0.0,1.0);
+    const static ai_real angle_epsilon = ai_real( 0.95 );
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+ComputeUVMappingProcess::ComputeUVMappingProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+ComputeUVMappingProcess::~ComputeUVMappingProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool ComputeUVMappingProcess::IsActive( unsigned int pFlags) const
+{
+    return  (pFlags & aiProcess_GenUVCoords) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check whether a ray intersects a plane and find the intersection point
+inline bool PlaneIntersect(const aiRay& ray, const aiVector3D& planePos,
+    const aiVector3D& planeNormal, aiVector3D& pos)
+{
+    const ai_real b = planeNormal * (planePos - ray.pos);
+    ai_real h = ray.dir * planeNormal;
+    if ((h < 10e-5 && h > -10e-5) || (h = b/h) < 0)
+        return false;
+
+    pos = ray.pos + (ray.dir * h);
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find the first empty UV channel in a mesh
+inline unsigned int FindEmptyUVChannel (aiMesh* mesh)
+{
+    for (unsigned int m = 0; m < AI_MAX_NUMBER_OF_TEXTURECOORDS;++m)
+        if (!mesh->mTextureCoords[m])return m;
+
+    ASSIMP_LOG_ERROR("Unable to compute UV coordinates, no free UV slot found");
+    return UINT_MAX;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Try to remove UV seams
+void RemoveUVSeams (aiMesh* mesh, aiVector3D* out)
+{
+    // TODO: just a very rough algorithm. I think it could be done
+    // much easier, but I don't know how and am currently too tired to
+    // to think about a better solution.
+
+    const static ai_real LOWER_LIMIT = ai_real( 0.1 );
+    const static ai_real UPPER_LIMIT = ai_real( 0.9 );
+
+    const static ai_real LOWER_EPSILON = ai_real( 10e-3 );
+    const static ai_real UPPER_EPSILON = ai_real( 1.0-10e-3 );
+
+    for (unsigned int fidx = 0; fidx < mesh->mNumFaces;++fidx)
+    {
+        const aiFace& face = mesh->mFaces[fidx];
+        if (face.mNumIndices < 3) continue; // triangles and polygons only, please
+
+        unsigned int small = face.mNumIndices, large = small;
+        bool zero = false, one = false, round_to_zero = false;
+
+        // Check whether this face lies on a UV seam. We can just guess,
+        // but the assumption that a face with at least one very small
+        // on the one side and one very large U coord on the other side
+        // lies on a UV seam should work for most cases.
+        for (unsigned int n = 0; n < face.mNumIndices;++n)
+        {
+            if (out[face.mIndices[n]].x < LOWER_LIMIT)
+            {
+                small = n;
+
+                // If we have a U value very close to 0 we can't
+                // round the others to 0, too.
+                if (out[face.mIndices[n]].x <= LOWER_EPSILON)
+                    zero = true;
+                else round_to_zero = true;
+            }
+            if (out[face.mIndices[n]].x > UPPER_LIMIT)
+            {
+                large = n;
+
+                // If we have a U value very close to 1 we can't
+                // round the others to 1, too.
+                if (out[face.mIndices[n]].x >= UPPER_EPSILON)
+                    one = true;
+            }
+        }
+        if (small != face.mNumIndices && large != face.mNumIndices)
+        {
+            for (unsigned int n = 0; n < face.mNumIndices;++n)
+            {
+                // If the u value is over the upper limit and no other u
+                // value of that face is 0, round it to 0
+                if (out[face.mIndices[n]].x > UPPER_LIMIT && !zero)
+                    out[face.mIndices[n]].x = 0.0;
+
+                // If the u value is below the lower limit and no other u
+                // value of that face is 1, round it to 1
+                else if (out[face.mIndices[n]].x < LOWER_LIMIT && !one)
+                    out[face.mIndices[n]].x = 1.0;
+
+                // The face contains both 0 and 1 as UV coords. This can occur
+                // for faces which have an edge that lies directly on the seam.
+                // Due to numerical inaccuracies one U coord becomes 0, the
+                // other 1. But we do still have a third UV coord to determine
+                // to which side we must round to.
+                else if (one && zero)
+                {
+                    if (round_to_zero && out[face.mIndices[n]].x >=  UPPER_EPSILON)
+                        out[face.mIndices[n]].x = 0.0;
+                    else if (!round_to_zero && out[face.mIndices[n]].x <= LOWER_EPSILON)
+                        out[face.mIndices[n]].x = 1.0;
+                }
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void ComputeUVMappingProcess::ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
+{
+    aiVector3D center, min, max;
+    FindMeshCenter(mesh, center, min, max);
+
+    // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ...
+    // currently the mapping axis will always be one of x,y,z, except if the
+    // PretransformVertices step is used (it transforms the meshes into worldspace,
+    // thus changing the mapping axis)
+    if (axis * base_axis_x >= angle_epsilon)    {
+
+        // For each point get a normalized projection vector in the sphere,
+        // get its longitude and latitude and map them to their respective
+        // UV axes. Problems occur around the poles ... unsolvable.
+        //
+        // The spherical coordinate system looks like this:
+        // x = cos(lon)*cos(lat)
+        // y = sin(lon)*cos(lat)
+        // z = sin(lat)
+        //
+        // Thus we can derive:
+        // lat  = arcsin (z)
+        // lon  = arctan (y/x)
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize();
+            out[pnt] = aiVector3D((std::atan2(diff.z, diff.y) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
+                (std::asin  (diff.x) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
+        }
+    }
+    else if (axis * base_axis_y >= angle_epsilon)   {
+        // ... just the same again
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize();
+            out[pnt] = aiVector3D((std::atan2(diff.x, diff.z) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
+                (std::asin  (diff.y) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
+        }
+    }
+    else if (axis * base_axis_z >= angle_epsilon)   {
+        // ... just the same again
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D diff = (mesh->mVertices[pnt]-center).Normalize();
+            out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
+                (std::asin  (diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
+        }
+    }
+    // slower code path in case the mapping axis is not one of the coordinate system axes
+    else    {
+        aiMatrix4x4 mTrafo;
+        aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo);
+
+        // again the same, except we're applying a transformation now
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D diff = ((mTrafo*mesh->mVertices[pnt])-center).Normalize();
+            out[pnt] = aiVector3D((std::atan2(diff.y, diff.x) + AI_MATH_PI_F ) / AI_MATH_TWO_PI_F,
+                (std::asin(diff.z) + AI_MATH_HALF_PI_F) / AI_MATH_PI_F, 0.0);
+        }
+    }
+
+
+    // Now find and remove UV seams. A seam occurs if a face has a tcoord
+    // close to zero on the one side, and a tcoord close to one on the
+    // other side.
+    RemoveUVSeams(mesh,out);
+}
+
+// ------------------------------------------------------------------------------------------------
+void ComputeUVMappingProcess::ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
+{
+    aiVector3D center, min, max;
+
+    // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ...
+    // currently the mapping axis will always be one of x,y,z, except if the
+    // PretransformVertices step is used (it transforms the meshes into worldspace,
+    // thus changing the mapping axis)
+    if (axis * base_axis_x >= angle_epsilon)    {
+        FindMeshCenter(mesh, center, min, max);
+        const ai_real diff = max.x - min.x;
+
+        // If the main axis is 'z', the z coordinate of a point 'p' is mapped
+        // directly to the texture V axis. The other axis is derived from
+        // the angle between ( p.x - c.x, p.y - c.y ) and (1,0), where
+        // 'c' is the center point of the mesh.
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D& pos = mesh->mVertices[pnt];
+            aiVector3D& uv  = out[pnt];
+
+            uv.y = (pos.x - min.x) / diff;
+            uv.x = (std::atan2( pos.z - center.z, pos.y - center.y) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
+        }
+    }
+    else if (axis * base_axis_y >= angle_epsilon)   {
+        FindMeshCenter(mesh, center, min, max);
+        const ai_real diff = max.y - min.y;
+
+        // just the same ...
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D& pos = mesh->mVertices[pnt];
+            aiVector3D& uv  = out[pnt];
+
+            uv.y = (pos.y - min.y) / diff;
+            uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
+        }
+    }
+    else if (axis * base_axis_z >= angle_epsilon)   {
+        FindMeshCenter(mesh, center, min, max);
+        const ai_real diff = max.z - min.z;
+
+        // just the same ...
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D& pos = mesh->mVertices[pnt];
+            aiVector3D& uv  = out[pnt];
+
+            uv.y = (pos.z - min.z) / diff;
+            uv.x = (std::atan2( pos.y - center.y, pos.x - center.x) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
+        }
+    }
+    // slower code path in case the mapping axis is not one of the coordinate system axes
+    else {
+        aiMatrix4x4 mTrafo;
+        aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo);
+        FindMeshCenterTransformed(mesh, center, min, max,mTrafo);
+        const ai_real diff = max.y - min.y;
+
+        // again the same, except we're applying a transformation now
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt){
+            const aiVector3D pos = mTrafo* mesh->mVertices[pnt];
+            aiVector3D& uv  = out[pnt];
+
+            uv.y = (pos.y - min.y) / diff;
+            uv.x = (std::atan2( pos.x - center.x, pos.z - center.z) +(ai_real)AI_MATH_PI ) / (ai_real)AI_MATH_TWO_PI;
+        }
+    }
+
+    // Now find and remove UV seams. A seam occurs if a face has a tcoord
+    // close to zero on the one side, and a tcoord close to one on the
+    // other side.
+    RemoveUVSeams(mesh,out);
+}
+
+// ------------------------------------------------------------------------------------------------
+void ComputeUVMappingProcess::ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis, aiVector3D* out)
+{
+    ai_real diffu,diffv;
+    aiVector3D center, min, max;
+
+    // If the axis is one of x,y,z run a faster code path. It's worth the extra effort ...
+    // currently the mapping axis will always be one of x,y,z, except if the
+    // PretransformVertices step is used (it transforms the meshes into worldspace,
+    // thus changing the mapping axis)
+    if (axis * base_axis_x >= angle_epsilon)    {
+        FindMeshCenter(mesh, center, min, max);
+        diffu = max.z - min.z;
+        diffv = max.y - min.y;
+
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D& pos = mesh->mVertices[pnt];
+            out[pnt].Set((pos.z - min.z) / diffu,(pos.y - min.y) / diffv,0.0);
+        }
+    }
+    else if (axis * base_axis_y >= angle_epsilon)   {
+        FindMeshCenter(mesh, center, min, max);
+        diffu = max.x - min.x;
+        diffv = max.z - min.z;
+
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D& pos = mesh->mVertices[pnt];
+            out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0);
+        }
+    }
+    else if (axis * base_axis_z >= angle_epsilon)   {
+        FindMeshCenter(mesh, center, min, max);
+        diffu = max.y - min.y;
+        diffv = max.z - min.z;
+
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D& pos = mesh->mVertices[pnt];
+            out[pnt].Set((pos.y - min.y) / diffu,(pos.x - min.x) / diffv,0.0);
+        }
+    }
+    // slower code path in case the mapping axis is not one of the coordinate system axes
+    else
+    {
+        aiMatrix4x4 mTrafo;
+        aiMatrix4x4::FromToMatrix(axis,base_axis_y,mTrafo);
+        FindMeshCenterTransformed(mesh, center, min, max,mTrafo);
+        diffu = max.x - min.x;
+        diffv = max.z - min.z;
+
+        // again the same, except we're applying a transformation now
+        for (unsigned int pnt = 0; pnt < mesh->mNumVertices;++pnt)  {
+            const aiVector3D pos = mTrafo * mesh->mVertices[pnt];
+            out[pnt].Set((pos.x - min.x) / diffu,(pos.z - min.z) / diffv,0.0);
+        }
+    }
+
+    // shouldn't be necessary to remove UV seams ...
+}
+
+// ------------------------------------------------------------------------------------------------
+void ComputeUVMappingProcess::ComputeBoxMapping( aiMesh*, aiVector3D* )
+{
+    ASSIMP_LOG_ERROR("Mapping type currently not implemented");
+}
+
+// ------------------------------------------------------------------------------------------------
+void ComputeUVMappingProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("GenUVCoordsProcess begin");
+    char buffer[1024];
+
+    if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT)
+        throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here");
+
+    std::list<MappingInfo> mappingStack;
+
+    /*  Iterate through all materials and search for non-UV mapped textures
+     */
+    for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
+    {
+        mappingStack.clear();
+        aiMaterial* mat = pScene->mMaterials[i];
+        for (unsigned int a = 0; a < mat->mNumProperties;++a)
+        {
+            aiMaterialProperty* prop = mat->mProperties[a];
+            if (!::strcmp( prop->mKey.data, "$tex.mapping"))
+            {
+                aiTextureMapping& mapping = *((aiTextureMapping*)prop->mData);
+                if (aiTextureMapping_UV != mapping)
+                {
+                    if (!DefaultLogger::isNullLogger())
+                    {
+                        ai_snprintf(buffer, 1024, "Found non-UV mapped texture (%s,%u). Mapping type: %s",
+                            TextureTypeToString((aiTextureType)prop->mSemantic),prop->mIndex,
+                            MappingTypeToString(mapping));
+
+                        ASSIMP_LOG_INFO(buffer);
+                    }
+
+                    if (aiTextureMapping_OTHER == mapping)
+                        continue;
+
+                    MappingInfo info (mapping);
+
+                    // Get further properties - currently only the major axis
+                    for (unsigned int a2 = 0; a2 < mat->mNumProperties;++a2)
+                    {
+                        aiMaterialProperty* prop2 = mat->mProperties[a2];
+                        if (prop2->mSemantic != prop->mSemantic || prop2->mIndex != prop->mIndex)
+                            continue;
+
+                        if ( !::strcmp( prop2->mKey.data, "$tex.mapaxis"))  {
+                            info.axis = *((aiVector3D*)prop2->mData);
+                            break;
+                        }
+                    }
+
+                    unsigned int idx( 99999999 );
+
+                    // Check whether we have this mapping mode already
+                    std::list<MappingInfo>::iterator it = std::find (mappingStack.begin(),mappingStack.end(), info);
+                    if (mappingStack.end() != it)
+                    {
+                        idx = (*it).uv;
+                    }
+                    else
+                    {
+                        /*  We have found a non-UV mapped texture. Now
+                        *   we need to find all meshes using this material
+                        *   that we can compute UV channels for them.
+                        */
+                        for (unsigned int m = 0; m < pScene->mNumMeshes;++m)
+                        {
+                            aiMesh* mesh = pScene->mMeshes[m];
+                            unsigned int outIdx = 0;
+                            if ( mesh->mMaterialIndex != i || ( outIdx = FindEmptyUVChannel(mesh) ) == UINT_MAX ||
+                                !mesh->mNumVertices)
+                            {
+                                continue;
+                            }
+
+                            // Allocate output storage
+                            aiVector3D* p = mesh->mTextureCoords[outIdx] = new aiVector3D[mesh->mNumVertices];
+
+                            switch (mapping)
+                            {
+                            case aiTextureMapping_SPHERE:
+                                ComputeSphereMapping(mesh,info.axis,p);
+                                break;
+                            case aiTextureMapping_CYLINDER:
+                                ComputeCylinderMapping(mesh,info.axis,p);
+                                break;
+                            case aiTextureMapping_PLANE:
+                                ComputePlaneMapping(mesh,info.axis,p);
+                                break;
+                            case aiTextureMapping_BOX:
+                                ComputeBoxMapping(mesh,p);
+                                break;
+                            default:
+                                ai_assert(false);
+                            }
+                            if (m && idx != outIdx)
+                            {
+                                ASSIMP_LOG_WARN("UV index mismatch. Not all meshes assigned to "
+                                    "this material have equal numbers of UV channels. The UV index stored in  "
+                                    "the material structure does therefore not apply for all meshes. ");
+                            }
+                            idx = outIdx;
+                        }
+                        info.uv = idx;
+                        mappingStack.push_back(info);
+                    }
+
+                    // Update the material property list
+                    mapping = aiTextureMapping_UV;
+                    ((aiMaterial*)mat)->AddProperty(&idx,1,AI_MATKEY_UVWSRC(prop->mSemantic,prop->mIndex));
+                }
+            }
+        }
+    }
+    ASSIMP_LOG_DEBUG("GenUVCoordsProcess finished");
+}

+ 148 - 0
thirdparty/assimp/code/ComputeUVMappingProcess.h

@@ -0,0 +1,148 @@
+/*
+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 Defines a post processing step to compute UV coordinates
+  from abstract mappings, such as box or spherical*/
+#ifndef AI_COMPUTEUVMAPPING_H_INC
+#define AI_COMPUTEUVMAPPING_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/mesh.h>
+#include <assimp/material.h>
+#include <assimp/types.h>
+
+class ComputeUVMappingTest;
+
+namespace Assimp {
+
+// ---------------------------------------------------------------------------
+/** ComputeUVMappingProcess - converts special mappings, such as spherical,
+ *  cylindrical or boxed to proper UV coordinates for rendering.
+*/
+class ComputeUVMappingProcess : public BaseProcess
+{
+public:
+    ComputeUVMappingProcess();
+    ~ComputeUVMappingProcess();
+
+public:
+
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag field.
+    * @param pFlags The processing flags the importer was called with. A bitwise
+    *   combination of #aiPostProcessSteps.
+    * @return true if the process is present in this flag fields, false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Computes spherical UV coordinates for a mesh
+     *
+     *  @param mesh Mesh to be processed
+     *  @param axis Main axis
+     *  @param out Receives output UV coordinates
+    */
+    void ComputeSphereMapping(aiMesh* mesh,const aiVector3D& axis,
+        aiVector3D* out);
+
+    // -------------------------------------------------------------------
+    /** Computes cylindrical UV coordinates for a mesh
+     *
+     *  @param mesh Mesh to be processed
+     *  @param axis Main axis
+     *  @param out Receives output UV coordinates
+    */
+    void ComputeCylinderMapping(aiMesh* mesh,const aiVector3D& axis,
+        aiVector3D* out);
+
+    // -------------------------------------------------------------------
+    /** Computes planar UV coordinates for a mesh
+     *
+     *  @param mesh Mesh to be processed
+     *  @param axis Main axis
+     *  @param out Receives output UV coordinates
+    */
+    void ComputePlaneMapping(aiMesh* mesh,const aiVector3D& axis,
+        aiVector3D* out);
+
+    // -------------------------------------------------------------------
+    /** Computes cubic UV coordinates for a mesh
+     *
+     *  @param mesh Mesh to be processed
+     *  @param out Receives output UV coordinates
+    */
+    void ComputeBoxMapping(aiMesh* mesh, aiVector3D* out);
+
+private:
+
+    // temporary structure to describe a mapping
+    struct MappingInfo
+    {
+        explicit MappingInfo(aiTextureMapping _type)
+            : type  (_type)
+            , axis  (0.f,1.f,0.f)
+            , uv    (0u)
+        {}
+
+        aiTextureMapping type;
+        aiVector3D axis;
+        unsigned int uv;
+
+        bool operator== (const MappingInfo& other)
+        {
+            return type == other.type && axis == other.axis;
+        }
+    };
+};
+
+} // end of namespace Assimp
+
+#endif // AI_COMPUTEUVMAPPING_H_INC

+ 414 - 0
thirdparty/assimp/code/ConvertToLHProcess.cpp

@@ -0,0 +1,414 @@
+/*
+---------------------------------------------------------------------------
+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  MakeLeftHandedProcess.cpp
+ *  @brief Implementation of the post processing step to convert all
+ *  imported data to a left-handed coordinate system.
+ *
+ *  Face order & UV flip are also implemented here, for the sake of a
+ *  better location.
+ */
+
+
+#include "ConvertToLHProcess.h"
+#include <assimp/scene.h>
+#include <assimp/postprocess.h>
+#include <assimp/DefaultLogger.hpp>
+
+using namespace Assimp;
+
+#ifndef ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS
+
+namespace {
+
+template <typename aiMeshType>
+void flipUVs(aiMeshType* pMesh) {
+    if (pMesh == nullptr) { return; }
+    // mirror texture y coordinate
+    for (unsigned int tcIdx = 0; tcIdx < AI_MAX_NUMBER_OF_TEXTURECOORDS; tcIdx++) {
+        if (!pMesh->HasTextureCoords(tcIdx)) {
+            break;
+        }
+
+        for (unsigned int vIdx = 0; vIdx < pMesh->mNumVertices; vIdx++) {
+            pMesh->mTextureCoords[tcIdx][vIdx].y = 1.0f - pMesh->mTextureCoords[tcIdx][vIdx].y;
+        }
+    }
+}
+
+} // namespace
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+MakeLeftHandedProcess::MakeLeftHandedProcess()
+: BaseProcess() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+MakeLeftHandedProcess::~MakeLeftHandedProcess() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool MakeLeftHandedProcess::IsActive( unsigned int pFlags) const
+{
+    return 0 != (pFlags & aiProcess_MakeLeftHanded);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void MakeLeftHandedProcess::Execute( aiScene* pScene)
+{
+    // Check for an existent root node to proceed
+    ai_assert(pScene->mRootNode != NULL);
+    ASSIMP_LOG_DEBUG("MakeLeftHandedProcess begin");
+
+    // recursively convert all the nodes
+    ProcessNode( pScene->mRootNode, aiMatrix4x4());
+
+    // process the meshes accordingly
+    for ( unsigned int a = 0; a < pScene->mNumMeshes; ++a ) {
+        ProcessMesh( pScene->mMeshes[ a ] );
+    }
+
+    // process the materials accordingly
+    for ( unsigned int a = 0; a < pScene->mNumMaterials; ++a ) {
+        ProcessMaterial( pScene->mMaterials[ a ] );
+    }
+
+    // transform all animation channels as well
+    for( unsigned int a = 0; a < pScene->mNumAnimations; a++)
+    {
+        aiAnimation* anim = pScene->mAnimations[a];
+        for( unsigned int b = 0; b < anim->mNumChannels; b++)
+        {
+            aiNodeAnim* nodeAnim = anim->mChannels[b];
+            ProcessAnimation( nodeAnim);
+        }
+    }
+    ASSIMP_LOG_DEBUG("MakeLeftHandedProcess finished");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively converts a node, all of its children and all of its meshes
+void MakeLeftHandedProcess::ProcessNode( aiNode* pNode, const aiMatrix4x4& pParentGlobalRotation)
+{
+    // mirror all base vectors at the local Z axis
+    pNode->mTransformation.c1 = -pNode->mTransformation.c1;
+    pNode->mTransformation.c2 = -pNode->mTransformation.c2;
+    pNode->mTransformation.c3 = -pNode->mTransformation.c3;
+    pNode->mTransformation.c4 = -pNode->mTransformation.c4;
+
+    // now invert the Z axis again to keep the matrix determinant positive.
+    // The local meshes will be inverted accordingly so that the result should look just fine again.
+    pNode->mTransformation.a3 = -pNode->mTransformation.a3;
+    pNode->mTransformation.b3 = -pNode->mTransformation.b3;
+    pNode->mTransformation.c3 = -pNode->mTransformation.c3;
+    pNode->mTransformation.d3 = -pNode->mTransformation.d3; // useless, but anyways...
+
+    // continue for all children
+    for( size_t a = 0; a < pNode->mNumChildren; ++a ) {
+        ProcessNode( pNode->mChildren[ a ], pParentGlobalRotation * pNode->mTransformation );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Converts a single mesh to left handed coordinates.
+void MakeLeftHandedProcess::ProcessMesh( aiMesh* pMesh) {
+    if ( nullptr == pMesh ) {
+        ASSIMP_LOG_ERROR( "Nullptr to mesh found." );
+        return;
+    }
+    // mirror positions, normals and stuff along the Z axis
+    for( size_t a = 0; a < pMesh->mNumVertices; ++a)
+    {
+        pMesh->mVertices[a].z *= -1.0f;
+        if (pMesh->HasNormals()) {
+            pMesh->mNormals[a].z *= -1.0f;
+        }
+        if( pMesh->HasTangentsAndBitangents())
+        {
+            pMesh->mTangents[a].z *= -1.0f;
+            pMesh->mBitangents[a].z *= -1.0f;
+        }
+    }
+
+    // mirror anim meshes positions, normals and stuff along the Z axis
+    for (size_t m = 0; m < pMesh->mNumAnimMeshes; ++m)
+    {
+        for (size_t a = 0; a < pMesh->mAnimMeshes[m]->mNumVertices; ++a)
+        {
+            pMesh->mAnimMeshes[m]->mVertices[a].z *= -1.0f;
+            if (pMesh->mAnimMeshes[m]->HasNormals()) {
+                pMesh->mAnimMeshes[m]->mNormals[a].z *= -1.0f;
+            }
+            if (pMesh->mAnimMeshes[m]->HasTangentsAndBitangents())
+            {
+                pMesh->mAnimMeshes[m]->mTangents[a].z *= -1.0f;
+                pMesh->mAnimMeshes[m]->mBitangents[a].z *= -1.0f;
+            }
+        }
+    }
+
+    // mirror offset matrices of all bones
+    for( size_t a = 0; a < pMesh->mNumBones; ++a)
+    {
+        aiBone* bone = pMesh->mBones[a];
+        bone->mOffsetMatrix.a3 = -bone->mOffsetMatrix.a3;
+        bone->mOffsetMatrix.b3 = -bone->mOffsetMatrix.b3;
+        bone->mOffsetMatrix.d3 = -bone->mOffsetMatrix.d3;
+        bone->mOffsetMatrix.c1 = -bone->mOffsetMatrix.c1;
+        bone->mOffsetMatrix.c2 = -bone->mOffsetMatrix.c2;
+        bone->mOffsetMatrix.c4 = -bone->mOffsetMatrix.c4;
+    }
+
+    // mirror bitangents as well as they're derived from the texture coords
+    if( pMesh->HasTangentsAndBitangents())
+    {
+        for( unsigned int a = 0; a < pMesh->mNumVertices; a++)
+            pMesh->mBitangents[a] *= -1.0f;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Converts a single material to left handed coordinates.
+void MakeLeftHandedProcess::ProcessMaterial( aiMaterial* _mat) {
+    if ( nullptr == _mat ) {
+        ASSIMP_LOG_ERROR( "Nullptr to aiMaterial found." );
+        return;
+    }
+
+    aiMaterial* mat = (aiMaterial*)_mat;
+    for (unsigned int a = 0; a < mat->mNumProperties;++a)   {
+        aiMaterialProperty* prop = mat->mProperties[a];
+
+        // Mapping axis for UV mappings?
+        if (!::strcmp( prop->mKey.data, "$tex.mapaxis"))    {
+            ai_assert( prop->mDataLength >= sizeof(aiVector3D)); /* something is wrong with the validation if we end up here */
+            aiVector3D* pff = (aiVector3D*)prop->mData;
+            pff->z *= -1.f;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Converts the given animation to LH coordinates.
+void MakeLeftHandedProcess::ProcessAnimation( aiNodeAnim* pAnim)
+{
+    // position keys
+    for( unsigned int a = 0; a < pAnim->mNumPositionKeys; a++)
+        pAnim->mPositionKeys[a].mValue.z *= -1.0f;
+
+    // rotation keys
+    for( unsigned int a = 0; a < pAnim->mNumRotationKeys; a++)
+    {
+        /* That's the safe version, but the float errors add up. So we try the short version instead
+        aiMatrix3x3 rotmat = pAnim->mRotationKeys[a].mValue.GetMatrix();
+        rotmat.a3 = -rotmat.a3; rotmat.b3 = -rotmat.b3;
+        rotmat.c1 = -rotmat.c1; rotmat.c2 = -rotmat.c2;
+        aiQuaternion rotquat( rotmat);
+        pAnim->mRotationKeys[a].mValue = rotquat;
+        */
+        pAnim->mRotationKeys[a].mValue.x *= -1.0f;
+        pAnim->mRotationKeys[a].mValue.y *= -1.0f;
+    }
+}
+
+#endif // !!  ASSIMP_BUILD_NO_MAKELEFTHANDED_PROCESS
+#ifndef  ASSIMP_BUILD_NO_FLIPUVS_PROCESS
+// # FlipUVsProcess
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+FlipUVsProcess::FlipUVsProcess()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+FlipUVsProcess::~FlipUVsProcess()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool FlipUVsProcess::IsActive( unsigned int pFlags) const
+{
+    return 0 != (pFlags & aiProcess_FlipUVs);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void FlipUVsProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("FlipUVsProcess begin");
+    for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
+        ProcessMesh(pScene->mMeshes[i]);
+
+    for (unsigned int i = 0; i < pScene->mNumMaterials;++i)
+        ProcessMaterial(pScene->mMaterials[i]);
+    ASSIMP_LOG_DEBUG("FlipUVsProcess finished");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Converts a single material
+void FlipUVsProcess::ProcessMaterial (aiMaterial* _mat)
+{
+    aiMaterial* mat = (aiMaterial*)_mat;
+    for (unsigned int a = 0; a < mat->mNumProperties;++a)   {
+        aiMaterialProperty* prop = mat->mProperties[a];
+        if( !prop ) {
+            ASSIMP_LOG_DEBUG( "Property is null" );
+            continue;
+        }
+
+        // UV transformation key?
+        if (!::strcmp( prop->mKey.data, "$tex.uvtrafo"))    {
+            ai_assert( prop->mDataLength >= sizeof(aiUVTransform));  /* something is wrong with the validation if we end up here */
+            aiUVTransform* uv = (aiUVTransform*)prop->mData;
+
+            // just flip it, that's everything
+            uv->mTranslation.y *= -1.f;
+            uv->mRotation *= -1.f;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Converts a single mesh
+void FlipUVsProcess::ProcessMesh( aiMesh* pMesh)
+{
+    flipUVs(pMesh);
+    for (unsigned int idx = 0; idx < pMesh->mNumAnimMeshes; idx++) {
+        flipUVs(pMesh->mAnimMeshes[idx]);
+    }
+}
+
+#endif // !ASSIMP_BUILD_NO_FLIPUVS_PROCESS
+#ifndef  ASSIMP_BUILD_NO_FLIPWINDING_PROCESS
+// # FlipWindingOrderProcess
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+FlipWindingOrderProcess::FlipWindingOrderProcess()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+FlipWindingOrderProcess::~FlipWindingOrderProcess()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool FlipWindingOrderProcess::IsActive( unsigned int pFlags) const
+{
+    return 0 != (pFlags & aiProcess_FlipWindingOrder);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void FlipWindingOrderProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("FlipWindingOrderProcess begin");
+    for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
+        ProcessMesh(pScene->mMeshes[i]);
+    ASSIMP_LOG_DEBUG("FlipWindingOrderProcess finished");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Converts a single mesh
+void FlipWindingOrderProcess::ProcessMesh( aiMesh* pMesh)
+{
+    // invert the order of all faces in this mesh
+    for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
+    {
+        aiFace& face = pMesh->mFaces[a];
+        for (unsigned int b = 0; b < face.mNumIndices / 2; b++) {
+            std::swap(face.mIndices[b], face.mIndices[face.mNumIndices - 1 - b]);
+        }
+    }
+
+    // invert the order of all components in this mesh anim meshes
+    for (unsigned int m = 0; m < pMesh->mNumAnimMeshes; m++) {
+        aiAnimMesh* animMesh = pMesh->mAnimMeshes[m];
+        unsigned int numVertices = animMesh->mNumVertices;
+        if (animMesh->HasPositions()) {
+            for (unsigned int a = 0; a < numVertices; a++)
+            {
+                std::swap(animMesh->mVertices[a], animMesh->mVertices[numVertices - 1 - a]);
+            }
+        }
+        if (animMesh->HasNormals()) {
+            for (unsigned int a = 0; a < numVertices; a++)
+            {
+                std::swap(animMesh->mNormals[a], animMesh->mNormals[numVertices - 1 - a]);
+            }
+        }
+        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; i++) {
+            if (animMesh->HasTextureCoords(i)) {
+                for (unsigned int a = 0; a < numVertices; a++)
+                {
+                    std::swap(animMesh->mTextureCoords[i][a], animMesh->mTextureCoords[i][numVertices - 1 - a]);
+                }
+            }
+        }
+        if (animMesh->HasTangentsAndBitangents()) {
+            for (unsigned int a = 0; a < numVertices; a++)
+            {
+                std::swap(animMesh->mTangents[a], animMesh->mTangents[numVertices - 1 - a]);
+                std::swap(animMesh->mBitangents[a], animMesh->mBitangents[numVertices - 1 - a]);
+            }
+        }
+        for (unsigned int v = 0; v < AI_MAX_NUMBER_OF_COLOR_SETS; v++) {
+            if (animMesh->HasVertexColors(v)) {
+                for (unsigned int a = 0; a < numVertices; a++)
+                {
+                    std::swap(animMesh->mColors[v][a], animMesh->mColors[v][numVertices - 1 - a]);
+                }
+            }
+        }
+    }
+}
+
+#endif // !! ASSIMP_BUILD_NO_FLIPWINDING_PROCESS

+ 170 - 0
thirdparty/assimp/code/ConvertToLHProcess.h

@@ -0,0 +1,170 @@
+/*
+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  MakeLeftHandedProcess.h
+ *  @brief Defines a bunch of post-processing steps to handle
+ *    coordinate system conversions.
+ *
+ *  - LH to RH
+ *  - UV origin upper-left to lower-left
+ *  - face order cw to ccw
+ */
+#ifndef AI_CONVERTTOLHPROCESS_H_INC
+#define AI_CONVERTTOLHPROCESS_H_INC
+
+#include <assimp/types.h>
+#include "BaseProcess.h"
+
+struct aiMesh;
+struct aiNodeAnim;
+struct aiNode;
+struct aiMaterial;
+
+namespace Assimp    {
+
+// -----------------------------------------------------------------------------------
+/** @brief The MakeLeftHandedProcess converts all imported data to a left-handed
+ *   coordinate system.
+ *
+ * This implies a mirroring of the Z axis of the coordinate system. But to keep
+ * transformation matrices free from reflections we shift the reflection to other
+ * places. We mirror the meshes and adapt the rotations.
+ *
+ * @note RH-LH and LH-RH is the same, so this class can be used for both
+ */
+class MakeLeftHandedProcess : public BaseProcess
+{
+
+
+public:
+    MakeLeftHandedProcess();
+    ~MakeLeftHandedProcess();
+
+    // -------------------------------------------------------------------
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    void Execute( aiScene* pScene);
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Recursively converts a node and all of its children
+     */
+    void ProcessNode( aiNode* pNode, const aiMatrix4x4& pParentGlobalRotation);
+
+    // -------------------------------------------------------------------
+    /** Converts a single mesh to left handed coordinates.
+     * This means that positions, normals and tangents are mirrored at
+     * the local Z axis and the order of all faces are inverted.
+     * @param pMesh The mesh to convert.
+     */
+    void ProcessMesh( aiMesh* pMesh);
+
+    // -------------------------------------------------------------------
+    /** Converts a single material to left-handed coordinates
+     * @param pMat Material to convert
+     */
+    void ProcessMaterial( aiMaterial* pMat);
+
+    // -------------------------------------------------------------------
+    /** Converts the given animation to LH coordinates.
+     * The rotation and translation keys are transformed, the scale keys
+     * work in local space and can therefore be left untouched.
+     * @param pAnim The bone animation to transform
+     */
+    void ProcessAnimation( aiNodeAnim* pAnim);
+};
+
+
+// ---------------------------------------------------------------------------
+/** Postprocessing step to flip the face order of the imported data
+ */
+class FlipWindingOrderProcess : public BaseProcess
+{
+    friend class Importer;
+
+public:
+    /** Constructor to be privately used by Importer */
+    FlipWindingOrderProcess();
+
+    /** Destructor, private as well */
+    ~FlipWindingOrderProcess();
+
+    // -------------------------------------------------------------------
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    void Execute( aiScene* pScene);
+
+protected:
+    void ProcessMesh( aiMesh* pMesh);
+};
+
+// ---------------------------------------------------------------------------
+/** Postprocessing step to flip the UV coordinate system of the import data
+ */
+class FlipUVsProcess : public BaseProcess
+{
+    friend class Importer;
+
+public:
+    /** Constructor to be privately used by Importer */
+    FlipUVsProcess();
+
+    /** Destructor, private as well */
+    ~FlipUVsProcess();
+
+    // -------------------------------------------------------------------
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    void Execute( aiScene* pScene);
+
+protected:
+    void ProcessMesh( aiMesh* pMesh);
+    void ProcessMaterial( aiMaterial* mat);
+};
+
+} // end of namespace Assimp
+
+#endif // AI_CONVERTTOLHPROCESS_H_INC

+ 92 - 0
thirdparty/assimp/code/CreateAnimMesh.cpp

@@ -0,0 +1,92 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (C) 2016 The Qt Company Ltd.
+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.
+---------------------------------------------------------------------------
+*/
+
+#include <assimp/CreateAnimMesh.h>
+
+namespace Assimp    {
+
+aiAnimMesh *aiCreateAnimMesh(const aiMesh *mesh)
+{
+    aiAnimMesh *animesh = new aiAnimMesh;
+    animesh->mVertices = NULL;
+    animesh->mNormals = NULL;
+    animesh->mTangents = NULL;
+    animesh->mBitangents = NULL;
+    animesh->mNumVertices = mesh->mNumVertices;
+    if (mesh->mVertices) {
+        animesh->mVertices = new aiVector3D[animesh->mNumVertices];
+        std::memcpy(animesh->mVertices, mesh->mVertices, mesh->mNumVertices * sizeof(aiVector3D));
+    }
+    if (mesh->mNormals) {
+        animesh->mNormals = new aiVector3D[animesh->mNumVertices];
+        std::memcpy(animesh->mNormals, mesh->mNormals, mesh->mNumVertices * sizeof(aiVector3D));
+    }
+    if (mesh->mTangents) {
+        animesh->mTangents = new aiVector3D[animesh->mNumVertices];
+        std::memcpy(animesh->mTangents, mesh->mTangents, mesh->mNumVertices * sizeof(aiVector3D));
+    }
+    if (mesh->mBitangents) {
+        animesh->mBitangents = new aiVector3D[animesh->mNumVertices];
+        std::memcpy(animesh->mBitangents, mesh->mBitangents, mesh->mNumVertices * sizeof(aiVector3D));
+    }
+
+    for (int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) {
+        if (mesh->mColors[i]) {
+            animesh->mColors[i] = new aiColor4D[animesh->mNumVertices];
+            std::memcpy(animesh->mColors[i], mesh->mColors[i], mesh->mNumVertices * sizeof(aiColor4D));
+        } else {
+            animesh->mColors[i] = NULL;
+        }
+    }
+
+    for (int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+        if (mesh->mTextureCoords[i]) {
+            animesh->mTextureCoords[i] = new aiVector3D[animesh->mNumVertices];
+            std::memcpy(animesh->mTextureCoords[i], mesh->mTextureCoords[i], mesh->mNumVertices * sizeof(aiVector3D));
+        } else {
+            animesh->mTextureCoords[i] = NULL;
+        }
+    }
+    return animesh;
+}
+
+} // end of namespace Assimp

+ 465 - 0
thirdparty/assimp/code/DeboneProcess.cpp

@@ -0,0 +1,465 @@
+/*
+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 DeboneProcess.cpp
+/** Implementation of the DeboneProcess post processing step */
+
+
+
+// internal headers of the post-processing framework
+#include "ProcessHelper.h"
+#include "DeboneProcess.h"
+#include <stdio.h>
+
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+DeboneProcess::DeboneProcess()
+{
+    mNumBones = 0;
+    mNumBonesCanDoWithout = 0;
+
+    mThreshold = AI_DEBONE_THRESHOLD;
+    mAllOrNone = false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+DeboneProcess::~DeboneProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool DeboneProcess::IsActive( unsigned int pFlags) const
+{
+    return (pFlags & aiProcess_Debone) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void DeboneProcess::SetupProperties(const Importer* pImp)
+{
+    // get the current value of the property
+    mAllOrNone = pImp->GetPropertyInteger(AI_CONFIG_PP_DB_ALL_OR_NONE,0)?true:false;
+    mThreshold = pImp->GetPropertyFloat(AI_CONFIG_PP_DB_THRESHOLD,AI_DEBONE_THRESHOLD);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void DeboneProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("DeboneProcess begin");
+
+    if(!pScene->mNumMeshes) {
+        return;
+    }
+
+    std::vector<bool> splitList(pScene->mNumMeshes);
+    for( unsigned int a = 0; a < pScene->mNumMeshes; a++) {
+        splitList[a] = ConsiderMesh( pScene->mMeshes[a] );
+    }
+
+    int numSplits = 0;
+
+    if(!!mNumBonesCanDoWithout && (!mAllOrNone||mNumBonesCanDoWithout==mNumBones))  {
+        for(unsigned int a = 0; a < pScene->mNumMeshes; a++)    {
+            if(splitList[a]) {
+                numSplits++;
+            }
+        }
+    }
+
+    if(numSplits)   {
+        // we need to do something. Let's go.
+        //mSubMeshIndices.clear();                  // really needed?
+        mSubMeshIndices.resize(pScene->mNumMeshes); // because we're doing it here anyway
+
+        // build a new array of meshes for the scene
+        std::vector<aiMesh*> meshes;
+
+        for(unsigned int a=0;a<pScene->mNumMeshes;a++)
+        {
+            aiMesh* srcMesh = pScene->mMeshes[a];
+
+            std::vector<std::pair<aiMesh*,const aiBone*> > newMeshes;
+
+            if(splitList[a]) {
+                SplitMesh(srcMesh,newMeshes);
+            }
+
+            // mesh was split
+            if(!newMeshes.empty())  {
+                unsigned int out = 0, in = srcMesh->mNumBones;
+
+                // store new meshes and indices of the new meshes
+                for(unsigned int b=0;b<newMeshes.size();b++)    {
+                    const aiString *find = newMeshes[b].second?&newMeshes[b].second->mName:0;
+
+                    aiNode *theNode = find?pScene->mRootNode->FindNode(*find):0;
+                    std::pair<unsigned int,aiNode*> push_pair(static_cast<unsigned int>(meshes.size()),theNode);
+
+                    mSubMeshIndices[a].push_back(push_pair);
+                    meshes.push_back(newMeshes[b].first);
+
+                    out+=newMeshes[b].first->mNumBones;
+                }
+
+                if(!DefaultLogger::isNullLogger()) {
+                    ASSIMP_LOG_INFO_F("Removed %u bones. Input bones:", in - out, ". Output bones: ", out);
+                }
+
+                // and destroy the source mesh. It should be completely contained inside the new submeshes
+                delete srcMesh;
+            }
+            else    {
+                // Mesh is kept unchanged - store it's new place in the mesh array
+                mSubMeshIndices[a].push_back(std::pair<unsigned int,aiNode*>(static_cast<unsigned int>(meshes.size()),(aiNode*)0));
+                meshes.push_back(srcMesh);
+            }
+        }
+
+        // rebuild the scene's mesh array
+        pScene->mNumMeshes = static_cast<unsigned int>(meshes.size());
+        delete [] pScene->mMeshes;
+        pScene->mMeshes = new aiMesh*[pScene->mNumMeshes];
+        std::copy( meshes.begin(), meshes.end(), pScene->mMeshes);
+
+        // recurse through all nodes and translate the node's mesh indices to fit the new mesh array
+        UpdateNode( pScene->mRootNode);
+    }
+
+    ASSIMP_LOG_DEBUG("DeboneProcess end");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Counts bones total/removable in a given mesh.
+bool DeboneProcess::ConsiderMesh(const aiMesh* pMesh)
+{
+    if(!pMesh->HasBones()) {
+        return false;
+    }
+
+    bool split = false;
+
+    //interstitial faces not permitted
+    bool isInterstitialRequired = false;
+
+    std::vector<bool> isBoneNecessary(pMesh->mNumBones,false);
+    std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX);
+
+    const unsigned int cUnowned = UINT_MAX;
+    const unsigned int cCoowned = UINT_MAX-1;
+
+    for(unsigned int i=0;i<pMesh->mNumBones;i++)    {
+        for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++)   {
+            float w = pMesh->mBones[i]->mWeights[j].mWeight;
+
+            if(w==0.0f) {
+                continue;
+            }
+
+            unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId;
+            if(w>=mThreshold)   {
+
+                if(vertexBones[vid]!=cUnowned)  {
+                    if(vertexBones[vid]==i) //double entry
+                    {
+                        ASSIMP_LOG_WARN("Encountered double entry in bone weights");
+                    }
+                    else //TODO: track attraction in order to break tie
+                    {
+                        vertexBones[vid] = cCoowned;
+                    }
+                }
+                else vertexBones[vid] = i;
+            }
+
+            if(!isBoneNecessary[i]) {
+                isBoneNecessary[i] = w<mThreshold;
+            }
+        }
+
+        if(!isBoneNecessary[i])  {
+            isInterstitialRequired = true;
+        }
+    }
+
+    if(isInterstitialRequired) {
+        for(unsigned int i=0;i<pMesh->mNumFaces;i++) {
+            unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]];
+
+            for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) {
+                unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]];
+
+                if(v!=w)    {
+                    if(v<pMesh->mNumBones) isBoneNecessary[v] = true;
+                    if(w<pMesh->mNumBones) isBoneNecessary[w] = true;
+                }
+            }
+        }
+    }
+
+    for(unsigned int i=0;i<pMesh->mNumBones;i++)    {
+        if(!isBoneNecessary[i]) {
+            mNumBonesCanDoWithout++;
+            split = true;
+        }
+
+        mNumBones++;
+    }
+    return split;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Splits the given mesh by bone count.
+void DeboneProcess::SplitMesh( const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const
+{
+    // same deal here as ConsiderMesh basically
+
+    std::vector<bool> isBoneNecessary(pMesh->mNumBones,false);
+    std::vector<unsigned int> vertexBones(pMesh->mNumVertices,UINT_MAX);
+
+    const unsigned int cUnowned = UINT_MAX;
+    const unsigned int cCoowned = UINT_MAX-1;
+
+    for(unsigned int i=0;i<pMesh->mNumBones;i++)    {
+        for(unsigned int j=0;j<pMesh->mBones[i]->mNumWeights;j++)   {
+            float w = pMesh->mBones[i]->mWeights[j].mWeight;
+
+            if(w==0.0f) {
+                continue;
+            }
+
+            unsigned int vid = pMesh->mBones[i]->mWeights[j].mVertexId;
+
+            if(w>=mThreshold) {
+                if(vertexBones[vid]!=cUnowned)  {
+                    if(vertexBones[vid]==i) //double entry
+                    {
+                        ASSIMP_LOG_WARN("Encountered double entry in bone weights");
+                    }
+                    else //TODO: track attraction in order to break tie
+                    {
+                        vertexBones[vid] = cCoowned;
+                    }
+                }
+                else vertexBones[vid] = i;
+            }
+
+            if(!isBoneNecessary[i]) {
+                isBoneNecessary[i] = w<mThreshold;
+            }
+        }
+    }
+
+    unsigned int nFacesUnowned = 0;
+
+    std::vector<unsigned int> faceBones(pMesh->mNumFaces,UINT_MAX);
+    std::vector<unsigned int> facesPerBone(pMesh->mNumBones,0);
+
+    for(unsigned int i=0;i<pMesh->mNumFaces;i++) {
+        unsigned int nInterstitial = 1;
+
+        unsigned int v = vertexBones[pMesh->mFaces[i].mIndices[0]];
+
+        for(unsigned int j=1;j<pMesh->mFaces[i].mNumIndices;j++) {
+            unsigned int w = vertexBones[pMesh->mFaces[i].mIndices[j]];
+
+            if(v!=w)    {
+                if(v<pMesh->mNumBones) isBoneNecessary[v] = true;
+                if(w<pMesh->mNumBones) isBoneNecessary[w] = true;
+            }
+            else nInterstitial++;
+        }
+
+        if(v<pMesh->mNumBones &&nInterstitial==pMesh->mFaces[i].mNumIndices)    {
+            faceBones[i] = v; //primitive belongs to bone #v
+            facesPerBone[v]++;
+        }
+        else nFacesUnowned++;
+    }
+
+    // invalidate any "cojoined" faces
+    for(unsigned int i=0;i<pMesh->mNumFaces;i++) {
+        if(faceBones[i]<pMesh->mNumBones&&isBoneNecessary[faceBones[i]])
+        {
+            ai_assert(facesPerBone[faceBones[i]]>0);
+            facesPerBone[faceBones[i]]--;
+
+            nFacesUnowned++;
+            faceBones[i] = cUnowned;
+        }
+    }
+
+    if(nFacesUnowned) {
+        std::vector<unsigned int> subFaces;
+
+        for(unsigned int i=0;i<pMesh->mNumFaces;i++)    {
+            if(faceBones[i]==cUnowned) {
+                subFaces.push_back(i);
+            }
+        }
+
+        aiMesh *baseMesh = MakeSubmesh(pMesh,subFaces,0);
+        std::pair<aiMesh*,const aiBone*> push_pair(baseMesh,(const aiBone*)0);
+
+        poNewMeshes.push_back(push_pair);
+    }
+
+    for(unsigned int i=0;i<pMesh->mNumBones;i++) {
+
+        if(!isBoneNecessary[i]&&facesPerBone[i]>0)  {
+            std::vector<unsigned int> subFaces;
+
+            for(unsigned int j=0;j<pMesh->mNumFaces;j++)    {
+                if(faceBones[j]==i) {
+                    subFaces.push_back(j);
+                }
+            }
+
+            unsigned int f = AI_SUBMESH_FLAGS_SANS_BONES;
+            aiMesh *subMesh =MakeSubmesh(pMesh,subFaces,f);
+
+            //Lifted from PretransformVertices.cpp
+            ApplyTransform(subMesh,pMesh->mBones[i]->mOffsetMatrix);
+            std::pair<aiMesh*,const aiBone*> push_pair(subMesh,pMesh->mBones[i]);
+
+            poNewMeshes.push_back(push_pair);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Recursively updates the node's mesh list to account for the changed mesh list
+void DeboneProcess::UpdateNode(aiNode* pNode) const
+{
+    // rebuild the node's mesh index list
+
+    std::vector<unsigned int> newMeshList;
+
+    // this will require two passes
+
+    unsigned int m = static_cast<unsigned int>(pNode->mNumMeshes), n = static_cast<unsigned int>(mSubMeshIndices.size());
+
+    // first pass, look for meshes which have not moved
+
+    for(unsigned int a=0;a<m;a++)   {
+
+        unsigned int srcIndex = pNode->mMeshes[a];
+        const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[srcIndex];
+        unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size());
+
+        for(unsigned int b=0;b<nSubmeshes;b++) {
+            if(!subMeshes[b].second) {
+                newMeshList.push_back(subMeshes[b].first);
+            }
+        }
+    }
+
+    // second pass, collect deboned meshes
+
+    for(unsigned int a=0;a<n;a++)
+    {
+        const std::vector< std::pair< unsigned int,aiNode* > > &subMeshes = mSubMeshIndices[a];
+        unsigned int nSubmeshes = static_cast<unsigned int>(subMeshes.size());
+
+        for(unsigned int b=0;b<nSubmeshes;b++) {
+            if(subMeshes[b].second == pNode)    {
+                newMeshList.push_back(subMeshes[b].first);
+            }
+        }
+    }
+
+    if( pNode->mNumMeshes > 0 ) {
+        delete [] pNode->mMeshes; pNode->mMeshes = NULL;
+    }
+
+    pNode->mNumMeshes = static_cast<unsigned int>(newMeshList.size());
+
+    if(pNode->mNumMeshes)   {
+        pNode->mMeshes = new unsigned int[pNode->mNumMeshes];
+        std::copy( newMeshList.begin(), newMeshList.end(), pNode->mMeshes);
+    }
+
+    // do that also recursively for all children
+    for( unsigned int a = 0; a < pNode->mNumChildren; ++a ) {
+        UpdateNode( pNode->mChildren[a]);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Apply the node transformation to a mesh
+void DeboneProcess::ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const
+{
+    // Check whether we need to transform the coordinates at all
+    if (!mat.IsIdentity()) {
+
+        if (mesh->HasPositions()) {
+            for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                mesh->mVertices[i] = mat * mesh->mVertices[i];
+            }
+        }
+        if (mesh->HasNormals() || mesh->HasTangentsAndBitangents()) {
+            aiMatrix4x4 mWorldIT = mat;
+            mWorldIT.Inverse().Transpose();
+
+            // TODO: implement Inverse() for aiMatrix3x3
+            aiMatrix3x3 m = aiMatrix3x3(mWorldIT);
+
+            if (mesh->HasNormals()) {
+                for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                    mesh->mNormals[i] = (m * mesh->mNormals[i]).Normalize();
+                }
+            }
+            if (mesh->HasTangentsAndBitangents()) {
+                for (unsigned int i = 0; i < mesh->mNumVertices; ++i) {
+                    mesh->mTangents[i]   = (m * mesh->mTangents[i]).Normalize();
+                    mesh->mBitangents[i] = (m * mesh->mBitangents[i]).Normalize();
+                }
+            }
+        }
+    }
+}

+ 134 - 0
thirdparty/assimp/code/DeboneProcess.h

@@ -0,0 +1,134 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+/** Defines a post processing step to limit the number of bones affecting a single vertex. */
+#ifndef AI_DEBONEPROCESS_H_INC
+#define AI_DEBONEPROCESS_H_INC
+
+#include <vector>
+#include <utility>
+#include "BaseProcess.h"
+
+#include <assimp/mesh.h>
+#include <assimp/scene.h>
+
+class DeboneTest;
+
+namespace Assimp
+{
+
+#if (!defined AI_DEBONE_THRESHOLD)
+#   define AI_DEBONE_THRESHOLD  1.0f
+#endif // !! AI_DEBONE_THRESHOLD
+
+// ---------------------------------------------------------------------------
+/** This post processing step removes bones nearly losslessly or according to
+* a configured threshold. In order to remove the bone, the primitives affected by
+* the bone are split from the mesh. The split off (new) mesh is boneless. At any
+* point in time, bones without affect upon a given mesh are to be removed.
+*/
+class DeboneProcess : public BaseProcess
+{
+public:
+
+    DeboneProcess();
+    ~DeboneProcess();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag.
+    * @param pFlags The processing flags the importer was called with.
+    *   A bitwise combination of #aiPostProcessSteps.
+    * @return true if the process is present in this flag fields,
+    *   false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Called prior to ExecuteOnScene().
+    * The function is a request to the process to update its configuration
+    * basing on the Importer's configuration property list.
+    */
+    void SetupProperties(const Importer* pImp);
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+    // -------------------------------------------------------------------
+    /** Counts bones total/removable in a given mesh.
+    * @param pMesh The mesh to process.
+    */
+    bool ConsiderMesh( const aiMesh* pMesh);
+
+    /// Splits the given mesh by bone count.
+    /// @param pMesh the Mesh to split. Is not changed at all, but might be superfluous in case it was split.
+    /// @param poNewMeshes Array of submeshes created in the process. Empty if splitting was not necessary.
+    void SplitMesh(const aiMesh* pMesh, std::vector< std::pair< aiMesh*,const aiBone* > >& poNewMeshes) const;
+
+    /// Recursively updates the node's mesh list to account for the changed mesh list
+    void UpdateNode(aiNode* pNode) const;
+
+    // -------------------------------------------------------------------
+    // Apply transformation to a mesh
+    void ApplyTransform(aiMesh* mesh, const aiMatrix4x4& mat)const;
+
+public:
+    /** Number of bones present in the scene. */
+    unsigned int mNumBones;
+    unsigned int mNumBonesCanDoWithout;
+
+    float mThreshold;
+    bool mAllOrNone;
+
+    /// Per mesh index: Array of indices of the new submeshes.
+    std::vector< std::vector< std::pair< unsigned int,aiNode* > > > mSubMeshIndices;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_DEBONEPROCESS_H_INC

+ 154 - 0
thirdparty/assimp/code/DefaultIOStream.cpp

@@ -0,0 +1,154 @@
+/*
+---------------------------------------------------------------------------
+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  DefaultIOStream.cpp
+ *  @brief Default File I/O implementation for #Importer
+ */
+
+
+#include <assimp/ai_assert.h>
+#include <assimp/DefaultIOStream.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+
+using namespace Assimp;
+
+// ----------------------------------------------------------------------------------
+DefaultIOStream::~DefaultIOStream()
+{
+    if (mFile) {
+        ::fclose(mFile);
+        mFile = nullptr;
+    }
+}
+
+// ----------------------------------------------------------------------------------
+size_t DefaultIOStream::Read(void* pvBuffer,
+    size_t pSize,
+    size_t pCount)
+{
+    ai_assert(NULL != pvBuffer && 0 != pSize && 0 != pCount);
+    return (mFile ? ::fread(pvBuffer, pSize, pCount, mFile) : 0);
+}
+
+// ----------------------------------------------------------------------------------
+size_t DefaultIOStream::Write(const void* pvBuffer,
+    size_t pSize,
+    size_t pCount)
+{
+    ai_assert(NULL != pvBuffer && 0 != pSize && 0 != pCount);
+    return (mFile ? ::fwrite(pvBuffer, pSize, pCount, mFile) : 0);
+}
+
+// ----------------------------------------------------------------------------------
+aiReturn DefaultIOStream::Seek(size_t pOffset,
+     aiOrigin pOrigin)
+{
+    if (!mFile) {
+        return AI_FAILURE;
+    }
+
+    // Just to check whether our enum maps one to one with the CRT constants
+    static_assert(aiOrigin_CUR == SEEK_CUR &&
+        aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET, "aiOrigin_CUR == SEEK_CUR && \
+        aiOrigin_END == SEEK_END && aiOrigin_SET == SEEK_SET");
+
+    // do the seek
+    return (0 == ::fseek(mFile, (long)pOffset,(int)pOrigin) ? AI_SUCCESS : AI_FAILURE);
+}
+
+// ----------------------------------------------------------------------------------
+size_t DefaultIOStream::Tell() const
+{
+    if (!mFile) {
+        return 0;
+    }
+    return ::ftell(mFile);
+}
+
+// ----------------------------------------------------------------------------------
+size_t DefaultIOStream::FileSize() const
+{
+    if (! mFile || mFilename.empty()) {
+        return 0;
+    }
+
+    if (SIZE_MAX == mCachedSize ) {
+
+        // Although fseek/ftell would allow us to reuse the existing file handle here,
+        // it is generally unsafe because:
+        //  - For binary streams, it is not technically well-defined
+        //  - For text files the results are meaningless
+        // That's why we use the safer variant fstat here.
+        //
+        // See here for details:
+        // https://www.securecoding.cert.org/confluence/display/seccode/FIO19-C.+Do+not+use+fseek()+and+ftell()+to+compute+the+size+of+a+regular+file
+#if defined _WIN32 && (!defined __GNUC__ || __MSVCRT_VERSION__ >= 0x0601)
+        struct __stat64 fileStat;
+        //using fileno + fstat avoids having to handle the filename
+        int err = _fstat64(  _fileno(mFile), &fileStat );
+        if (0 != err)
+            return 0;
+        mCachedSize = (size_t) (fileStat.st_size);
+#elif defined __GNUC__ || defined __APPLE__ || defined __MACH__ || defined __FreeBSD__
+        struct stat fileStat;
+        int err = stat(mFilename.c_str(), &fileStat );
+        if (0 != err)
+            return 0;
+        const unsigned long long cachedSize = fileStat.st_size;
+        mCachedSize = static_cast< size_t >( cachedSize );
+#else
+#   error "Unknown platform"
+#endif
+    }
+    return mCachedSize;
+}
+
+// ----------------------------------------------------------------------------------
+void DefaultIOStream::Flush()
+{
+    if (mFile) {
+        ::fflush(mFile);
+    }
+}
+
+// ----------------------------------------------------------------------------------

+ 257 - 0
thirdparty/assimp/code/DefaultIOSystem.cpp

@@ -0,0 +1,257 @@
+/*
+---------------------------------------------------------------------------
+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 Default implementation of IOSystem using the standard C file functions */
+
+#include <assimp/StringComparison.h>
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/DefaultIOStream.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/ai_assert.h>
+#include <stdlib.h>
+
+#ifdef __unix__
+#include <sys/param.h>
+#include <stdlib.h>
+#endif
+
+#ifdef _WIN32
+#include <windows.h>
+#endif
+
+using namespace Assimp;
+
+// maximum path length
+// XXX http://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
+#ifdef PATH_MAX
+#   define PATHLIMIT PATH_MAX
+#else
+#   define PATHLIMIT 4096
+#endif
+
+// ------------------------------------------------------------------------------------------------
+// Tests for the existence of a file at the given path.
+bool DefaultIOSystem::Exists( const char* pFile) const
+{
+#ifdef _WIN32
+    wchar_t fileName16[PATHLIMIT];
+
+#ifndef WindowsStore
+    bool isUnicode = IsTextUnicode(pFile, static_cast<int>(strlen(pFile)), NULL) != 0;
+    if (isUnicode) {
+
+        MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, pFile, -1, fileName16, PATHLIMIT);
+        struct __stat64 filestat;
+        if (0 != _wstat64(fileName16, &filestat)) {
+            return false;
+        }
+    } else {
+#endif
+        FILE* file = ::fopen(pFile, "rb");
+        if (!file)
+            return false;
+
+        ::fclose(file);
+#ifndef WindowsStore
+    }
+#endif
+#else
+    FILE* file = ::fopen( pFile, "rb");
+    if( !file)
+        return false;
+
+    ::fclose( file);
+#endif
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Open a new file with a given path.
+IOStream* DefaultIOSystem::Open( const char* strFile, const char* strMode)
+{
+    ai_assert(NULL != strFile);
+    ai_assert(NULL != strMode);
+    FILE* file;
+#ifdef _WIN32
+    wchar_t fileName16[PATHLIMIT];
+#ifndef WindowsStore
+    bool isUnicode = IsTextUnicode(strFile, static_cast<int>(strlen(strFile)), NULL) != 0;
+    if (isUnicode) {
+        MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, strFile, -1, fileName16, PATHLIMIT);
+        std::string mode8(strMode);
+        file = ::_wfopen(fileName16, std::wstring(mode8.begin(), mode8.end()).c_str());
+    } else {
+#endif
+        file = ::fopen(strFile, strMode);
+#ifndef WindowsStore
+    }
+#endif
+#else
+    file = ::fopen(strFile, strMode);
+#endif
+    if (nullptr == file)
+        return nullptr;
+
+    return new DefaultIOStream(file, (std::string) strFile);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Closes the given file and releases all resources associated with it.
+void DefaultIOSystem::Close( IOStream* pFile)
+{
+    delete pFile;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns the operation specific directory separator
+char DefaultIOSystem::getOsSeparator() const
+{
+#ifndef _WIN32
+    return '/';
+#else
+    return '\\';
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+// IOSystem default implementation (ComparePaths isn't a pure virtual function)
+bool IOSystem::ComparePaths (const char* one, const char* second) const
+{
+    return !ASSIMP_stricmp(one,second);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Convert a relative path into an absolute path
+inline static void MakeAbsolutePath (const char* in, char* _out)
+{
+    ai_assert(in && _out);
+#if defined( _MSC_VER ) || defined( __MINGW32__ )
+#ifndef WindowsStore
+    bool isUnicode = IsTextUnicode(in, static_cast<int>(strlen(in)), NULL) != 0;
+    if (isUnicode) {
+        wchar_t out16[PATHLIMIT];
+        wchar_t in16[PATHLIMIT];
+        MultiByteToWideChar(CP_UTF8, MB_PRECOMPOSED, in, -1, out16, PATHLIMIT);
+        wchar_t* ret = ::_wfullpath(out16, in16, PATHLIMIT);
+        if (ret) {
+            WideCharToMultiByte(CP_UTF8, MB_PRECOMPOSED, out16, -1, _out, PATHLIMIT, nullptr, nullptr);
+        }
+        if (!ret) {
+            // preserve the input path, maybe someone else is able to fix
+            // the path before it is accessed (e.g. our file system filter)
+            ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
+            strcpy(_out, in);
+        }
+
+    } else {
+#endif
+        char* ret = :: _fullpath(_out, in, PATHLIMIT);
+        if (!ret) {
+            // preserve the input path, maybe someone else is able to fix
+            // the path before it is accessed (e.g. our file system filter)
+            ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
+            strcpy(_out, in);
+        }
+#ifndef WindowsStore
+    }
+#endif
+#else
+    // use realpath
+    char* ret = realpath(in, _out);
+    if(!ret) {
+        // preserve the input path, maybe someone else is able to fix
+        // the path before it is accessed (e.g. our file system filter)
+        ASSIMP_LOG_WARN_F("Invalid path: ", std::string(in));
+        strcpy(_out,in);
+    }
+#endif
+}
+
+// ------------------------------------------------------------------------------------------------
+// DefaultIOSystem's more specialized implementation
+bool DefaultIOSystem::ComparePaths (const char* one, const char* second) const
+{
+    // chances are quite good both paths are formatted identically,
+    // so we can hopefully return here already
+    if( !ASSIMP_stricmp(one,second) )
+        return true;
+
+    char temp1[PATHLIMIT];
+    char temp2[PATHLIMIT];
+
+    MakeAbsolutePath (one, temp1);
+    MakeAbsolutePath (second, temp2);
+
+    return !ASSIMP_stricmp(temp1,temp2);
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string DefaultIOSystem::fileName( const std::string &path )
+{
+    std::string ret = path;
+    std::size_t last = ret.find_last_of("\\/");
+    if (last != std::string::npos) ret = ret.substr(last + 1);
+    return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string DefaultIOSystem::completeBaseName( const std::string &path )
+{
+    std::string ret = fileName(path);
+    std::size_t pos = ret.find_last_of('.');
+    if(pos != ret.npos) ret = ret.substr(0, pos);
+    return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string DefaultIOSystem::absolutePath( const std::string &path )
+{
+    std::string ret = path;
+    std::size_t last = ret.find_last_of("\\/");
+    if (last != std::string::npos) ret = ret.substr(0, last);
+    return ret;
+}
+
+// ------------------------------------------------------------------------------------------------
+
+#undef PATHLIMIT

+ 418 - 0
thirdparty/assimp/code/DefaultLogger.cpp

@@ -0,0 +1,418 @@
+/*
+---------------------------------------------------------------------------
+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  DefaultLogger.cpp
+ *  @brief Implementation of DefaultLogger (and Logger)
+ */
+
+// Default log streams
+#include "Win32DebugLogStream.h"
+#include "StdOStreamLogStream.h"
+#include "FileLogStream.h"
+#include <assimp/StringUtils.h>
+
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/NullLogger.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/ai_assert.h>
+#include <iostream>
+#include <stdio.h>
+
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+#   include <thread>
+#   include <mutex>
+    std::mutex loggerMutex;
+#endif
+
+namespace Assimp    {
+
+// ----------------------------------------------------------------------------------
+NullLogger DefaultLogger::s_pNullLogger;
+Logger *DefaultLogger::m_pLogger = &DefaultLogger::s_pNullLogger;
+
+static const unsigned int SeverityAll = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging;
+
+// ----------------------------------------------------------------------------------
+// Represents a log-stream + its error severity
+struct LogStreamInfo {
+    unsigned int  m_uiErrorSeverity;
+    LogStream    *m_pStream;
+
+    // Constructor
+    LogStreamInfo( unsigned int uiErrorSev, LogStream *pStream ) :
+        m_uiErrorSeverity( uiErrorSev ),
+        m_pStream( pStream ) {
+        // empty
+    }
+
+    // Destructor
+    ~LogStreamInfo() {
+        delete m_pStream;
+    }
+};
+
+// ----------------------------------------------------------------------------------
+// Construct a default log stream
+LogStream* LogStream::createDefaultStream(aiDefaultLogStream    streams,
+    const char* name /*= "AssimpLog.txt"*/,
+    IOSystem* io            /*= NULL*/)
+{
+    switch (streams)
+    {
+        // This is a platform-specific feature
+    case aiDefaultLogStream_DEBUGGER:
+#ifdef WIN32
+        return new Win32DebugLogStream();
+#else
+        return nullptr;
+#endif
+
+        // Platform-independent default streams
+    case aiDefaultLogStream_STDERR:
+        return new StdOStreamLogStream(std::cerr);
+    case aiDefaultLogStream_STDOUT:
+        return new StdOStreamLogStream(std::cout);
+    case aiDefaultLogStream_FILE:
+        return (name && *name ? new FileLogStream(name,io) : nullptr );
+    default:
+        // We don't know this default log stream, so raise an assertion
+        ai_assert(false);
+
+    };
+
+    // For compilers without dead code path detection
+    return NULL;
+}
+
+// ----------------------------------------------------------------------------------
+//  Creates the only singleton instance
+Logger *DefaultLogger::create(const char* name /*= "AssimpLog.txt"*/,
+    LogSeverity severity                       /*= NORMAL*/,
+    unsigned int defStreams                    /*= aiDefaultLogStream_DEBUGGER | aiDefaultLogStream_FILE*/,
+    IOSystem* io                               /*= NULL*/) {
+    // enter the mutex here to avoid concurrency problems
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+    std::lock_guard<std::mutex> lock(loggerMutex);
+#endif
+
+    if ( m_pLogger && !isNullLogger() ) {
+        delete m_pLogger;
+    }
+
+    m_pLogger = new DefaultLogger( severity );
+
+    // Attach default log streams
+    // Stream the log to the MSVC debugger?
+    if ( defStreams & aiDefaultLogStream_DEBUGGER ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_DEBUGGER ) );
+    }
+
+    // Stream the log to COUT?
+    if ( defStreams & aiDefaultLogStream_STDOUT ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_STDOUT ) );
+    }
+
+    // Stream the log to CERR?
+    if ( defStreams & aiDefaultLogStream_STDERR ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_STDERR ) );
+    }
+
+    // Stream the log to a file
+    if ( defStreams & aiDefaultLogStream_FILE && name && *name ) {
+        m_pLogger->attachStream( LogStream::createDefaultStream( aiDefaultLogStream_FILE, name, io ) );
+    }
+
+    return m_pLogger;
+}
+
+// ----------------------------------------------------------------------------------
+void Logger::debug(const char* message) {
+
+    // SECURITY FIX: otherwise it's easy to produce overruns since
+    // sometimes importers will include data from the input file
+    // (i.e. node names) in their messages.
+    if (strlen(message)>MAX_LOG_MESSAGE_LENGTH) {
+        return;
+    }
+    return OnDebug(message);
+}
+
+// ----------------------------------------------------------------------------------
+void Logger::info(const char* message)  {
+
+    // SECURITY FIX: see above
+    if (strlen(message)>MAX_LOG_MESSAGE_LENGTH) {
+        return;
+    }
+    return OnInfo(message);
+}
+
+// ----------------------------------------------------------------------------------
+void Logger::warn(const char* message)  {
+
+    // SECURITY FIX: see above
+    if (strlen(message)>MAX_LOG_MESSAGE_LENGTH) {
+        return;
+    }
+    return OnWarn(message);
+}
+
+// ----------------------------------------------------------------------------------
+void Logger::error(const char* message) {
+    // SECURITY FIX: see above
+    if (strlen(message)>MAX_LOG_MESSAGE_LENGTH) {
+        return;
+    }
+    return OnError(message);
+}
+
+// ----------------------------------------------------------------------------------
+void DefaultLogger::set( Logger *logger ) {
+    // enter the mutex here to avoid concurrency problems
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+    std::lock_guard<std::mutex> lock(loggerMutex);
+#endif
+
+    if ( nullptr == logger ) {
+        logger = &s_pNullLogger;
+    }
+    if ( nullptr != m_pLogger && !isNullLogger() ) {
+        delete m_pLogger;
+    }
+
+    DefaultLogger::m_pLogger = logger;
+}
+
+// ----------------------------------------------------------------------------------
+bool DefaultLogger::isNullLogger() {
+    return m_pLogger == &s_pNullLogger;
+}
+
+// ----------------------------------------------------------------------------------
+Logger *DefaultLogger::get() {
+    return m_pLogger;
+}
+
+// ----------------------------------------------------------------------------------
+//  Kills the only instance
+void DefaultLogger::kill() {
+    // enter the mutex here to avoid concurrency problems
+#ifndef ASSIMP_BUILD_SINGLETHREADED
+    std::lock_guard<std::mutex> lock(loggerMutex);
+#endif
+
+	if ( m_pLogger == &s_pNullLogger ) {
+		return;
+	}
+    delete m_pLogger;
+    m_pLogger = &s_pNullLogger;
+}
+
+// ----------------------------------------------------------------------------------
+//  Debug message
+void DefaultLogger::OnDebug( const char* message ) {
+    if ( m_Severity == Logger::NORMAL ) {
+        return;
+    }
+
+	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
+	char msg[Size];
+	ai_snprintf(msg, Size, "Debug, T%u: %s", GetThreadID(), message);
+
+    WriteToStreams( msg, Logger::Debugging );
+}
+
+// ----------------------------------------------------------------------------------
+//  Logs an info
+void DefaultLogger::OnInfo( const char* message ){
+	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
+	char msg[Size];
+    ai_snprintf(msg, Size, "Info,  T%u: %s", GetThreadID(), message );
+
+    WriteToStreams( msg , Logger::Info );
+}
+
+// ----------------------------------------------------------------------------------
+//  Logs a warning
+void DefaultLogger::OnWarn( const char* message ) {
+	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
+	char msg[Size];
+	ai_snprintf(msg, Size, "Warn,  T%u: %s", GetThreadID(), message );
+
+    WriteToStreams( msg, Logger::Warn );
+}
+
+// ----------------------------------------------------------------------------------
+//  Logs an error
+void DefaultLogger::OnError( const char* message ) {
+	static const size_t Size = MAX_LOG_MESSAGE_LENGTH + 16;
+	char msg[ Size ];
+    ai_snprintf(msg, Size, "Error, T%u: %s", GetThreadID(), message );
+
+    WriteToStreams( msg, Logger::Err );
+}
+
+// ----------------------------------------------------------------------------------
+//  Will attach a new stream
+bool DefaultLogger::attachStream( LogStream *pStream, unsigned int severity ) {
+    if ( nullptr == pStream ) {
+        return false;
+    }
+
+    if (0 == severity)  {
+        severity = Logger::Info | Logger::Err | Logger::Warn | Logger::Debugging;
+    }
+
+    for ( StreamIt it = m_StreamArray.begin();
+        it != m_StreamArray.end();
+        ++it )
+    {
+        if ( (*it)->m_pStream == pStream ) {
+            (*it)->m_uiErrorSeverity |= severity;
+            return true;
+        }
+    }
+
+    LogStreamInfo *pInfo = new LogStreamInfo( severity, pStream );
+    m_StreamArray.push_back( pInfo );
+    return true;
+}
+
+// ----------------------------------------------------------------------------------
+//  Detach a stream
+bool DefaultLogger::detatchStream( LogStream *pStream, unsigned int severity ) {
+    if ( nullptr == pStream ) {
+        return false;
+    }
+
+    if (0 == severity)  {
+        severity = SeverityAll;
+    }
+
+    bool res( false );
+    for ( StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it ) {
+        if ( (*it)->m_pStream == pStream ) {
+            (*it)->m_uiErrorSeverity &= ~severity;
+            if ( (*it)->m_uiErrorSeverity == 0 ) {
+                // don't delete the underlying stream 'cause the caller gains ownership again
+                (**it).m_pStream = nullptr;
+                delete *it;
+                m_StreamArray.erase( it );
+                res = true;
+                break;
+            }
+            return true;
+        }
+    }
+    return res;
+}
+
+// ----------------------------------------------------------------------------------
+//  Constructor
+DefaultLogger::DefaultLogger(LogSeverity severity)
+    :   Logger  ( severity )
+    ,   noRepeatMsg (false)
+    ,   lastLen( 0 ) {
+    lastMsg[0] = '\0';
+}
+
+// ----------------------------------------------------------------------------------
+//  Destructor
+DefaultLogger::~DefaultLogger() {
+    for ( StreamIt it = m_StreamArray.begin(); it != m_StreamArray.end(); ++it ) {
+        // also frees the underlying stream, we are its owner.
+        delete *it;
+    }
+}
+
+// ----------------------------------------------------------------------------------
+//  Writes message to stream
+void DefaultLogger::WriteToStreams(const char *message, ErrorSeverity ErrorSev ) {
+    ai_assert(nullptr != message);
+
+    // Check whether this is a repeated message
+    if (! ::strncmp( message,lastMsg, lastLen-1))
+    {
+        if (!noRepeatMsg)
+        {
+            noRepeatMsg = true;
+            message = "Skipping one or more lines with the same contents\n";
+        }
+        else return;
+    }
+    else
+    {
+        // append a new-line character to the message to be printed
+        lastLen = ::strlen(message);
+        ::memcpy(lastMsg,message,lastLen+1);
+        ::strcat(lastMsg+lastLen,"\n");
+
+        message = lastMsg;
+        noRepeatMsg = false;
+        ++lastLen;
+    }
+    for ( ConstStreamIt it = m_StreamArray.begin();
+        it != m_StreamArray.end();
+        ++it)
+    {
+        if ( ErrorSev & (*it)->m_uiErrorSeverity )
+            (*it)->m_pStream->write( message);
+    }
+}
+
+// ----------------------------------------------------------------------------------
+//  Returns thread id, if not supported only a zero will be returned.
+unsigned int DefaultLogger::GetThreadID()
+{
+    // fixme: we can get this value via std::threads
+    // std::this_thread::get_id().hash() returns a (big) size_t, not sure if this is useful in this case.
+#ifdef WIN32
+    return (unsigned int)::GetCurrentThreadId();
+#else
+    return 0; // not supported
+#endif
+}
+
+// ----------------------------------------------------------------------------------
+
+} // !namespace Assimp

+ 65 - 0
thirdparty/assimp/code/DefaultProgressHandler.h

@@ -0,0 +1,65 @@
+/*
+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 ProgressHandler.hpp
+ *  @brief Abstract base class 'ProgressHandler'.
+ */
+#ifndef INCLUDED_AI_DEFAULTPROGRESSHANDLER_H
+#define INCLUDED_AI_DEFAULTPROGRESSHANDLER_H
+
+#include <assimp/ProgressHandler.hpp>
+
+namespace Assimp    {
+
+// ------------------------------------------------------------------------------------
+/** @brief Internal default implementation of the #ProgressHandler interface. */
+class DefaultProgressHandler : public ProgressHandler    {
+
+    virtual bool Update(float /*percentage*/) {
+        return false;
+    }
+
+
+}; // !class DefaultProgressHandler
+} // Namespace Assimp
+
+#endif

+ 109 - 0
thirdparty/assimp/code/DropFaceNormalsProcess.cpp

@@ -0,0 +1,109 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of the post processing step to drop face
+* normals for all imported faces.
+*/
+
+
+#include "DropFaceNormalsProcess.h"
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Exceptional.h>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+DropFaceNormalsProcess::DropFaceNormalsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+DropFaceNormalsProcess::~DropFaceNormalsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool DropFaceNormalsProcess::IsActive( unsigned int pFlags) const {
+    return  (pFlags & aiProcess_DropNormals) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void DropFaceNormalsProcess::Execute( aiScene* pScene) {
+    ASSIMP_LOG_DEBUG("DropFaceNormalsProcess begin");
+
+    if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) {
+        throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here");
+    }
+
+    bool bHas = false;
+    for( unsigned int a = 0; a < pScene->mNumMeshes; a++) {
+        bHas |= this->DropMeshFaceNormals( pScene->mMeshes[a]);
+    }
+    if (bHas)   {
+        ASSIMP_LOG_INFO("DropFaceNormalsProcess finished. "
+            "Face normals have been removed");
+    } else {
+        ASSIMP_LOG_DEBUG("DropFaceNormalsProcess finished. "
+            "No normals were present");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+bool DropFaceNormalsProcess::DropMeshFaceNormals (aiMesh* pMesh) {
+    if (NULL == pMesh->mNormals) {
+        return false;
+    }
+    
+    delete[] pMesh->mNormals;
+    pMesh->mNormals = nullptr;
+    return true;
+}

+ 86 - 0
thirdparty/assimp/code/DropFaceNormalsProcess.h

@@ -0,0 +1,86 @@
+/*
+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 Defines a post processing step to compute face normals for all loaded faces*/
+#ifndef AI_DROPFACENORMALPROCESS_H_INC
+#define AI_DROPFACENORMALPROCESS_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/mesh.h>
+
+namespace Assimp
+{
+
+// ---------------------------------------------------------------------------
+/** The DropFaceNormalsProcess computes face normals for all faces of all meshes
+*/
+class ASSIMP_API_WINONLY DropFaceNormalsProcess : public BaseProcess
+{
+public:
+
+    DropFaceNormalsProcess();
+    ~DropFaceNormalsProcess();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag field.
+    * @param pFlags The processing flags the importer was called with. A bitwise
+    *   combination of #aiPostProcessSteps.
+    * @return true if the process is present in this flag fields, false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+
+private:
+    bool DropMeshFaceNormals(aiMesh* pcMesh);
+};
+
+} // end of namespace Assimp
+
+#endif // !!AI_DROPFACENORMALPROCESS_H_INC

+ 152 - 0
thirdparty/assimp/code/EmbedTexturesProcess.cpp

@@ -0,0 +1,152 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+#include "EmbedTexturesProcess.h"
+#include <assimp/ParsingUtils.h>
+#include "ProcessHelper.h"
+
+#include <fstream>
+
+using namespace Assimp;
+
+EmbedTexturesProcess::EmbedTexturesProcess()
+: BaseProcess() {
+}
+
+EmbedTexturesProcess::~EmbedTexturesProcess() {
+}
+
+bool EmbedTexturesProcess::IsActive(unsigned int pFlags) const {
+    return (pFlags & aiProcess_EmbedTextures) != 0;
+}
+
+void EmbedTexturesProcess::SetupProperties(const Importer* pImp) {
+    mRootPath = pImp->GetPropertyString("sourceFilePath");
+    mRootPath = mRootPath.substr(0, mRootPath.find_last_of("\\/") + 1u);
+}
+
+void EmbedTexturesProcess::Execute(aiScene* pScene) {
+    if (pScene == nullptr || pScene->mRootNode == nullptr) return;
+
+    aiString path;
+
+    uint32_t embeddedTexturesCount = 0u;
+
+    for (auto matId = 0u; matId < pScene->mNumMaterials; ++matId) {
+        auto material = pScene->mMaterials[matId];
+
+        for (auto ttId = 1u; ttId < AI_TEXTURE_TYPE_MAX; ++ttId) {
+            auto tt = static_cast<aiTextureType>(ttId);
+            auto texturesCount = material->GetTextureCount(tt);
+
+            for (auto texId = 0u; texId < texturesCount; ++texId) {
+                material->GetTexture(tt, texId, &path);
+                if (path.data[0] == '*') continue; // Already embedded
+
+                // Indeed embed
+                if (addTexture(pScene, path.data)) {
+                    auto embeddedTextureId = pScene->mNumTextures - 1u;
+                    ::ai_snprintf(path.data, 1024, "*%u", embeddedTextureId);
+                    material->AddProperty(&path, AI_MATKEY_TEXTURE(tt, texId));
+                    embeddedTexturesCount++;
+                }
+            }
+        }
+    }
+
+    ASSIMP_LOG_INFO_F("EmbedTexturesProcess finished. Embedded ", embeddedTexturesCount, " textures." );
+}
+
+bool EmbedTexturesProcess::addTexture(aiScene* pScene, std::string path) const {
+    std::streampos imageSize = 0;
+    std::string    imagePath = path;
+
+    // Test path directly
+    std::ifstream file(imagePath, std::ios::binary | std::ios::ate);
+    if ((imageSize = file.tellg()) == std::streampos(-1)) {
+        ASSIMP_LOG_WARN_F("EmbedTexturesProcess: Cannot find image: ", imagePath, ". Will try to find it in root folder.");
+
+        // Test path in root path
+        imagePath = mRootPath + path;
+        file.open(imagePath, std::ios::binary | std::ios::ate);
+        if ((imageSize = file.tellg()) == std::streampos(-1)) {
+            // Test path basename in root path
+            imagePath = mRootPath + path.substr(path.find_last_of("\\/") + 1u);
+            file.open(imagePath, std::ios::binary | std::ios::ate);
+            if ((imageSize = file.tellg()) == std::streampos(-1)) {
+                ASSIMP_LOG_ERROR_F("EmbedTexturesProcess: Unable to embed texture: ", path, ".");
+                return false;
+            }
+        }
+    }
+
+    aiTexel* imageContent = new aiTexel[ 1ul + static_cast<unsigned long>( imageSize ) / sizeof(aiTexel)];
+    file.seekg(0, std::ios::beg);
+    file.read(reinterpret_cast<char*>(imageContent), imageSize);
+
+    // Enlarging the textures table
+    unsigned int textureId = pScene->mNumTextures++;
+    auto oldTextures = pScene->mTextures;
+    pScene->mTextures = new aiTexture*[pScene->mNumTextures];
+    ::memmove(pScene->mTextures, oldTextures, sizeof(aiTexture*) * (pScene->mNumTextures - 1u));
+
+    // Add the new texture
+    auto pTexture = new aiTexture;
+    pTexture->mHeight = 0; // Means that this is still compressed
+    pTexture->mWidth = static_cast<uint32_t>(imageSize);
+    pTexture->pcData = imageContent;
+
+    auto extension = path.substr(path.find_last_of('.') + 1u);
+    std::transform(extension.begin(), extension.end(), extension.begin(), ::tolower);
+    if (extension == "jpeg") {
+        extension = "jpg";
+    }
+
+    size_t len = extension.size();
+    if (len > HINTMAXTEXTURELEN -1 ) {
+        len = HINTMAXTEXTURELEN - 1;
+    }
+    ::strncpy(pTexture->achFormatHint, extension.c_str(), len);
+    pScene->mTextures[textureId] = pTexture;
+
+    return true;
+}

+ 85 - 0
thirdparty/assimp/code/EmbedTexturesProcess.h

@@ -0,0 +1,85 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+#pragma once
+
+#include "BaseProcess.h"
+
+#include <string>
+
+struct aiNode;
+
+namespace Assimp {
+
+/**
+ *  Force embedding of textures (using the path = "*1" convention).
+ *  If a texture's file does not exist at the specified path
+ *  (due, for instance, to an absolute path generated on another system),
+ *  it will check if a file with the same name exists at the root folder
+ *  of the imported model. And if so, it uses that.
+ */
+class ASSIMP_API EmbedTexturesProcess : public BaseProcess {
+public:
+    /// The default class constructor.
+    EmbedTexturesProcess();
+
+    /// The class destructor.
+    virtual ~EmbedTexturesProcess();
+
+    /// Overwritten, @see BaseProcess
+    virtual bool IsActive(unsigned int pFlags) const;
+
+    /// Overwritten, @see BaseProcess
+    virtual void SetupProperties(const Importer* pImp);
+
+    /// Overwritten, @see BaseProcess
+    virtual void Execute(aiScene* pScene);
+
+private:
+    // Resolve the path and add the file content to the scene as a texture.
+    bool addTexture(aiScene* pScene, std::string path) const;
+
+private:
+    std::string mRootPath;
+};
+
+} // namespace Assimp

+ 648 - 0
thirdparty/assimp/code/Exporter.cpp

@@ -0,0 +1,648 @@
+/*
+---------------------------------------------------------------------------
+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 Exporter.cpp
+
+Assimp export interface. While it's public interface bears many similarities
+to the import interface (in fact, it is largely symmetric), the internal
+implementations differs a lot. Exporters are considered stateless and are
+simple callbacks which we maintain in a global list along with their
+description strings.
+
+Here we implement only the C++ interface (Assimp::Exporter).
+*/
+
+#ifndef ASSIMP_BUILD_NO_EXPORT
+
+#include <assimp/BlobIOSystem.h>
+#include <assimp/SceneCombiner.h>
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Exporter.hpp>
+#include <assimp/mesh.h>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+
+#include "DefaultProgressHandler.h"
+#include "BaseProcess.h"
+#include "JoinVerticesProcess.h"
+#include "MakeVerboseFormat.h"
+#include "ConvertToLHProcess.h"
+#include "PretransformVertices.h"
+#include <assimp/Exceptional.h>
+#include "ScenePrivate.h"
+
+#include <memory>
+
+namespace Assimp {
+
+// PostStepRegistry.cpp
+void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out);
+
+// ------------------------------------------------------------------------------------------------
+// Exporter worker function prototypes. Should not be necessary to #ifndef them, it's just a prototype
+// do not use const, because some exporter need to convert the scene temporary
+void ExportSceneCollada(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneXFile(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneStep(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneObj(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneObjNoMtl(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneSTL(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneSTLBinary(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportScenePly(const char*,IOSystem*, const aiScene*, const ExportProperties*);
+void ExportScenePlyBinary(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportScene3DS(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneGLTF(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneGLB(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneGLTF2(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneGLB2(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneAssbin(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneAssxml(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneX3D(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneFBX(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportSceneFBXA(const char*, IOSystem*, const aiScene*, const ExportProperties*);
+void ExportScene3MF( const char*, IOSystem*, const aiScene*, const ExportProperties* );
+
+// ------------------------------------------------------------------------------------------------
+// global array of all export formats which Assimp supports in its current build
+Exporter::ExportFormatEntry gExporters[] =
+{
+#ifndef ASSIMP_BUILD_NO_COLLADA_EXPORTER
+    Exporter::ExportFormatEntry( "collada", "COLLADA - Digital Asset Exchange Schema", "dae", &ExportSceneCollada ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_X_EXPORTER
+    Exporter::ExportFormatEntry( "x", "X Files", "x", &ExportSceneXFile,
+        aiProcess_MakeLeftHanded | aiProcess_FlipWindingOrder | aiProcess_FlipUVs ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_STEP_EXPORTER
+    Exporter::ExportFormatEntry( "stp", "Step Files", "stp", &ExportSceneStep, 0 ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_OBJ_EXPORTER
+    Exporter::ExportFormatEntry( "obj", "Wavefront OBJ format", "obj", &ExportSceneObj,
+        aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ),
+    Exporter::ExportFormatEntry( "objnomtl", "Wavefront OBJ format without material file", "obj", &ExportSceneObjNoMtl,
+        aiProcess_GenSmoothNormals /*| aiProcess_PreTransformVertices */ ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_STL_EXPORTER
+    Exporter::ExportFormatEntry( "stl", "Stereolithography", "stl" , &ExportSceneSTL,
+        aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices
+    ),
+    Exporter::ExportFormatEntry( "stlb", "Stereolithography (binary)", "stl" , &ExportSceneSTLBinary,
+        aiProcess_Triangulate | aiProcess_GenNormals | aiProcess_PreTransformVertices
+    ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_PLY_EXPORTER
+    Exporter::ExportFormatEntry( "ply", "Stanford Polygon Library", "ply" , &ExportScenePly,
+        aiProcess_PreTransformVertices
+    ),
+    Exporter::ExportFormatEntry( "plyb", "Stanford Polygon Library (binary)", "ply", &ExportScenePlyBinary,
+        aiProcess_PreTransformVertices
+    ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_3DS_EXPORTER
+    Exporter::ExportFormatEntry( "3ds", "Autodesk 3DS (legacy)", "3ds" , &ExportScene3DS,
+        aiProcess_Triangulate | aiProcess_SortByPType | aiProcess_JoinIdenticalVertices ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_GLTF_EXPORTER
+    Exporter::ExportFormatEntry( "gltf2", "GL Transmission Format v. 2", "gltf", &ExportSceneGLTF2,
+        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
+    Exporter::ExportFormatEntry( "glb2", "GL Transmission Format v. 2 (binary)", "glb", &ExportSceneGLB2,
+        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
+    Exporter::ExportFormatEntry( "gltf", "GL Transmission Format", "gltf", &ExportSceneGLTF,
+        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
+    Exporter::ExportFormatEntry( "glb", "GL Transmission Format (binary)", "glb", &ExportSceneGLB,
+        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate | aiProcess_SortByPType ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_ASSBIN_EXPORTER
+    Exporter::ExportFormatEntry( "assbin", "Assimp Binary", "assbin" , &ExportSceneAssbin, 0 ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_ASSXML_EXPORTER
+    Exporter::ExportFormatEntry( "assxml", "Assxml Document", "assxml" , &ExportSceneAssxml, 0 ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_X3D_EXPORTER
+    Exporter::ExportFormatEntry( "x3d", "Extensible 3D", "x3d" , &ExportSceneX3D, 0 ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+    Exporter::ExportFormatEntry( "fbx", "Autodesk FBX (binary)", "fbx", &ExportSceneFBX, 0 ),
+    Exporter::ExportFormatEntry( "fbxa", "Autodesk FBX (ascii)", "fbx", &ExportSceneFBXA, 0 ),
+#endif
+
+#ifndef ASSIMP_BUILD_NO_3MF_EXPORTER
+    Exporter::ExportFormatEntry( "3mf", "The 3MF-File-Format", "3mf", &ExportScene3MF, 0 )
+#endif
+};
+
+#define ASSIMP_NUM_EXPORTERS (sizeof(gExporters)/sizeof(gExporters[0]))
+
+
+class ExporterPimpl {
+public:
+    ExporterPimpl()
+    : blob()
+    , mIOSystem(new Assimp::DefaultIOSystem())
+    , mIsDefaultIOHandler(true)
+    , mProgressHandler( nullptr )
+    , mIsDefaultProgressHandler( true )
+    , mPostProcessingSteps()
+    , mError()
+    , mExporters() {
+        GetPostProcessingStepInstanceList(mPostProcessingSteps);
+
+        // grab all built-in exporters
+        if ( 0 != ( ASSIMP_NUM_EXPORTERS ) ) {
+            mExporters.resize( ASSIMP_NUM_EXPORTERS );
+            std::copy( gExporters, gExporters + ASSIMP_NUM_EXPORTERS, mExporters.begin() );
+        }
+    }
+
+    ~ExporterPimpl() {
+        delete blob;
+
+        // Delete all post-processing plug-ins
+        for( unsigned int a = 0; a < mPostProcessingSteps.size(); a++) {
+            delete mPostProcessingSteps[a];
+        }
+        delete mProgressHandler;
+    }
+
+public:
+    aiExportDataBlob* blob;
+    std::shared_ptr< Assimp::IOSystem > mIOSystem;
+    bool mIsDefaultIOHandler;
+
+    /** The progress handler */
+    ProgressHandler *mProgressHandler;
+    bool mIsDefaultProgressHandler;
+
+    /** Post processing steps we can apply at the imported data. */
+    std::vector< BaseProcess* > mPostProcessingSteps;
+
+    /** Last fatal export error */
+    std::string mError;
+
+    /** Exporters, this includes those registered using #Assimp::Exporter::RegisterExporter */
+    std::vector<Exporter::ExportFormatEntry> mExporters;
+};
+
+} // end of namespace Assimp
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+Exporter :: Exporter()
+: pimpl(new ExporterPimpl()) {
+    pimpl->mProgressHandler = new DefaultProgressHandler();
+}
+
+// ------------------------------------------------------------------------------------------------
+Exporter::~Exporter() {
+    FreeBlob();
+    delete pimpl;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Exporter::SetIOHandler( IOSystem* pIOHandler) {
+    pimpl->mIsDefaultIOHandler = !pIOHandler;
+    pimpl->mIOSystem.reset(pIOHandler);
+}
+
+// ------------------------------------------------------------------------------------------------
+IOSystem* Exporter::GetIOHandler() const {
+    return pimpl->mIOSystem.get();
+}
+
+// ------------------------------------------------------------------------------------------------
+bool Exporter::IsDefaultIOHandler() const {
+    return pimpl->mIsDefaultIOHandler;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Exporter::SetProgressHandler(ProgressHandler* pHandler) {
+    ai_assert(nullptr != pimpl);
+
+    if ( nullptr == pHandler) {
+        // Release pointer in the possession of the caller
+        pimpl->mProgressHandler = new DefaultProgressHandler();
+        pimpl->mIsDefaultProgressHandler = true;
+        return;
+    }
+
+    if (pimpl->mProgressHandler == pHandler) {
+        return;
+    }
+
+    delete pimpl->mProgressHandler;
+    pimpl->mProgressHandler = pHandler;
+    pimpl->mIsDefaultProgressHandler = false;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiExportDataBlob* Exporter::ExportToBlob( const aiScene* pScene, const char* pFormatId,
+                                                unsigned int, const ExportProperties* /*pProperties*/ ) {
+    if (pimpl->blob) {
+        delete pimpl->blob;
+        pimpl->blob = nullptr;
+    }
+
+    std::shared_ptr<IOSystem> old = pimpl->mIOSystem;
+    BlobIOSystem* blobio = new BlobIOSystem();
+    pimpl->mIOSystem = std::shared_ptr<IOSystem>( blobio );
+
+    if (AI_SUCCESS != Export(pScene,pFormatId,blobio->GetMagicFileName())) {
+        pimpl->mIOSystem = old;
+        return nullptr;
+    }
+
+    pimpl->blob = blobio->GetBlobChain();
+    pimpl->mIOSystem = old;
+
+    return pimpl->blob;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool IsVerboseFormat(const aiMesh* mesh) {
+    // avoid slow vector<bool> specialization
+    std::vector<unsigned int> seen(mesh->mNumVertices,0);
+    for(unsigned int i = 0; i < mesh->mNumFaces; ++i) {
+        const aiFace& f = mesh->mFaces[i];
+        for(unsigned int j = 0; j < f.mNumIndices; ++j) {
+            if(++seen[f.mIndices[j]] == 2) {
+                // found a duplicate index
+                return false;
+            }
+        }
+    }
+
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+bool IsVerboseFormat(const aiScene* pScene) {
+    for(unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+        if(!IsVerboseFormat(pScene->mMeshes[i])) {
+            return false;
+        }
+    }
+
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+aiReturn Exporter::Export( const aiScene* pScene, const char* pFormatId, const char* pPath,
+        unsigned int pPreprocessing, const ExportProperties* pProperties) {
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // when they create scenes from scratch, users will likely create them not in verbose
+    // format. They will likely not be aware that there is a flag in the scene to indicate
+    // this, however. To avoid surprises and bug reports, we check for duplicates in
+    // meshes upfront.
+    const bool is_verbose_format = !(pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) || IsVerboseFormat(pScene);
+
+    pimpl->mProgressHandler->UpdateFileWrite(0, 4);
+
+    pimpl->mError = "";
+    for (size_t i = 0; i < pimpl->mExporters.size(); ++i) {
+        const Exporter::ExportFormatEntry& exp = pimpl->mExporters[i];
+        if (!strcmp(exp.mDescription.id,pFormatId)) {
+            try {
+                // Always create a full copy of the scene. We might optimize this one day,
+                // but for now it is the most pragmatic way.
+                aiScene* scenecopy_tmp = nullptr;
+                SceneCombiner::CopyScene(&scenecopy_tmp,pScene);
+
+                pimpl->mProgressHandler->UpdateFileWrite(1, 4);
+
+                std::unique_ptr<aiScene> scenecopy(scenecopy_tmp);
+                const ScenePrivateData* const priv = ScenePriv(pScene);
+
+                // steps that are not idempotent, i.e. we might need to run them again, usually to get back to the
+                // original state before the step was applied first. When checking which steps we don't need
+                // to run, those are excluded.
+                const unsigned int nonIdempotentSteps = aiProcess_FlipWindingOrder | aiProcess_FlipUVs | aiProcess_MakeLeftHanded;
+
+                // Erase all pp steps that were already applied to this scene
+                const unsigned int pp = (exp.mEnforcePP | pPreprocessing) & ~(priv && !priv->mIsCopy
+                    ? (priv->mPPStepsApplied & ~nonIdempotentSteps)
+                    : 0u);
+
+                // If no extra post-processing was specified, and we obtained this scene from an
+                // Assimp importer, apply the reverse steps automatically.
+                // TODO: either drop this, or document it. Otherwise it is just a bad surprise.
+                //if (!pPreprocessing && priv) {
+                //  pp |= (nonIdempotentSteps & priv->mPPStepsApplied);
+                //}
+
+                // If the input scene is not in verbose format, but there is at least post-processing step that relies on it,
+                // we need to run the MakeVerboseFormat step first.
+                bool must_join_again = false;
+                if (!is_verbose_format) {
+                    bool verbosify = false;
+                    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++) {
+                        BaseProcess* const p = pimpl->mPostProcessingSteps[a];
+
+                        if (p->IsActive(pp) && p->RequireVerboseFormat()) {
+                            verbosify = true;
+                            break;
+                        }
+                    }
+
+                    if (verbosify || (exp.mEnforcePP & aiProcess_JoinIdenticalVertices)) {
+                        ASSIMP_LOG_DEBUG("export: Scene data not in verbose format, applying MakeVerboseFormat step first");
+
+                        MakeVerboseFormatProcess proc;
+                        proc.Execute(scenecopy.get());
+
+                        if(!(exp.mEnforcePP & aiProcess_JoinIdenticalVertices)) {
+                            must_join_again = true;
+                        }
+                    }
+                }
+
+                pimpl->mProgressHandler->UpdateFileWrite(2, 4);
+
+                if (pp) {
+                    // the three 'conversion' steps need to be executed first because all other steps rely on the standard data layout
+                    {
+                        FlipWindingOrderProcess step;
+                        if (step.IsActive(pp)) {
+                            step.Execute(scenecopy.get());
+                        }
+                    }
+
+                    {
+                        FlipUVsProcess step;
+                        if (step.IsActive(pp)) {
+                            step.Execute(scenecopy.get());
+                        }
+                    }
+
+                    {
+                        MakeLeftHandedProcess step;
+                        if (step.IsActive(pp)) {
+                            step.Execute(scenecopy.get());
+                        }
+                    }
+
+                    bool exportPointCloud(false);
+                    if (nullptr != pProperties) {
+                        exportPointCloud = pProperties->GetPropertyBool(AI_CONFIG_EXPORT_POINT_CLOUDS);
+                    }
+
+                    // dispatch other processes
+                    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++) {
+                        BaseProcess* const p = pimpl->mPostProcessingSteps[a];
+
+                        if (p->IsActive(pp)
+                            && !dynamic_cast<FlipUVsProcess*>(p)
+                            && !dynamic_cast<FlipWindingOrderProcess*>(p)
+                            && !dynamic_cast<MakeLeftHandedProcess*>(p)) {
+                            if (dynamic_cast<PretransformVertices*>(p) && exportPointCloud) {
+                                continue;
+                            }
+                            p->Execute(scenecopy.get());
+                        }
+                    }
+                    ScenePrivateData* const privOut = ScenePriv(scenecopy.get());
+                    ai_assert(nullptr != privOut);
+
+                    privOut->mPPStepsApplied |= pp;
+                }
+
+                pimpl->mProgressHandler->UpdateFileWrite(3, 4);
+
+                if(must_join_again) {
+                    JoinVerticesProcess proc;
+                    proc.Execute(scenecopy.get());
+                }
+
+                ExportProperties emptyProperties;  // Never pass NULL ExportProperties so Exporters don't have to worry.
+                exp.mExportFunction(pPath,pimpl->mIOSystem.get(),scenecopy.get(), pProperties ? pProperties : &emptyProperties);
+
+                pimpl->mProgressHandler->UpdateFileWrite(4, 4);
+            } catch (DeadlyExportError& err) {
+                pimpl->mError = err.what();
+                return AI_FAILURE;
+            }
+            return AI_SUCCESS;
+        }
+    }
+
+    pimpl->mError = std::string("Found no exporter to handle this file format: ") + pFormatId;
+    ASSIMP_END_EXCEPTION_REGION(aiReturn);
+
+    return AI_FAILURE;
+}
+
+// ------------------------------------------------------------------------------------------------
+const char* Exporter::GetErrorString() const {
+    return pimpl->mError.c_str();
+}
+
+// ------------------------------------------------------------------------------------------------
+void Exporter::FreeBlob() {
+    delete pimpl->blob;
+    pimpl->blob = nullptr;
+
+    pimpl->mError = "";
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiExportDataBlob* Exporter::GetBlob() const {
+    return pimpl->blob;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiExportDataBlob* Exporter::GetOrphanedBlob() const {
+    const aiExportDataBlob* tmp = pimpl->blob;
+    pimpl->blob = nullptr;
+    return tmp;
+}
+
+// ------------------------------------------------------------------------------------------------
+size_t Exporter::GetExportFormatCount() const {
+    return pimpl->mExporters.size();
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiExportFormatDesc* Exporter::GetExportFormatDescription( size_t index ) const {
+    if (index >= GetExportFormatCount()) {
+        return nullptr;
+    }
+
+    // Return from static storage if the requested index is built-in.
+    if (index < sizeof(gExporters) / sizeof(gExporters[0])) {
+        return &gExporters[index].mDescription;
+    }
+
+    return &pimpl->mExporters[index].mDescription;
+}
+
+// ------------------------------------------------------------------------------------------------
+aiReturn Exporter::RegisterExporter(const ExportFormatEntry& desc) {
+    for(const ExportFormatEntry& e : pimpl->mExporters) {
+        if (!strcmp(e.mDescription.id,desc.mDescription.id)) {
+            return aiReturn_FAILURE;
+        }
+    }
+
+    pimpl->mExporters.push_back(desc);
+    return aiReturn_SUCCESS;
+}
+
+// ------------------------------------------------------------------------------------------------
+void Exporter::UnregisterExporter(const char* id) {
+    for(std::vector<ExportFormatEntry>::iterator it = pimpl->mExporters.begin();
+            it != pimpl->mExporters.end(); ++it) {
+        if (!strcmp((*it).mDescription.id,id)) {
+            pimpl->mExporters.erase(it);
+            break;
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+ExportProperties::ExportProperties() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+ExportProperties::ExportProperties(const ExportProperties &other)
+: mIntProperties(other.mIntProperties)
+, mFloatProperties(other.mFloatProperties)
+, mStringProperties(other.mStringProperties)
+, mMatrixProperties(other.mMatrixProperties) {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool ExportProperties::SetPropertyInteger(const char* szName, int iValue) {
+    return SetGenericProperty<int>(mIntProperties, szName,iValue);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool ExportProperties::SetPropertyFloat(const char* szName, ai_real iValue) {
+    return SetGenericProperty<ai_real>(mFloatProperties, szName,iValue);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool ExportProperties::SetPropertyString(const char* szName, const std::string& value) {
+    return SetGenericProperty<std::string>(mStringProperties, szName,value);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool ExportProperties::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value) {
+    return SetGenericProperty<aiMatrix4x4>(mMatrixProperties, szName,value);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+int ExportProperties::GetPropertyInteger(const char* szName, int iErrorReturn /*= 0xffffffff*/) const {
+    return GetGenericProperty<int>(mIntProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+ai_real ExportProperties::GetPropertyFloat(const char* szName, ai_real iErrorReturn /*= 10e10*/) const {
+    return GetGenericProperty<ai_real>(mFloatProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+const std::string ExportProperties::GetPropertyString(const char* szName,
+        const std::string& iErrorReturn /*= ""*/) const {
+    return GetGenericProperty<std::string>(mStringProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Has a configuration property
+const aiMatrix4x4 ExportProperties::GetPropertyMatrix(const char* szName,
+        const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const {
+    return GetGenericProperty<aiMatrix4x4>(mMatrixProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Has a configuration property
+bool ExportProperties::HasPropertyInteger(const char* szName) const {
+    return HasGenericProperty<int>(mIntProperties, szName);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Has a configuration property
+bool ExportProperties::HasPropertyBool(const char* szName) const {
+    return HasGenericProperty<int>(mIntProperties, szName);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Has a configuration property
+bool ExportProperties::HasPropertyFloat(const char* szName) const {
+    return HasGenericProperty<ai_real>(mFloatProperties, szName);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Has a configuration property
+bool ExportProperties::HasPropertyString(const char* szName) const {
+    return HasGenericProperty<std::string>(mStringProperties, szName);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Has a configuration property
+bool ExportProperties::HasPropertyMatrix(const char* szName) const {
+    return HasGenericProperty<aiMatrix4x4>(mMatrixProperties, szName);
+}
+
+
+#endif // !ASSIMP_BUILD_NO_EXPORT

+ 305 - 0
thirdparty/assimp/code/FBXAnimation.cpp

@@ -0,0 +1,305 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXParser.h"
+#include "FBXDocument.h"
+#include "FBXImporter.h"
+#include "FBXDocumentUtil.h"
+
+namespace Assimp {
+namespace FBX {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurve::AnimationCurve(uint64_t id, const Element& element, const std::string& name, const Document& /*doc*/)
+: Object(id, element, name)
+{
+    const Scope& sc = GetRequiredScope(element);
+    const Element& KeyTime = GetRequiredElement(sc,"KeyTime");
+    const Element& KeyValueFloat = GetRequiredElement(sc,"KeyValueFloat");
+
+    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);
+    }
+
+    // check if the key times are well-ordered
+    if(!std::equal(keys.begin(), keys.end() - 1, keys.begin() + 1, std::less<KeyTimeList::value_type>())) {
+        DOMError("the keyframes are not in ascending order",&KeyTime);
+    }
+
+    const Element* KeyAttrDataFloat = sc["KeyAttrDataFloat"];
+    if(KeyAttrDataFloat) {
+        ParseVectorDataArray(attributes, *KeyAttrDataFloat);
+    }
+
+    const Element* KeyAttrFlags = sc["KeyAttrFlags"];
+    if(KeyAttrFlags) {
+        ParseVectorDataArray(flags, *KeyAttrFlags);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurve::~AnimationCurve()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurveNode::AnimationCurveNode(uint64_t id, const Element& 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 Scope& 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;
+        }
+
+        if(target_prop_whitelist) {
+            const char* const s = con->PropertyName().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) {
+                throw std::range_error("AnimationCurveNode target property is not in whitelist");
+            }
+        }
+
+        const Object* const ob = con->DestinationObject();
+        if(!ob) {
+            DOMWarning("failed to read destination object for AnimationCurveNode->Model link, ignoring",&element);
+            continue;
+        }
+
+        // XXX support constraints as DOM class
+        //ai_assert(dynamic_cast<const Model*>(ob) || dynamic_cast<const NodeAttribute*>(ob));
+        target = ob;
+        if(!target) {
+            continue;
+        }
+
+        prop = con->PropertyName();
+        break;
+    }
+
+    if(!target) {
+        DOMWarning("failed to resolve target Model/NodeAttribute/Constraint for AnimationCurveNode",&element);
+    }
+
+    props = GetPropertyTable(doc,"AnimationCurveNode.FbxAnimCurveNode",element,sc,false);
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurveNode::~AnimationCurveNode()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+const AnimationCurveMap& AnimationCurveNode::Curves() const
+{
+    if ( curves.empty() ) {
+        // resolve attached animation curves
+        const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"AnimationCurve");
+
+        for(const Connection* con : conns) {
+
+            // link should go for a property
+            if (!con->PropertyName().length()) {
+                continue;
+            }
+
+            const Object* const ob = con->SourceObject();
+            if(!ob) {
+                DOMWarning("failed to read source object for AnimationCurve->AnimationCurveNode link, ignoring",&element);
+                continue;
+            }
+
+            const AnimationCurve* const anim = dynamic_cast<const AnimationCurve*>(ob);
+            if(!anim) {
+                DOMWarning("source object for ->AnimationCurveNode link is not an AnimationCurve",&element);
+                continue;
+            }
+
+            curves[con->PropertyName()] = anim;
+        }
+    }
+
+    return curves;
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationLayer::AnimationLayer(uint64_t id, const Element& element, const std::string& name, const Document& doc)
+: Object(id, element, name)
+, doc(doc)
+{
+    const Scope& 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
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationCurveNodeList AnimationLayer::Nodes(const char* const * target_prop_whitelist /*= NULL*/,
+    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;
+        }
+
+        const Object* const ob = con->SourceObject();
+        if(!ob) {
+            DOMWarning("failed to read source object for AnimationCurveNode->AnimationLayer link, ignoring",&element);
+            continue;
+        }
+
+        const AnimationCurveNode* const anim = dynamic_cast<const 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; // pray for NRVO
+}
+
+// ------------------------------------------------------------------------------------------------
+AnimationStack::AnimationStack(uint64_t id, const Element& element, const std::string& name, const Document& doc)
+: Object(id, element, name)
+{
+    const Scope& 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;
+        }
+
+        const Object* const ob = con->SourceObject();
+        if(!ob) {
+            DOMWarning("failed to read source object for AnimationLayer->AnimationStack link, ignoring",&element);
+            continue;
+        }
+
+        const AnimationLayer* const 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()
+{
+    // empty
+}
+
+} //!FBX
+} //!Assimp
+
+#endif // ASSIMP_BUILD_NO_FBX_IMPORTER

+ 466 - 0
thirdparty/assimp/code/FBXBinaryTokenizer.cpp

@@ -0,0 +1,466 @@
+/*
+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.
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXTokenizer.h"
+#include "FBXUtil.h"
+#include <assimp/defs.h>
+#include <stdint.h>
+#include <assimp/Exceptional.h>
+#include <assimp/ByteSwapper.h>
+
+namespace Assimp {
+namespace FBX {
+
+//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, unsigned int offset)
+    :
+    #ifdef DEBUG
+    contents(sbegin, static_cast<size_t>(send-sbegin)),
+    #endif
+    sbegin(sbegin)
+    , send(send)
+    , type(type)
+    , line(offset)
+    , column(BINARY_MARKER)
+{
+    ai_assert(sbegin);
+    ai_assert(send);
+
+    // binary tokens may have zero length because they are sometimes dummies
+    // inserted by TokenizeBinary()
+    ai_assert(send >= sbegin);
+}
+
+
+namespace {
+
+// ------------------------------------------------------------------------------------------------
+// signal tokenization error, this is always unrecoverable. Throws DeadlyImportError.
+AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int offset) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int offset)
+{
+    throw DeadlyImportError(Util::AddOffset("FBX-Tokenize",message,offset));
+}
+
+
+// ------------------------------------------------------------------------------------------------
+uint32_t Offset(const char* begin, const char* cursor) {
+    ai_assert(begin <= cursor);
+
+    return static_cast<unsigned int>(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:
+                ai_assert(false);
+            };
+            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, unsigned int length)
+{
+    ai_assert(input);
+
+    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);
+	const bool is64bits = version >= 7500;
+    const char *end = input + length;
+    while (cursor < end ) {
+		if (!ReadScope(output_tokens, input, cursor, input + length, is64bits)) {
+            break;
+        }
+    }
+}
+
+} // !FBX
+} // !Assimp
+
+#endif

+ 86 - 0
thirdparty/assimp/code/FBXCommon.h

@@ -0,0 +1,86 @@
+/*
+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 AI_FBXCOMMON_H_INC
+#define AI_FBXCOMMON_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+
+namespace FBX
+{
+    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
+    };
+
+    // transformation inheritance method. Most of the time RSrs
+    enum TransformInheritance {
+        TransformInheritance_RrSs = 0,
+        TransformInheritance_RSrs,
+        TransformInheritance_Rrs,
+
+        TransformInheritance_MAX // end-of-enum sentinel
+    };
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXCOMMON_H_INC

+ 70 - 0
thirdparty/assimp/code/FBXCompileConfig.h

@@ -0,0 +1,70 @@
+/*
+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  FBXCompileConfig.h
+ *  @brief FBX importer compile-time switches
+ */
+#ifndef INCLUDED_AI_FBX_COMPILECONFIG_H
+#define INCLUDED_AI_FBX_COMPILECONFIG_H
+
+#include <map>
+
+//
+#if _MSC_VER > 1500 || (defined __GNUC___)
+#   define ASSIMP_FBX_USE_UNORDERED_MULTIMAP
+#   else
+#   define fbx_unordered_map map
+#   define fbx_unordered_multimap multimap
+#endif
+
+#ifdef ASSIMP_FBX_USE_UNORDERED_MULTIMAP
+#   include <unordered_map>
+#   if _MSC_VER > 1600
+#       define fbx_unordered_map unordered_map
+#       define fbx_unordered_multimap unordered_multimap
+#   else
+#       define fbx_unordered_map tr1::unordered_map
+#       define fbx_unordered_multimap tr1::unordered_multimap
+#   endif
+#endif
+
+#endif // INCLUDED_AI_FBX_COMPILECONFIG_H

+ 3515 - 0
thirdparty/assimp/code/FBXConverter.cpp

@@ -0,0 +1,3515 @@
+/*
+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  FBXConverter.cpp
+ *  @brief Implementation of the FBX DOM -> aiScene converter
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXConverter.h"
+#include "FBXParser.h"
+#include "FBXMeshGeometry.h"
+#include "FBXDocument.h"
+#include "FBXUtil.h"
+#include "FBXProperties.h"
+#include "FBXImporter.h"
+
+#include <assimp/StringComparison.h>
+
+#include <assimp/scene.h>
+
+#include <assimp/CreateAnimMesh.h>
+
+#include <tuple>
+#include <memory>
+#include <iterator>
+#include <vector>
+#include <sstream>
+#include <iomanip>
+
+namespace Assimp {
+    namespace FBX {
+
+        using namespace Util;
+
+#define MAGIC_NODE_TAG "_$AssimpFbx$"
+
+#define CONVERT_FBX_TIME(time) static_cast<double>(time) / 46186158000L
+
+        FBXConverter::FBXConverter(aiScene* out, const Document& doc)
+            : defaultMaterialIndex()
+            , out(out)
+            , doc(doc) {
+            // animations need to be converted first since this will
+            // populate the node_anim_chain_bits map, which is needed
+            // to determine which nodes need to be generated.
+            ConvertAnimations();
+            ConvertRootNode();
+
+            if (doc.Settings().readAllMaterials) {
+                // unfortunately this means we have to evaluate all objects
+                for (const ObjectMap::value_type& v : doc.Objects()) {
+
+                    const Object* ob = v.second->Get();
+                    if (!ob) {
+                        continue;
+                    }
+
+                    const Material* mat = dynamic_cast<const Material*>(ob);
+                    if (mat) {
+
+                        if (materials_converted.find(mat) == materials_converted.end()) {
+                            ConvertMaterial(*mat, 0);
+                        }
+                    }
+                }
+            }
+
+            ConvertGlobalSettings();
+            TransferDataToScene();
+
+            // if we didn't read any meshes set the AI_SCENE_FLAGS_INCOMPLETE
+            // to make sure the scene passes assimp's validation. FBX files
+            // need not contain geometry (i.e. camera animations, raw armatures).
+            if (out->mNumMeshes == 0) {
+                out->mFlags |= AI_SCENE_FLAGS_INCOMPLETE;
+            }
+        }
+
+
+        FBXConverter::~FBXConverter() {
+            std::for_each(meshes.begin(), meshes.end(), Util::delete_fun<aiMesh>());
+            std::for_each(materials.begin(), materials.end(), Util::delete_fun<aiMaterial>());
+            std::for_each(animations.begin(), animations.end(), Util::delete_fun<aiAnimation>());
+            std::for_each(lights.begin(), lights.end(), Util::delete_fun<aiLight>());
+            std::for_each(cameras.begin(), cameras.end(), Util::delete_fun<aiCamera>());
+            std::for_each(textures.begin(), textures.end(), Util::delete_fun<aiTexture>());
+        }
+
+        void FBXConverter::ConvertRootNode() {
+            out->mRootNode = new aiNode();
+            out->mRootNode->mName.Set("RootNode");
+
+            // root has ID 0
+            ConvertNodes(0L, *out->mRootNode);
+        }
+
+        void FBXConverter::ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform) {
+            const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(id, "Model");
+
+            std::vector<aiNode*> nodes;
+            nodes.reserve(conns.size());
+
+            std::vector<aiNode*> nodes_chain;
+            std::vector<aiNode*> post_nodes_chain;
+
+            try {
+                for (const Connection* con : conns) {
+
+                    // ignore object-property links
+                    if (con->PropertyName().length()) {
+                        continue;
+                    }
+
+                    const Object* const object = con->SourceObject();
+                    if (nullptr == object) {
+                        FBXImporter::LogWarn("failed to convert source object for Model link");
+                        continue;
+                    }
+
+                    const Model* const model = dynamic_cast<const Model*>(object);
+
+                    if (nullptr != model) {
+                        nodes_chain.clear();
+                        post_nodes_chain.clear();
+
+                        aiMatrix4x4 new_abs_transform = parent_transform;
+
+                        // even though there is only a single input node, the design of
+                        // assimp (or rather: the complicated transformation chain that
+                        // is employed by fbx) means that we may need multiple aiNode's
+                        // to represent a fbx node's transformation.
+                        GenerateTransformationNodeChain(*model, nodes_chain, post_nodes_chain);
+
+                        ai_assert(nodes_chain.size());
+
+                        std::string original_name = FixNodeName(model->Name());
+
+                        // check if any of the nodes in the chain has the name the fbx node
+                        // is supposed to have. If there is none, add another node to
+                        // preserve the name - people might have scripts etc. that rely
+                        // on specific node names.
+                        aiNode* name_carrier = NULL;
+                        for (aiNode* prenode : nodes_chain) {
+                            if (!strcmp(prenode->mName.C_Str(), original_name.c_str())) {
+                                name_carrier = prenode;
+                                break;
+                            }
+                        }
+
+                        if (!name_carrier) {
+                            std::string old_original_name = original_name;
+                            GetUniqueName(old_original_name, original_name);
+                            nodes_chain.push_back(new aiNode(original_name));
+                        }
+                        else {
+                            original_name = nodes_chain.back()->mName.C_Str();
+                        }
+
+                        //setup metadata on newest node
+                        SetupNodeMetadata(*model, *nodes_chain.back());
+
+                        // link all nodes in a row
+                        aiNode* last_parent = &parent;
+                        for (aiNode* prenode : nodes_chain) {
+                            ai_assert(prenode);
+
+                            if (last_parent != &parent) {
+                                last_parent->mNumChildren = 1;
+                                last_parent->mChildren = new aiNode*[1];
+                                last_parent->mChildren[0] = prenode;
+                            }
+
+                            prenode->mParent = last_parent;
+                            last_parent = prenode;
+
+                            new_abs_transform *= prenode->mTransformation;
+                        }
+
+                        // attach geometry
+                        ConvertModel(*model, *nodes_chain.back(), new_abs_transform);
+
+                        // check if there will be any child nodes
+                        const std::vector<const Connection*>& child_conns
+                            = doc.GetConnectionsByDestinationSequenced(model->ID(), "Model");
+
+                        // if so, link the geometric transform inverse nodes
+                        // before we attach any child nodes
+                        if (child_conns.size()) {
+                            for (aiNode* postnode : post_nodes_chain) {
+                                ai_assert(postnode);
+
+                                if (last_parent != &parent) {
+                                    last_parent->mNumChildren = 1;
+                                    last_parent->mChildren = new aiNode*[1];
+                                    last_parent->mChildren[0] = postnode;
+                                }
+
+                                postnode->mParent = last_parent;
+                                last_parent = postnode;
+
+                                new_abs_transform *= postnode->mTransformation;
+                            }
+                        }
+                        else {
+                            // free the nodes we allocated as we don't need them
+                            Util::delete_fun<aiNode> deleter;
+                            std::for_each(
+                                post_nodes_chain.begin(),
+                                post_nodes_chain.end(),
+                                deleter
+                            );
+                        }
+
+                        // attach sub-nodes (if any)
+                        ConvertNodes(model->ID(), *last_parent, new_abs_transform);
+
+                        if (doc.Settings().readLights) {
+                            ConvertLights(*model, original_name);
+                        }
+
+                        if (doc.Settings().readCameras) {
+                            ConvertCameras(*model, original_name);
+                        }
+
+                        nodes.push_back(nodes_chain.front());
+                        nodes_chain.clear();
+                    }
+                }
+
+                if (nodes.size()) {
+                    parent.mChildren = new aiNode*[nodes.size()]();
+                    parent.mNumChildren = static_cast<unsigned int>(nodes.size());
+
+                    std::swap_ranges(nodes.begin(), nodes.end(), parent.mChildren);
+                }
+            }
+            catch (std::exception&) {
+                Util::delete_fun<aiNode> deleter;
+                std::for_each(nodes.begin(), nodes.end(), deleter);
+                std::for_each(nodes_chain.begin(), nodes_chain.end(), deleter);
+                std::for_each(post_nodes_chain.begin(), post_nodes_chain.end(), deleter);
+            }
+        }
+
+
+        void FBXConverter::ConvertLights(const Model& model, const std::string &orig_name) {
+            const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
+            for (const NodeAttribute* attr : node_attrs) {
+                const Light* const light = dynamic_cast<const Light*>(attr);
+                if (light) {
+                    ConvertLight(*light, orig_name);
+                }
+            }
+        }
+
+        void FBXConverter::ConvertCameras(const Model& model, const std::string &orig_name) {
+            const std::vector<const NodeAttribute*>& node_attrs = model.GetAttributes();
+            for (const NodeAttribute* attr : node_attrs) {
+                const Camera* const cam = dynamic_cast<const Camera*>(attr);
+                if (cam) {
+                    ConvertCamera(*cam, orig_name);
+                }
+            }
+        }
+
+        void FBXConverter::ConvertLight(const Light& light, const std::string &orig_name) {
+            lights.push_back(new aiLight());
+            aiLight* const out_light = lights.back();
+
+            out_light->mName.Set(orig_name);
+
+            const float intensity = light.Intensity() / 100.0f;
+            const aiVector3D& col = light.Color();
+
+            out_light->mColorDiffuse = aiColor3D(col.x, col.y, col.z);
+            out_light->mColorDiffuse.r *= intensity;
+            out_light->mColorDiffuse.g *= intensity;
+            out_light->mColorDiffuse.b *= intensity;
+
+            out_light->mColorSpecular = out_light->mColorDiffuse;
+
+            //lights are defined along negative y direction
+            out_light->mPosition = aiVector3D(0.0f);
+            out_light->mDirection = aiVector3D(0.0f, -1.0f, 0.0f);
+            out_light->mUp = aiVector3D(0.0f, 0.0f, -1.0f);
+
+            switch (light.LightType())
+            {
+            case Light::Type_Point:
+                out_light->mType = aiLightSource_POINT;
+                break;
+
+            case Light::Type_Directional:
+                out_light->mType = aiLightSource_DIRECTIONAL;
+                break;
+
+            case Light::Type_Spot:
+                out_light->mType = aiLightSource_SPOT;
+                out_light->mAngleOuterCone = AI_DEG_TO_RAD(light.OuterAngle());
+                out_light->mAngleInnerCone = AI_DEG_TO_RAD(light.InnerAngle());
+                break;
+
+            case Light::Type_Area:
+                FBXImporter::LogWarn("cannot represent area light, set to UNDEFINED");
+                out_light->mType = aiLightSource_UNDEFINED;
+                break;
+
+            case Light::Type_Volume:
+                FBXImporter::LogWarn("cannot represent volume light, set to UNDEFINED");
+                out_light->mType = aiLightSource_UNDEFINED;
+                break;
+            default:
+                ai_assert(false);
+            }
+
+            float decay = light.DecayStart();
+            switch (light.DecayType())
+            {
+            case Light::Decay_None:
+                out_light->mAttenuationConstant = decay;
+                out_light->mAttenuationLinear = 0.0f;
+                out_light->mAttenuationQuadratic = 0.0f;
+                break;
+            case Light::Decay_Linear:
+                out_light->mAttenuationConstant = 0.0f;
+                out_light->mAttenuationLinear = 2.0f / decay;
+                out_light->mAttenuationQuadratic = 0.0f;
+                break;
+            case Light::Decay_Quadratic:
+                out_light->mAttenuationConstant = 0.0f;
+                out_light->mAttenuationLinear = 0.0f;
+                out_light->mAttenuationQuadratic = 2.0f / (decay * decay);
+                break;
+            case Light::Decay_Cubic:
+                FBXImporter::LogWarn("cannot represent cubic attenuation, set to Quadratic");
+                out_light->mAttenuationQuadratic = 1.0f;
+                break;
+            default:
+                ai_assert(false);
+            }
+        }
+
+        void FBXConverter::ConvertCamera(const Camera& cam, const std::string &orig_name)
+        {
+            cameras.push_back(new aiCamera());
+            aiCamera* const out_camera = cameras.back();
+
+            out_camera->mName.Set(orig_name);
+
+            out_camera->mAspect = cam.AspectWidth() / cam.AspectHeight();
+
+            //cameras are defined along positive x direction
+            /*out_camera->mPosition = cam.Position();
+            out_camera->mLookAt = (cam.InterestPosition() - out_camera->mPosition).Normalize();
+            out_camera->mUp = cam.UpVector();*/
+
+            out_camera->mPosition = aiVector3D(0.0f);
+            out_camera->mLookAt = aiVector3D(1.0f, 0.0f, 0.0f);
+            out_camera->mUp = aiVector3D(0.0f, 1.0f, 0.0f);
+
+            out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView());
+
+            out_camera->mClipPlaneNear = cam.NearPlane();
+            out_camera->mClipPlaneFar = cam.FarPlane();
+
+            out_camera->mHorizontalFOV = AI_DEG_TO_RAD(cam.FieldOfView());
+            out_camera->mClipPlaneNear = cam.NearPlane();
+            out_camera->mClipPlaneFar = cam.FarPlane();
+        }
+
+        void FBXConverter::GetUniqueName(const std::string &name, std::string &uniqueName)
+        {
+            int i = 0;
+            uniqueName = name;
+            while (mNodeNames.find(uniqueName) != mNodeNames.end())
+            {
+                ++i;
+                std::stringstream ext;
+                ext << name << std::setfill('0') << std::setw(3) << i;
+                uniqueName = ext.str();
+            }
+            mNodeNames.insert(uniqueName);
+        }
+
+
+        const char* FBXConverter::NameTransformationComp(TransformationComp comp) {
+            switch (comp) {
+            case TransformationComp_Translation:
+                return "Translation";
+            case TransformationComp_RotationOffset:
+                return "RotationOffset";
+            case TransformationComp_RotationPivot:
+                return "RotationPivot";
+            case TransformationComp_PreRotation:
+                return "PreRotation";
+            case TransformationComp_Rotation:
+                return "Rotation";
+            case TransformationComp_PostRotation:
+                return "PostRotation";
+            case TransformationComp_RotationPivotInverse:
+                return "RotationPivotInverse";
+            case TransformationComp_ScalingOffset:
+                return "ScalingOffset";
+            case TransformationComp_ScalingPivot:
+                return "ScalingPivot";
+            case TransformationComp_Scaling:
+                return "Scaling";
+            case TransformationComp_ScalingPivotInverse:
+                return "ScalingPivotInverse";
+            case TransformationComp_GeometricScaling:
+                return "GeometricScaling";
+            case TransformationComp_GeometricRotation:
+                return "GeometricRotation";
+            case TransformationComp_GeometricTranslation:
+                return "GeometricTranslation";
+            case TransformationComp_GeometricScalingInverse:
+                return "GeometricScalingInverse";
+            case TransformationComp_GeometricRotationInverse:
+                return "GeometricRotationInverse";
+            case TransformationComp_GeometricTranslationInverse:
+                return "GeometricTranslationInverse";
+            case TransformationComp_MAXIMUM: // this is to silence compiler warnings
+            default:
+                break;
+            }
+
+            ai_assert(false);
+
+            return nullptr;
+        }
+
+        const char* FBXConverter::NameTransformationCompProperty(TransformationComp comp) {
+            switch (comp) {
+            case TransformationComp_Translation:
+                return "Lcl Translation";
+            case TransformationComp_RotationOffset:
+                return "RotationOffset";
+            case TransformationComp_RotationPivot:
+                return "RotationPivot";
+            case TransformationComp_PreRotation:
+                return "PreRotation";
+            case TransformationComp_Rotation:
+                return "Lcl Rotation";
+            case TransformationComp_PostRotation:
+                return "PostRotation";
+            case TransformationComp_RotationPivotInverse:
+                return "RotationPivotInverse";
+            case TransformationComp_ScalingOffset:
+                return "ScalingOffset";
+            case TransformationComp_ScalingPivot:
+                return "ScalingPivot";
+            case TransformationComp_Scaling:
+                return "Lcl Scaling";
+            case TransformationComp_ScalingPivotInverse:
+                return "ScalingPivotInverse";
+            case TransformationComp_GeometricScaling:
+                return "GeometricScaling";
+            case TransformationComp_GeometricRotation:
+                return "GeometricRotation";
+            case TransformationComp_GeometricTranslation:
+                return "GeometricTranslation";
+            case TransformationComp_GeometricScalingInverse:
+                return "GeometricScalingInverse";
+            case TransformationComp_GeometricRotationInverse:
+                return "GeometricRotationInverse";
+            case TransformationComp_GeometricTranslationInverse:
+                return "GeometricTranslationInverse";
+            case TransformationComp_MAXIMUM: // this is to silence compiler warnings
+                break;
+            }
+
+            ai_assert(false);
+
+            return nullptr;
+        }
+
+        aiVector3D FBXConverter::TransformationCompDefaultValue(TransformationComp comp)
+        {
+            // XXX a neat way to solve the never-ending special cases for scaling
+            // would be to do everything in log space!
+            return comp == TransformationComp_Scaling ? aiVector3D(1.f, 1.f, 1.f) : aiVector3D();
+        }
+
+        void FBXConverter::GetRotationMatrix(Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out)
+        {
+            if (mode == Model::RotOrder_SphericXYZ) {
+                FBXImporter::LogError("Unsupported RotationMode: SphericXYZ");
+                out = aiMatrix4x4();
+                return;
+            }
+
+            const float angle_epsilon = 1e-6f;
+
+            out = aiMatrix4x4();
+
+            bool is_id[3] = { true, true, true };
+
+            aiMatrix4x4 temp[3];
+            if (std::fabs(rotation.z) > angle_epsilon) {
+                aiMatrix4x4::RotationZ(AI_DEG_TO_RAD(rotation.z), temp[2]);
+                is_id[2] = false;
+            }
+            if (std::fabs(rotation.y) > angle_epsilon) {
+                aiMatrix4x4::RotationY(AI_DEG_TO_RAD(rotation.y), temp[1]);
+                is_id[1] = false;
+            }
+            if (std::fabs(rotation.x) > angle_epsilon) {
+                aiMatrix4x4::RotationX(AI_DEG_TO_RAD(rotation.x), temp[0]);
+                is_id[0] = false;
+            }
+
+            int order[3] = { -1, -1, -1 };
+
+            // note: rotation order is inverted since we're left multiplying as is usual in assimp
+            switch (mode)
+            {
+            case Model::RotOrder_EulerXYZ:
+                order[0] = 2;
+                order[1] = 1;
+                order[2] = 0;
+                break;
+
+            case Model::RotOrder_EulerXZY:
+                order[0] = 1;
+                order[1] = 2;
+                order[2] = 0;
+                break;
+
+            case Model::RotOrder_EulerYZX:
+                order[0] = 0;
+                order[1] = 2;
+                order[2] = 1;
+                break;
+
+            case Model::RotOrder_EulerYXZ:
+                order[0] = 2;
+                order[1] = 0;
+                order[2] = 1;
+                break;
+
+            case Model::RotOrder_EulerZXY:
+                order[0] = 1;
+                order[1] = 0;
+                order[2] = 2;
+                break;
+
+            case Model::RotOrder_EulerZYX:
+                order[0] = 0;
+                order[1] = 1;
+                order[2] = 2;
+                break;
+
+            default:
+                ai_assert(false);
+                break;
+            }
+
+            ai_assert(order[0] >= 0);
+            ai_assert(order[0] <= 2);
+            ai_assert(order[1] >= 0);
+            ai_assert(order[1] <= 2);
+            ai_assert(order[2] >= 0);
+            ai_assert(order[2] <= 2);
+
+            if (!is_id[order[0]]) {
+                out = temp[order[0]];
+            }
+
+            if (!is_id[order[1]]) {
+                out = out * temp[order[1]];
+            }
+
+            if (!is_id[order[2]]) {
+                out = out * temp[order[2]];
+            }
+        }
+
+        bool FBXConverter::NeedsComplexTransformationChain(const Model& model)
+        {
+            const PropertyTable& props = model.Props();
+            bool ok;
+
+            const float zero_epsilon = 1e-6f;
+            const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
+            for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
+                const TransformationComp comp = static_cast<TransformationComp>(i);
+
+                if (comp == TransformationComp_Rotation || comp == TransformationComp_Scaling || comp == TransformationComp_Translation) {
+                    continue;
+                }
+
+                bool scale_compare = (comp == TransformationComp_GeometricScaling || comp == TransformationComp_Scaling);
+
+                const aiVector3D& v = PropertyGet<aiVector3D>(props, NameTransformationCompProperty(comp), ok);
+                if (ok && scale_compare) {
+                    if ((v - all_ones).SquareLength() > zero_epsilon) {
+                        return true;
+                    }
+                }
+                else if (ok) {
+                    if (v.SquareLength() > zero_epsilon) {
+                        return true;
+                    }
+                }
+            }
+
+            return false;
+        }
+
+        std::string FBXConverter::NameTransformationChainNode(const std::string& name, TransformationComp comp)
+        {
+            return name + std::string(MAGIC_NODE_TAG) + "_" + NameTransformationComp(comp);
+        }
+
+        void FBXConverter::GenerateTransformationNodeChain(const Model& model, std::vector<aiNode*>& output_nodes,
+            std::vector<aiNode*>& post_output_nodes) {
+            const PropertyTable& props = model.Props();
+            const Model::RotOrder rot = model.RotationOrder();
+
+            bool ok;
+
+            aiMatrix4x4 chain[TransformationComp_MAXIMUM];
+            std::fill_n(chain, static_cast<unsigned int>(TransformationComp_MAXIMUM), aiMatrix4x4());
+
+            // generate transformation matrices for all the different transformation components
+            const float zero_epsilon = 1e-6f;
+            const aiVector3D all_ones(1.0f, 1.0f, 1.0f);
+            bool is_complex = false;
+
+            const aiVector3D& PreRotation = PropertyGet<aiVector3D>(props, "PreRotation", ok);
+            if (ok && PreRotation.SquareLength() > zero_epsilon) {
+                is_complex = true;
+
+                GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PreRotation, chain[TransformationComp_PreRotation]);
+            }
+
+            const aiVector3D& PostRotation = PropertyGet<aiVector3D>(props, "PostRotation", ok);
+            if (ok && PostRotation.SquareLength() > zero_epsilon) {
+                is_complex = true;
+
+                GetRotationMatrix(Model::RotOrder::RotOrder_EulerXYZ, PostRotation, chain[TransformationComp_PostRotation]);
+            }
+
+            const aiVector3D& RotationPivot = PropertyGet<aiVector3D>(props, "RotationPivot", ok);
+            if (ok && RotationPivot.SquareLength() > zero_epsilon) {
+                is_complex = true;
+
+                aiMatrix4x4::Translation(RotationPivot, chain[TransformationComp_RotationPivot]);
+                aiMatrix4x4::Translation(-RotationPivot, chain[TransformationComp_RotationPivotInverse]);
+            }
+
+            const aiVector3D& RotationOffset = PropertyGet<aiVector3D>(props, "RotationOffset", ok);
+            if (ok && RotationOffset.SquareLength() > zero_epsilon) {
+                is_complex = true;
+
+                aiMatrix4x4::Translation(RotationOffset, chain[TransformationComp_RotationOffset]);
+            }
+
+            const aiVector3D& ScalingOffset = PropertyGet<aiVector3D>(props, "ScalingOffset", ok);
+            if (ok && ScalingOffset.SquareLength() > zero_epsilon) {
+                is_complex = true;
+
+                aiMatrix4x4::Translation(ScalingOffset, chain[TransformationComp_ScalingOffset]);
+            }
+
+            const aiVector3D& ScalingPivot = PropertyGet<aiVector3D>(props, "ScalingPivot", ok);
+            if (ok && ScalingPivot.SquareLength() > zero_epsilon) {
+                is_complex = true;
+
+                aiMatrix4x4::Translation(ScalingPivot, chain[TransformationComp_ScalingPivot]);
+                aiMatrix4x4::Translation(-ScalingPivot, chain[TransformationComp_ScalingPivotInverse]);
+            }
+
+            const aiVector3D& Translation = PropertyGet<aiVector3D>(props, "Lcl Translation", ok);
+            if (ok && Translation.SquareLength() > zero_epsilon) {
+                aiMatrix4x4::Translation(Translation, chain[TransformationComp_Translation]);
+            }
+
+            const aiVector3D& Scaling = PropertyGet<aiVector3D>(props, "Lcl Scaling", ok);
+            if (ok && (Scaling - all_ones).SquareLength() > zero_epsilon) {
+                aiMatrix4x4::Scaling(Scaling, chain[TransformationComp_Scaling]);
+            }
+
+            const aiVector3D& Rotation = PropertyGet<aiVector3D>(props, "Lcl Rotation", ok);
+            if (ok && Rotation.SquareLength() > zero_epsilon) {
+                GetRotationMatrix(rot, Rotation, chain[TransformationComp_Rotation]);
+            }
+
+            const aiVector3D& GeometricScaling = PropertyGet<aiVector3D>(props, "GeometricScaling", ok);
+            if (ok && (GeometricScaling - all_ones).SquareLength() > zero_epsilon) {
+                is_complex = true;
+                aiMatrix4x4::Scaling(GeometricScaling, chain[TransformationComp_GeometricScaling]);
+                aiVector3D GeometricScalingInverse = GeometricScaling;
+                bool canscale = true;
+                for (unsigned int i = 0; i < 3; ++i) {
+                    if (std::fabs(GeometricScalingInverse[i]) > zero_epsilon) {
+                        GeometricScalingInverse[i] = 1.0f / GeometricScaling[i];
+                    }
+                    else {
+                        FBXImporter::LogError("cannot invert geometric scaling matrix with a 0.0 scale component");
+                        canscale = false;
+                        break;
+                    }
+                }
+                if (canscale) {
+                    aiMatrix4x4::Scaling(GeometricScalingInverse, chain[TransformationComp_GeometricScalingInverse]);
+                }
+            }
+
+            const aiVector3D& GeometricRotation = PropertyGet<aiVector3D>(props, "GeometricRotation", ok);
+            if (ok && GeometricRotation.SquareLength() > zero_epsilon) {
+                is_complex = true;
+                GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotation]);
+                GetRotationMatrix(rot, GeometricRotation, chain[TransformationComp_GeometricRotationInverse]);
+                chain[TransformationComp_GeometricRotationInverse].Inverse();
+            }
+
+            const aiVector3D& GeometricTranslation = PropertyGet<aiVector3D>(props, "GeometricTranslation", ok);
+            if (ok && GeometricTranslation.SquareLength() > zero_epsilon) {
+                is_complex = true;
+                aiMatrix4x4::Translation(GeometricTranslation, chain[TransformationComp_GeometricTranslation]);
+                aiMatrix4x4::Translation(-GeometricTranslation, chain[TransformationComp_GeometricTranslationInverse]);
+            }
+
+            // is_complex needs to be consistent with NeedsComplexTransformationChain()
+            // or the interplay between this code and the animation converter would
+            // not be guaranteed.
+            ai_assert(NeedsComplexTransformationChain(model) == is_complex);
+
+            std::string name = FixNodeName(model.Name());
+
+            // now, if we have more than just Translation, Scaling and Rotation,
+            // we need to generate a full node chain to accommodate for assimp's
+            // lack to express pivots and offsets.
+            if (is_complex && doc.Settings().preservePivots) {
+                FBXImporter::LogInfo("generating full transformation chain for node: " + name);
+
+                // query the anim_chain_bits dictionary to find out which chain elements
+                // have associated node animation channels. These can not be dropped
+                // even if they have identity transform in bind pose.
+                NodeAnimBitMap::const_iterator it = node_anim_chain_bits.find(name);
+                const unsigned int anim_chain_bitmask = (it == node_anim_chain_bits.end() ? 0 : (*it).second);
+
+                unsigned int bit = 0x1;
+                for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
+                    const TransformationComp comp = static_cast<TransformationComp>(i);
+
+                    if (chain[i].IsIdentity() && (anim_chain_bitmask & bit) == 0) {
+                        continue;
+                    }
+
+                    if (comp == TransformationComp_PostRotation) {
+                        chain[i] = chain[i].Inverse();
+                    }
+
+                    aiNode* nd = new aiNode();
+                    nd->mName.Set(NameTransformationChainNode(name, comp));
+                    nd->mTransformation = chain[i];
+
+                    // geometric inverses go in a post-node chain
+                    if (comp == TransformationComp_GeometricScalingInverse ||
+                        comp == TransformationComp_GeometricRotationInverse ||
+                        comp == TransformationComp_GeometricTranslationInverse
+                        ) {
+                        post_output_nodes.push_back(nd);
+                    }
+                    else {
+                        output_nodes.push_back(nd);
+                    }
+                }
+
+                ai_assert(output_nodes.size());
+                return;
+            }
+
+            // else, we can just multiply the matrices together
+            aiNode* nd = new aiNode();
+            output_nodes.push_back(nd);
+            std::string uniqueName;
+            GetUniqueName(name, uniqueName);
+
+            nd->mName.Set(uniqueName);
+
+            for (const auto &transform : chain) {
+                nd->mTransformation = nd->mTransformation * transform;
+            }
+        }
+
+        void FBXConverter::SetupNodeMetadata(const Model& model, aiNode& nd)
+        {
+            const PropertyTable& props = model.Props();
+            DirectPropertyMap unparsedProperties = props.GetUnparsedProperties();
+
+            // create metadata on node
+            const std::size_t numStaticMetaData = 2;
+            aiMetadata* data = aiMetadata::Alloc(static_cast<unsigned int>(unparsedProperties.size() + numStaticMetaData));
+            nd.mMetaData = data;
+            int index = 0;
+
+            // find user defined properties (3ds Max)
+            data->Set(index++, "UserProperties", aiString(PropertyGet<std::string>(props, "UDP3DSMAX", "")));
+            // preserve the info that a node was marked as Null node in the original file.
+            data->Set(index++, "IsNull", model.IsNull() ? true : false);
+
+            // add unparsed properties to the node's metadata
+            for (const DirectPropertyMap::value_type& prop : unparsedProperties) {
+                // Interpret the property as a concrete type
+                if (const TypedProperty<bool>* interpreted = prop.second->As<TypedProperty<bool> >()) {
+                    data->Set(index++, prop.first, interpreted->Value());
+                }
+                else if (const TypedProperty<int>* interpreted = prop.second->As<TypedProperty<int> >()) {
+                    data->Set(index++, prop.first, interpreted->Value());
+                }
+                else if (const TypedProperty<uint64_t>* interpreted = prop.second->As<TypedProperty<uint64_t> >()) {
+                    data->Set(index++, prop.first, interpreted->Value());
+                }
+                else if (const TypedProperty<float>* interpreted = prop.second->As<TypedProperty<float> >()) {
+                    data->Set(index++, prop.first, interpreted->Value());
+                }
+                else if (const TypedProperty<std::string>* interpreted = prop.second->As<TypedProperty<std::string> >()) {
+                    data->Set(index++, prop.first, aiString(interpreted->Value()));
+                }
+                else if (const TypedProperty<aiVector3D>* interpreted = prop.second->As<TypedProperty<aiVector3D> >()) {
+                    data->Set(index++, prop.first, interpreted->Value());
+                }
+                else {
+                    ai_assert(false);
+                }
+            }
+        }
+
+        void FBXConverter::ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform)
+        {
+            const std::vector<const Geometry*>& geos = model.GetGeometry();
+
+            std::vector<unsigned int> meshes;
+            meshes.reserve(geos.size());
+
+            for (const Geometry* geo : geos) {
+
+                const MeshGeometry* const mesh = dynamic_cast<const MeshGeometry*>(geo);
+                const LineGeometry* const line = dynamic_cast<const LineGeometry*>(geo);
+                if (mesh) {
+                    const std::vector<unsigned int>& indices = ConvertMesh(*mesh, model, node_global_transform, nd);
+                    std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
+                }
+                else if (line) {
+                    const std::vector<unsigned int>& indices = ConvertLine(*line, model, node_global_transform, nd);
+                    std::copy(indices.begin(), indices.end(), std::back_inserter(meshes));
+                }
+                else {
+                    FBXImporter::LogWarn("ignoring unrecognized geometry: " + geo->Name());
+                }
+            }
+
+            if (meshes.size()) {
+                nd.mMeshes = new unsigned int[meshes.size()]();
+                nd.mNumMeshes = static_cast<unsigned int>(meshes.size());
+
+                std::swap_ranges(meshes.begin(), meshes.end(), nd.mMeshes);
+            }
+        }
+
+        std::vector<unsigned int> FBXConverter::ConvertMesh(const MeshGeometry& mesh, const Model& model,
+            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        {
+            std::vector<unsigned int> temp;
+
+            MeshMap::const_iterator it = meshes_converted.find(&mesh);
+            if (it != meshes_converted.end()) {
+                std::copy((*it).second.begin(), (*it).second.end(), std::back_inserter(temp));
+                return temp;
+            }
+
+            const std::vector<aiVector3D>& vertices = mesh.GetVertices();
+            const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
+            if (vertices.empty() || faces.empty()) {
+                FBXImporter::LogWarn("ignoring empty geometry: " + mesh.Name());
+                return temp;
+            }
+
+            // one material per mesh maps easily to aiMesh. Multiple material
+            // meshes need to be split.
+            const MatIndexArray& mindices = mesh.GetMaterialIndices();
+            if (doc.Settings().readMaterials && !mindices.empty()) {
+                const MatIndexArray::value_type base = mindices[0];
+                for (MatIndexArray::value_type index : mindices) {
+                    if (index != base) {
+                        return ConvertMeshMultiMaterial(mesh, model, node_global_transform, nd);
+                    }
+                }
+            }
+
+            // faster code-path, just copy the data
+            temp.push_back(ConvertMeshSingleMaterial(mesh, model, node_global_transform, nd));
+            return temp;
+        }
+
+        std::vector<unsigned int> FBXConverter::ConvertLine(const LineGeometry& line, const Model& model,
+            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        {
+            std::vector<unsigned int> temp;
+
+            const std::vector<aiVector3D>& vertices = line.GetVertices();
+            const std::vector<int>& indices = line.GetIndices();
+            if (vertices.empty() || indices.empty()) {
+                FBXImporter::LogWarn("ignoring empty line: " + line.Name());
+                return temp;
+            }
+
+            aiMesh* const out_mesh = SetupEmptyMesh(line, nd);
+            out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
+
+            // copy vertices
+            out_mesh->mNumVertices = static_cast<unsigned int>(vertices.size());
+            out_mesh->mVertices = new aiVector3D[out_mesh->mNumVertices];
+            std::copy(vertices.begin(), vertices.end(), out_mesh->mVertices);
+
+            //Number of line segments (faces) is "Number of Points - Number of Endpoints"
+            //N.B.: Endpoints in FbxLine are denoted by negative indices.
+            //If such an Index is encountered, add 1 and multiply by -1 to get the real index.
+            unsigned int epcount = 0;
+            for (unsigned i = 0; i < indices.size(); i++)
+            {
+                if (indices[i] < 0) epcount++;
+            }
+            unsigned int pcount = static_cast<unsigned int>( indices.size() );
+            unsigned int scount = out_mesh->mNumFaces = pcount - epcount;
+
+            aiFace* fac = out_mesh->mFaces = new aiFace[scount]();
+            for (unsigned int i = 0; i < pcount; ++i) {
+                if (indices[i] < 0) continue;
+                aiFace& f = *fac++;
+                f.mNumIndices = 2; //2 == aiPrimitiveType_LINE 
+                f.mIndices = new unsigned int[2];
+                f.mIndices[0] = indices[i];
+                int segid = indices[(i + 1 == pcount ? 0 : i + 1)];   //If we have reached he last point, wrap around
+                f.mIndices[1] = (segid < 0 ? (segid + 1)*-1 : segid); //Convert EndPoint Index to normal Index
+            }
+            temp.push_back(static_cast<unsigned int>(meshes.size() - 1));
+            return temp;
+        }
+
+        aiMesh* FBXConverter::SetupEmptyMesh(const Geometry& mesh, aiNode& nd)
+        {
+            aiMesh* const out_mesh = new aiMesh();
+            meshes.push_back(out_mesh);
+            meshes_converted[&mesh].push_back(static_cast<unsigned int>(meshes.size() - 1));
+
+            // set name
+            std::string name = mesh.Name();
+            if (name.substr(0, 10) == "Geometry::") {
+                name = name.substr(10);
+            }
+
+            if (name.length()) {
+                out_mesh->mName.Set(name);
+            }
+            else
+            {
+                out_mesh->mName = nd.mName;
+            }
+
+            return out_mesh;
+        }
+
+        unsigned int FBXConverter::ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
+            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        {
+            const MatIndexArray& mindices = mesh.GetMaterialIndices();
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+
+            const std::vector<aiVector3D>& vertices = mesh.GetVertices();
+            const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
+
+            // copy vertices
+            out_mesh->mNumVertices = static_cast<unsigned int>(vertices.size());
+            out_mesh->mVertices = new aiVector3D[vertices.size()];
+
+            std::copy(vertices.begin(), vertices.end(), out_mesh->mVertices);
+
+            // generate dummy faces
+            out_mesh->mNumFaces = static_cast<unsigned int>(faces.size());
+            aiFace* fac = out_mesh->mFaces = new aiFace[faces.size()]();
+
+            unsigned int cursor = 0;
+            for (unsigned int pcount : faces) {
+                aiFace& f = *fac++;
+                f.mNumIndices = pcount;
+                f.mIndices = new unsigned int[pcount];
+                switch (pcount)
+                {
+                case 1:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
+                    break;
+                case 2:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
+                    break;
+                case 3:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+                    break;
+                default:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+                    break;
+                }
+                for (unsigned int i = 0; i < pcount; ++i) {
+                    f.mIndices[i] = cursor++;
+                }
+            }
+
+            // copy normals
+            const std::vector<aiVector3D>& normals = mesh.GetNormals();
+            if (normals.size()) {
+                ai_assert(normals.size() == vertices.size());
+
+                out_mesh->mNormals = new aiVector3D[vertices.size()];
+                std::copy(normals.begin(), normals.end(), out_mesh->mNormals);
+            }
+
+            // copy tangents - assimp requires both tangents and bitangents (binormals)
+            // to be present, or neither of them. Compute binormals from normals
+            // and tangents if needed.
+            const std::vector<aiVector3D>& tangents = mesh.GetTangents();
+            const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
+
+            if (tangents.size()) {
+                std::vector<aiVector3D> tempBinormals;
+                if (!binormals->size()) {
+                    if (normals.size()) {
+                        tempBinormals.resize(normals.size());
+                        for (unsigned int i = 0; i < tangents.size(); ++i) {
+                            tempBinormals[i] = normals[i] ^ tangents[i];
+                        }
+
+                        binormals = &tempBinormals;
+                    }
+                    else {
+                        binormals = NULL;
+                    }
+                }
+
+                if (binormals) {
+                    ai_assert(tangents.size() == vertices.size());
+                    ai_assert(binormals->size() == vertices.size());
+
+                    out_mesh->mTangents = new aiVector3D[vertices.size()];
+                    std::copy(tangents.begin(), tangents.end(), out_mesh->mTangents);
+
+                    out_mesh->mBitangents = new aiVector3D[vertices.size()];
+                    std::copy(binormals->begin(), binormals->end(), out_mesh->mBitangents);
+                }
+            }
+
+            // copy texture coords
+            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
+                if (uvs.empty()) {
+                    break;
+                }
+
+                aiVector3D* out_uv = out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()];
+                for (const aiVector2D& v : uvs) {
+                    *out_uv++ = aiVector3D(v.x, v.y, 0.0f);
+                }
+
+                out_mesh->mNumUVComponents[i] = 2;
+            }
+
+            // copy vertex colors
+            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i) {
+                const std::vector<aiColor4D>& colors = mesh.GetVertexColors(i);
+                if (colors.empty()) {
+                    break;
+                }
+
+                out_mesh->mColors[i] = new aiColor4D[vertices.size()];
+                std::copy(colors.begin(), colors.end(), out_mesh->mColors[i]);
+            }
+
+            if (!doc.Settings().readMaterials || mindices.empty()) {
+                FBXImporter::LogError("no material assigned to mesh, setting default material");
+                out_mesh->mMaterialIndex = GetDefaultMaterial();
+            }
+            else {
+                ConvertMaterialForMesh(out_mesh, model, mesh, mindices[0]);
+            }
+
+            if (doc.Settings().readWeights && mesh.DeformerSkin() != NULL) {
+                ConvertWeights(out_mesh, model, mesh, node_global_transform, NO_MATERIAL_SEPARATION);
+            }
+
+            std::vector<aiAnimMesh*> animMeshes;
+            for (const BlendShape* blendShape : mesh.GetBlendShapes()) {
+                for (const BlendShapeChannel* blendShapeChannel : blendShape->BlendShapeChannels()) {
+                    const std::vector<const ShapeGeometry*>& shapeGeometries = blendShapeChannel->GetShapeGeometries();
+                    for (size_t i = 0; i < shapeGeometries.size(); i++) {
+                        aiAnimMesh *animMesh = aiCreateAnimMesh(out_mesh);
+                        const ShapeGeometry* shapeGeometry = shapeGeometries.at(i);
+                        const std::vector<aiVector3D>& vertices = shapeGeometry->GetVertices();
+                        const std::vector<aiVector3D>& normals = shapeGeometry->GetNormals();
+                        const std::vector<unsigned int>& indices = shapeGeometry->GetIndices();
+                        animMesh->mName.Set(FixAnimMeshName(shapeGeometry->Name()));
+                        for (size_t j = 0; j < indices.size(); j++) {
+                            unsigned int index = indices.at(j);
+                            aiVector3D vertex = vertices.at(j);
+                            aiVector3D normal = normals.at(j);
+                            unsigned int count = 0;
+                            const unsigned int* outIndices = mesh.ToOutputVertexIndex(index, count);
+                            for (unsigned int k = 0; k < count; k++) {
+                                unsigned int index = outIndices[k];
+                                animMesh->mVertices[index] += vertex;
+                                if (animMesh->mNormals != nullptr) {
+                                    animMesh->mNormals[index] += normal;
+                                    animMesh->mNormals[index].NormalizeSafe();
+                                }
+                            }
+                        }
+                        animMesh->mWeight = shapeGeometries.size() > 1 ? blendShapeChannel->DeformPercent() / 100.0f : 1.0f;
+                        animMeshes.push_back(animMesh);
+                    }
+                }
+            }
+            const size_t numAnimMeshes = animMeshes.size();
+            if (numAnimMeshes > 0) {
+                out_mesh->mNumAnimMeshes = static_cast<unsigned int>(numAnimMeshes);
+                out_mesh->mAnimMeshes = new aiAnimMesh*[numAnimMeshes];
+                for (size_t i = 0; i < numAnimMeshes; i++) {
+                    out_mesh->mAnimMeshes[i] = animMeshes.at(i);
+                }
+            }
+            return static_cast<unsigned int>(meshes.size() - 1);
+        }
+
+        std::vector<unsigned int> FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
+            const aiMatrix4x4& node_global_transform, aiNode& nd)
+        {
+            const MatIndexArray& mindices = mesh.GetMaterialIndices();
+            ai_assert(mindices.size());
+
+            std::set<MatIndexArray::value_type> had;
+            std::vector<unsigned int> indices;
+
+            for (MatIndexArray::value_type index : mindices) {
+                if (had.find(index) == had.end()) {
+
+                    indices.push_back(ConvertMeshMultiMaterial(mesh, model, index, node_global_transform, nd));
+                    had.insert(index);
+                }
+            }
+
+            return indices;
+        }
+
+        unsigned int FBXConverter::ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
+            MatIndexArray::value_type index,
+            const aiMatrix4x4& node_global_transform,
+            aiNode& nd)
+        {
+            aiMesh* const out_mesh = SetupEmptyMesh(mesh, nd);
+
+            const MatIndexArray& mindices = mesh.GetMaterialIndices();
+            const std::vector<aiVector3D>& vertices = mesh.GetVertices();
+            const std::vector<unsigned int>& faces = mesh.GetFaceIndexCounts();
+
+            const bool process_weights = doc.Settings().readWeights && mesh.DeformerSkin() != NULL;
+
+            unsigned int count_faces = 0;
+            unsigned int count_vertices = 0;
+
+            // count faces
+            std::vector<unsigned int>::const_iterator itf = faces.begin();
+            for (MatIndexArray::const_iterator it = mindices.begin(),
+                end = mindices.end(); it != end; ++it, ++itf)
+            {
+                if ((*it) != index) {
+                    continue;
+                }
+                ++count_faces;
+                count_vertices += *itf;
+            }
+
+            ai_assert(count_faces);
+            ai_assert(count_vertices);
+
+            // mapping from output indices to DOM indexing, needed to resolve weights
+            std::vector<unsigned int> reverseMapping;
+
+            if (process_weights) {
+                reverseMapping.resize(count_vertices);
+            }
+
+            // allocate output data arrays, but don't fill them yet
+            out_mesh->mNumVertices = count_vertices;
+            out_mesh->mVertices = new aiVector3D[count_vertices];
+
+            out_mesh->mNumFaces = count_faces;
+            aiFace* fac = out_mesh->mFaces = new aiFace[count_faces]();
+
+
+            // allocate normals
+            const std::vector<aiVector3D>& normals = mesh.GetNormals();
+            if (normals.size()) {
+                ai_assert(normals.size() == vertices.size());
+                out_mesh->mNormals = new aiVector3D[vertices.size()];
+            }
+
+            // allocate tangents, binormals.
+            const std::vector<aiVector3D>& tangents = mesh.GetTangents();
+            const std::vector<aiVector3D>* binormals = &mesh.GetBinormals();
+            std::vector<aiVector3D> tempBinormals;
+
+            if (tangents.size()) {
+                if (!binormals->size()) {
+                    if (normals.size()) {
+                        // XXX this computes the binormals for the entire mesh, not only
+                        // the part for which we need them.
+                        tempBinormals.resize(normals.size());
+                        for (unsigned int i = 0; i < tangents.size(); ++i) {
+                            tempBinormals[i] = normals[i] ^ tangents[i];
+                        }
+
+                        binormals = &tempBinormals;
+                    }
+                    else {
+                        binormals = NULL;
+                    }
+                }
+
+                if (binormals) {
+                    ai_assert(tangents.size() == vertices.size() && binormals->size() == vertices.size());
+
+                    out_mesh->mTangents = new aiVector3D[vertices.size()];
+                    out_mesh->mBitangents = new aiVector3D[vertices.size()];
+                }
+            }
+
+            // allocate texture coords
+            unsigned int num_uvs = 0;
+            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i, ++num_uvs) {
+                const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(i);
+                if (uvs.empty()) {
+                    break;
+                }
+
+                out_mesh->mTextureCoords[i] = new aiVector3D[vertices.size()];
+                out_mesh->mNumUVComponents[i] = 2;
+            }
+
+            // allocate vertex colors
+            unsigned int num_vcs = 0;
+            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_COLOR_SETS; ++i, ++num_vcs) {
+                const std::vector<aiColor4D>& colors = mesh.GetVertexColors(i);
+                if (colors.empty()) {
+                    break;
+                }
+
+                out_mesh->mColors[i] = new aiColor4D[vertices.size()];
+            }
+
+            unsigned int cursor = 0, in_cursor = 0;
+
+            itf = faces.begin();
+            for (MatIndexArray::const_iterator it = mindices.begin(), end = mindices.end(); it != end; ++it, ++itf)
+            {
+                const unsigned int pcount = *itf;
+                if ((*it) != index) {
+                    in_cursor += pcount;
+                    continue;
+                }
+
+                aiFace& f = *fac++;
+
+                f.mNumIndices = pcount;
+                f.mIndices = new unsigned int[pcount];
+                switch (pcount)
+                {
+                case 1:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
+                    break;
+                case 2:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
+                    break;
+                case 3:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+                    break;
+                default:
+                    out_mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+                    break;
+                }
+                for (unsigned int i = 0; i < pcount; ++i, ++cursor, ++in_cursor) {
+                    f.mIndices[i] = cursor;
+
+                    if (reverseMapping.size()) {
+                        reverseMapping[cursor] = in_cursor;
+                    }
+
+                    out_mesh->mVertices[cursor] = vertices[in_cursor];
+
+                    if (out_mesh->mNormals) {
+                        out_mesh->mNormals[cursor] = normals[in_cursor];
+                    }
+
+                    if (out_mesh->mTangents) {
+                        out_mesh->mTangents[cursor] = tangents[in_cursor];
+                        out_mesh->mBitangents[cursor] = (*binormals)[in_cursor];
+                    }
+
+                    for (unsigned int j = 0; j < num_uvs; ++j) {
+                        const std::vector<aiVector2D>& uvs = mesh.GetTextureCoords(j);
+                        out_mesh->mTextureCoords[j][cursor] = aiVector3D(uvs[in_cursor].x, uvs[in_cursor].y, 0.0f);
+                    }
+
+                    for (unsigned int j = 0; j < num_vcs; ++j) {
+                        const std::vector<aiColor4D>& cols = mesh.GetVertexColors(j);
+                        out_mesh->mColors[j][cursor] = cols[in_cursor];
+                    }
+                }
+            }
+
+            ConvertMaterialForMesh(out_mesh, model, mesh, index);
+
+            if (process_weights) {
+                ConvertWeights(out_mesh, model, mesh, node_global_transform, index, &reverseMapping);
+            }
+
+            return static_cast<unsigned int>(meshes.size() - 1);
+        }
+
+        void FBXConverter::ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
+            const aiMatrix4x4& node_global_transform,
+            unsigned int materialIndex,
+            std::vector<unsigned int>* outputVertStartIndices)
+        {
+            ai_assert(geo.DeformerSkin());
+
+            std::vector<size_t> out_indices;
+            std::vector<size_t> index_out_indices;
+            std::vector<size_t> count_out_indices;
+
+            const Skin& sk = *geo.DeformerSkin();
+
+            std::vector<aiBone*> bones;
+            bones.reserve(sk.Clusters().size());
+
+            const bool no_mat_check = materialIndex == NO_MATERIAL_SEPARATION;
+            ai_assert(no_mat_check || outputVertStartIndices);
+
+            try {
+
+                for (const Cluster* cluster : sk.Clusters()) {
+                    ai_assert(cluster);
+
+                    const WeightIndexArray& indices = cluster->GetIndices();
+
+                    if (indices.empty()) {
+                        continue;
+                    }
+
+                    const MatIndexArray& mats = geo.GetMaterialIndices();
+
+                    bool ok = false;
+
+                    const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
+
+                    count_out_indices.clear();
+                    index_out_indices.clear();
+                    out_indices.clear();
+
+                    // now check if *any* of these weights is contained in the output mesh,
+                    // taking notes so we don't need to do it twice.
+                    for (WeightIndexArray::value_type index : indices) {
+
+                        unsigned int count = 0;
+                        const unsigned int* const out_idx = geo.ToOutputVertexIndex(index, count);
+                        // ToOutputVertexIndex only returns NULL if index is out of bounds
+                        // which should never happen
+                        ai_assert(out_idx != NULL);
+
+                        index_out_indices.push_back(no_index_sentinel);
+                        count_out_indices.push_back(0);
+
+                        for (unsigned int i = 0; i < count; ++i) {
+                            if (no_mat_check || static_cast<size_t>(mats[geo.FaceForVertexIndex(out_idx[i])]) == materialIndex) {
+
+                                if (index_out_indices.back() == no_index_sentinel) {
+                                    index_out_indices.back() = out_indices.size();
+
+                                }
+
+                                if (no_mat_check) {
+                                    out_indices.push_back(out_idx[i]);
+                                }
+                                else {
+                                    // this extra lookup is in O(logn), so the entire algorithm becomes O(nlogn)
+                                    const std::vector<unsigned int>::iterator it = std::lower_bound(
+                                        outputVertStartIndices->begin(),
+                                        outputVertStartIndices->end(),
+                                        out_idx[i]
+                                    );
+
+                                    out_indices.push_back(std::distance(outputVertStartIndices->begin(), it));
+                                }
+
+                                ++count_out_indices.back();
+                                ok = true;
+                            }
+                        }
+                    }
+
+                    // if we found at least one, generate the output bones
+                    // XXX this could be heavily simplified by collecting the bone
+                    // data in a single step.
+                    if (ok) {
+                        ConvertCluster(bones, model, *cluster, out_indices, index_out_indices,
+                            count_out_indices, node_global_transform);
+                    }
+                }
+            }
+            catch (std::exception&) {
+                std::for_each(bones.begin(), bones.end(), Util::delete_fun<aiBone>());
+                throw;
+            }
+
+            if (bones.empty()) {
+                return;
+            }
+
+            out->mBones = new aiBone*[bones.size()]();
+            out->mNumBones = static_cast<unsigned int>(bones.size());
+
+            std::swap_ranges(bones.begin(), bones.end(), out->mBones);
+        }
+
+        void FBXConverter::ConvertCluster(std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
+            std::vector<size_t>& out_indices,
+            std::vector<size_t>& index_out_indices,
+            std::vector<size_t>& count_out_indices,
+            const aiMatrix4x4& node_global_transform)
+        {
+
+            aiBone* const bone = new aiBone();
+            bones.push_back(bone);
+
+            bone->mName = FixNodeName(cl.TargetNode()->Name());
+
+            bone->mOffsetMatrix = cl.TransformLink();
+            bone->mOffsetMatrix.Inverse();
+
+            bone->mOffsetMatrix = bone->mOffsetMatrix * node_global_transform;
+
+            bone->mNumWeights = static_cast<unsigned int>(out_indices.size());
+            aiVertexWeight* cursor = bone->mWeights = new aiVertexWeight[out_indices.size()];
+
+            const size_t no_index_sentinel = std::numeric_limits<size_t>::max();
+            const WeightArray& weights = cl.GetWeights();
+
+            const size_t c = index_out_indices.size();
+            for (size_t i = 0; i < c; ++i) {
+                const size_t index_index = index_out_indices[i];
+
+                if (index_index == no_index_sentinel) {
+                    continue;
+                }
+
+                const size_t cc = count_out_indices[i];
+                for (size_t j = 0; j < cc; ++j) {
+                    aiVertexWeight& out_weight = *cursor++;
+
+                    out_weight.mVertexId = static_cast<unsigned int>(out_indices[index_index + j]);
+                    out_weight.mWeight = weights[i];
+                }
+            }
+        }
+
+        void FBXConverter::ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
+            MatIndexArray::value_type materialIndex)
+        {
+            // locate source materials for this mesh
+            const std::vector<const Material*>& mats = model.GetMaterials();
+            if (static_cast<unsigned int>(materialIndex) >= mats.size() || materialIndex < 0) {
+                FBXImporter::LogError("material index out of bounds, setting default material");
+                out->mMaterialIndex = GetDefaultMaterial();
+                return;
+            }
+
+            const Material* const mat = mats[materialIndex];
+            MaterialMap::const_iterator it = materials_converted.find(mat);
+            if (it != materials_converted.end()) {
+                out->mMaterialIndex = (*it).second;
+                return;
+            }
+
+            out->mMaterialIndex = ConvertMaterial(*mat, &geo);
+            materials_converted[mat] = out->mMaterialIndex;
+        }
+
+        unsigned int FBXConverter::GetDefaultMaterial()
+        {
+            if (defaultMaterialIndex) {
+                return defaultMaterialIndex - 1;
+            }
+
+            aiMaterial* out_mat = new aiMaterial();
+            materials.push_back(out_mat);
+
+            const aiColor3D diffuse = aiColor3D(0.8f, 0.8f, 0.8f);
+            out_mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+
+            aiString s;
+            s.Set(AI_DEFAULT_MATERIAL_NAME);
+
+            out_mat->AddProperty(&s, AI_MATKEY_NAME);
+
+            defaultMaterialIndex = static_cast<unsigned int>(materials.size());
+            return defaultMaterialIndex - 1;
+        }
+
+
+        unsigned int FBXConverter::ConvertMaterial(const Material& material, const MeshGeometry* const mesh)
+        {
+            const PropertyTable& props = material.Props();
+
+            // generate empty output material
+            aiMaterial* out_mat = new aiMaterial();
+            materials_converted[&material] = static_cast<unsigned int>(materials.size());
+
+            materials.push_back(out_mat);
+
+            aiString str;
+
+            // strip Material:: prefix
+            std::string name = material.Name();
+            if (name.substr(0, 10) == "Material::") {
+                name = name.substr(10);
+            }
+
+            // set material name if not empty - this could happen
+            // and there should be no key for it in this case.
+            if (name.length()) {
+                str.Set(name);
+                out_mat->AddProperty(&str, AI_MATKEY_NAME);
+            }
+
+            // shading stuff and colors
+            SetShadingPropertiesCommon(out_mat, props);
+            SetShadingPropertiesRaw( out_mat, props, material.Textures(), mesh );
+
+            // texture assignments
+            SetTextureProperties(out_mat, material.Textures(), mesh);
+            SetTextureProperties(out_mat, material.LayeredTextures(), mesh);
+
+            return static_cast<unsigned int>(materials.size() - 1);
+        }
+
+        unsigned int FBXConverter::ConvertVideo(const Video& video)
+        {
+            // generate empty output texture
+            aiTexture* out_tex = new aiTexture();
+            textures.push_back(out_tex);
+
+            // assuming the texture is compressed
+            out_tex->mWidth = static_cast<unsigned int>(video.ContentLength()); // total data size
+            out_tex->mHeight = 0; // fixed to 0
+
+            // steal the data from the Video to avoid an additional copy
+            out_tex->pcData = reinterpret_cast<aiTexel*>(const_cast<Video&>(video).RelinquishContent());
+
+            // try to extract a hint from the file extension
+            const std::string& filename = video.FileName().empty() ? video.RelativeFilename() : video.FileName();
+            std::string ext = BaseImporter::GetExtension(filename);
+
+            if (ext == "jpeg") {
+                ext = "jpg";
+            }
+
+            if (ext.size() <= 3) {
+                memcpy(out_tex->achFormatHint, ext.c_str(), ext.size());
+            }
+
+            out_tex->mFilename.Set(video.FileName().c_str());
+
+            return static_cast<unsigned int>(textures.size() - 1);
+        }
+
+        aiString FBXConverter::GetTexturePath(const Texture* tex)
+        {
+            aiString path;
+            path.Set(tex->RelativeFilename());
+
+            const Video* media = tex->Media();
+            if (media != nullptr) {
+                bool textureReady = false; //tells if our texture is ready (if it was loaded or if it was found)
+                unsigned int index;
+
+                VideoMap::const_iterator it = textures_converted.find(media);
+                if (it != textures_converted.end()) {
+                    index = (*it).second;
+                    textureReady = true;
+                }
+                else {
+                    if (media->ContentLength() > 0) {
+                        index = ConvertVideo(*media);
+                        textures_converted[media] = index;
+                        textureReady = true;
+                    }
+                }
+
+                // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture), if the texture is ready
+                if (doc.Settings().useLegacyEmbeddedTextureNaming) {
+                    if (textureReady) {
+                        // TODO: check the possibility of using the flag "AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING"
+                        // In FBX files textures are now stored internally by Assimp with their filename included
+                        // Now Assimp can lookup through the loaded textures after all data is processed
+                        // We need to load all textures before referencing them, as FBX file format order may reference a texture before loading it
+                        // This may occur on this case too, it has to be studied
+                        path.data[0] = '*';
+                        path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index);
+                    }
+                }
+            }
+
+            return path;
+        }
+
+        void FBXConverter::TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures,
+            const std::string& propName,
+            aiTextureType target, const MeshGeometry* const mesh)
+        {
+            TextureMap::const_iterator it = textures.find(propName);
+            if (it == textures.end()) {
+                return;
+            }
+
+            const Texture* const tex = (*it).second;
+            if (tex != 0)
+            {
+                aiString path = GetTexturePath(tex);
+                out_mat->AddProperty(&path, _AI_MATKEY_TEXTURE_BASE, target, 0);
+
+                aiUVTransform uvTrafo;
+                // XXX handle all kinds of UV transformations
+                uvTrafo.mScaling = tex->UVScaling();
+                uvTrafo.mTranslation = tex->UVTranslation();
+                out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, 0);
+
+                const PropertyTable& props = tex->Props();
+
+                int uvIndex = 0;
+
+                bool ok;
+                const std::string& uvSet = PropertyGet<std::string>(props, "UVSet", ok);
+                if (ok) {
+                    // "default" is the name which usually appears in the FbxFileTexture template
+                    if (uvSet != "default" && uvSet.length()) {
+                        // this is a bit awkward - we need to find a mesh that uses this
+                        // material and scan its UV channels for the given UV name because
+                        // assimp references UV channels by index, not by name.
+
+                        // XXX: the case that UV channels may appear in different orders
+                        // in meshes is unhandled. A possible solution would be to sort
+                        // the UV channels alphabetically, but this would have the side
+                        // effect that the primary (first) UV channel would sometimes
+                        // be moved, causing trouble when users read only the first
+                        // UV channel and ignore UV channel assignments altogether.
+
+                        const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(),
+                            std::find(materials.begin(), materials.end(), out_mat)
+                        ));
+
+
+                        uvIndex = -1;
+                        if (!mesh)
+                        {
+                            for (const MeshMap::value_type& v : meshes_converted) {
+                                const MeshGeometry* const meshGeom = dynamic_cast<const MeshGeometry*> (v.first);
+                                if (!meshGeom) {
+                                    continue;
+                                }
+
+                                const MatIndexArray& mats = meshGeom->GetMaterialIndices();
+                                if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) {
+                                    continue;
+                                }
+
+                                int index = -1;
+                                for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                                    if (meshGeom->GetTextureCoords(i).empty()) {
+                                        break;
+                                    }
+                                    const std::string& name = meshGeom->GetTextureCoordChannelName(i);
+                                    if (name == uvSet) {
+                                        index = static_cast<int>(i);
+                                        break;
+                                    }
+                                }
+                                if (index == -1) {
+                                    FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
+                                    continue;
+                                }
+
+                                if (uvIndex == -1) {
+                                    uvIndex = index;
+                                }
+                                else {
+                                    FBXImporter::LogWarn("the UV channel named " + uvSet +
+                                        " appears at different positions in meshes, results will be wrong");
+                                }
+                            }
+                        }
+                        else
+                        {
+                            int index = -1;
+                            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                                if (mesh->GetTextureCoords(i).empty()) {
+                                    break;
+                                }
+                                const std::string& name = mesh->GetTextureCoordChannelName(i);
+                                if (name == uvSet) {
+                                    index = static_cast<int>(i);
+                                    break;
+                                }
+                            }
+                            if (index == -1) {
+                                FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
+                            }
+
+                            if (uvIndex == -1) {
+                                uvIndex = index;
+                            }
+                        }
+
+                        if (uvIndex == -1) {
+                            FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel");
+                            uvIndex = 0;
+                        }
+                    }
+                }
+
+                out_mat->AddProperty(&uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, 0);
+            }
+        }
+
+        void FBXConverter::TrySetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
+            const std::string& propName,
+            aiTextureType target, const MeshGeometry* const mesh) {
+            LayeredTextureMap::const_iterator it = layeredTextures.find(propName);
+            if (it == layeredTextures.end()) {
+                return;
+            }
+
+            int texCount = (*it).second->textureCount();
+
+            // Set the blend mode for layered textures
+            int blendmode = (*it).second->GetBlendMode();
+            out_mat->AddProperty(&blendmode, 1, _AI_MATKEY_TEXOP_BASE, target, 0);
+
+            for (int texIndex = 0; texIndex < texCount; texIndex++) {
+
+                const Texture* const tex = (*it).second->getTexture(texIndex);
+
+                aiString path = GetTexturePath(tex);
+                out_mat->AddProperty(&path, _AI_MATKEY_TEXTURE_BASE, target, texIndex);
+
+                aiUVTransform uvTrafo;
+                // XXX handle all kinds of UV transformations
+                uvTrafo.mScaling = tex->UVScaling();
+                uvTrafo.mTranslation = tex->UVTranslation();
+                out_mat->AddProperty(&uvTrafo, 1, _AI_MATKEY_UVTRANSFORM_BASE, target, texIndex);
+
+                const PropertyTable& props = tex->Props();
+
+                int uvIndex = 0;
+
+                bool ok;
+                const std::string& uvSet = PropertyGet<std::string>(props, "UVSet", ok);
+                if (ok) {
+                    // "default" is the name which usually appears in the FbxFileTexture template
+                    if (uvSet != "default" && uvSet.length()) {
+                        // this is a bit awkward - we need to find a mesh that uses this
+                        // material and scan its UV channels for the given UV name because
+                        // assimp references UV channels by index, not by name.
+
+                        // XXX: the case that UV channels may appear in different orders
+                        // in meshes is unhandled. A possible solution would be to sort
+                        // the UV channels alphabetically, but this would have the side
+                        // effect that the primary (first) UV channel would sometimes
+                        // be moved, causing trouble when users read only the first
+                        // UV channel and ignore UV channel assignments altogether.
+
+                        const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(),
+                            std::find(materials.begin(), materials.end(), out_mat)
+                        ));
+
+                        uvIndex = -1;
+                        if (!mesh)
+                        {
+                            for (const MeshMap::value_type& v : meshes_converted) {
+                                const MeshGeometry* const meshGeom = dynamic_cast<const MeshGeometry*> (v.first);
+                                if (!meshGeom) {
+                                    continue;
+                                }
+
+                                const MatIndexArray& mats = meshGeom->GetMaterialIndices();
+                                if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) {
+                                    continue;
+                                }
+
+                                int index = -1;
+                                for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                                    if (meshGeom->GetTextureCoords(i).empty()) {
+                                        break;
+                                    }
+                                    const std::string& name = meshGeom->GetTextureCoordChannelName(i);
+                                    if (name == uvSet) {
+                                        index = static_cast<int>(i);
+                                        break;
+                                    }
+                                }
+                                if (index == -1) {
+                                    FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
+                                    continue;
+                                }
+
+                                if (uvIndex == -1) {
+                                    uvIndex = index;
+                                }
+                                else {
+                                    FBXImporter::LogWarn("the UV channel named " + uvSet +
+                                        " appears at different positions in meshes, results will be wrong");
+                                }
+                            }
+                        }
+                        else
+                        {
+                            int index = -1;
+                            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                                if (mesh->GetTextureCoords(i).empty()) {
+                                    break;
+                                }
+                                const std::string& name = mesh->GetTextureCoordChannelName(i);
+                                if (name == uvSet) {
+                                    index = static_cast<int>(i);
+                                    break;
+                                }
+                            }
+                            if (index == -1) {
+                                FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
+                            }
+
+                            if (uvIndex == -1) {
+                                uvIndex = index;
+                            }
+                        }
+
+                        if (uvIndex == -1) {
+                            FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel");
+                            uvIndex = 0;
+                        }
+                    }
+                }
+
+                out_mat->AddProperty(&uvIndex, 1, _AI_MATKEY_UVWSRC_BASE, target, texIndex);
+            }
+        }
+
+        void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh)
+        {
+            TrySetTextureProperties(out_mat, textures, "DiffuseColor", aiTextureType_DIFFUSE, mesh);
+            TrySetTextureProperties(out_mat, textures, "AmbientColor", aiTextureType_AMBIENT, mesh);
+            TrySetTextureProperties(out_mat, textures, "EmissiveColor", aiTextureType_EMISSIVE, mesh);
+            TrySetTextureProperties(out_mat, textures, "SpecularColor", aiTextureType_SPECULAR, mesh);
+            TrySetTextureProperties(out_mat, textures, "SpecularFactor", aiTextureType_SPECULAR, mesh);
+            TrySetTextureProperties(out_mat, textures, "TransparentColor", aiTextureType_OPACITY, mesh);
+            TrySetTextureProperties(out_mat, textures, "ReflectionColor", aiTextureType_REFLECTION, mesh);
+            TrySetTextureProperties(out_mat, textures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh);
+            TrySetTextureProperties(out_mat, textures, "NormalMap", aiTextureType_NORMALS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Bump", aiTextureType_HEIGHT, mesh);
+            TrySetTextureProperties(out_mat, textures, "ShininessExponent", aiTextureType_SHININESS, mesh);
+			TrySetTextureProperties( out_mat, textures, "TransparencyFactor", aiTextureType_OPACITY, mesh );
+			TrySetTextureProperties( out_mat, textures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh );
+            //Maya counterparts
+            TrySetTextureProperties(out_mat, textures, "Maya|DiffuseTexture", aiTextureType_DIFFUSE, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|NormalTexture", aiTextureType_NORMALS, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|SpecularTexture", aiTextureType_SPECULAR, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|FalloffTexture", aiTextureType_OPACITY, mesh);
+            TrySetTextureProperties(out_mat, textures, "Maya|ReflectionMapTexture", aiTextureType_REFLECTION, mesh);
+        }
+
+        void FBXConverter::SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh)
+        {
+            TrySetTextureProperties(out_mat, layeredTextures, "DiffuseColor", aiTextureType_DIFFUSE, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "AmbientColor", aiTextureType_AMBIENT, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "EmissiveColor", aiTextureType_EMISSIVE, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "SpecularColor", aiTextureType_SPECULAR, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "SpecularFactor", aiTextureType_SPECULAR, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "TransparentColor", aiTextureType_OPACITY, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "ReflectionColor", aiTextureType_REFLECTION, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "DisplacementColor", aiTextureType_DISPLACEMENT, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "NormalMap", aiTextureType_NORMALS, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "Bump", aiTextureType_HEIGHT, mesh);
+            TrySetTextureProperties(out_mat, layeredTextures, "ShininessExponent", aiTextureType_SHININESS, mesh);
+			TrySetTextureProperties( out_mat, layeredTextures, "EmissiveFactor", aiTextureType_EMISSIVE, mesh );
+			TrySetTextureProperties( out_mat, layeredTextures, "TransparencyFactor", aiTextureType_OPACITY, mesh );
+        }
+
+        aiColor3D FBXConverter::GetColorPropertyFactored(const PropertyTable& props, const std::string& colorName,
+            const std::string& factorName, bool& result, bool useTemplate)
+        {
+            result = true;
+
+            bool ok;
+            aiVector3D BaseColor = PropertyGet<aiVector3D>(props, colorName, ok, useTemplate);
+            if (!ok) {
+                result = false;
+                return aiColor3D(0.0f, 0.0f, 0.0f);
+            }
+
+            // if no factor name, return the colour as is
+            if (factorName.empty()) {
+                return aiColor3D(BaseColor.x, BaseColor.y, BaseColor.z);
+            }
+
+            // otherwise it should be multiplied by the factor, if found.
+            float factor = PropertyGet<float>(props, factorName, ok, useTemplate);
+            if (ok) {
+                BaseColor *= factor;
+            }
+            return aiColor3D(BaseColor.x, BaseColor.y, BaseColor.z);
+        }
+
+        aiColor3D FBXConverter::GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName,
+            bool& result)
+        {
+            return GetColorPropertyFactored(props, baseName + "Color", baseName + "Factor", result, true);
+        }
+
+        aiColor3D FBXConverter::GetColorProperty(const PropertyTable& props, const std::string& colorName,
+            bool& result, bool useTemplate)
+        {
+            result = true;
+            bool ok;
+            const aiVector3D& ColorVec = PropertyGet<aiVector3D>(props, colorName, ok, useTemplate);
+            if (!ok) {
+                result = false;
+                return aiColor3D(0.0f, 0.0f, 0.0f);
+            }
+            return aiColor3D(ColorVec.x, ColorVec.y, ColorVec.z);
+        }
+
+        void FBXConverter::SetShadingPropertiesCommon(aiMaterial* out_mat, const PropertyTable& props)
+        {
+            // Set shading properties.
+            // Modern FBX Files have two separate systems for defining these,
+            // with only the more comprehensive one described in the property template.
+            // Likely the other values are a legacy system,
+            // which is still always exported by the official FBX SDK.
+            //
+            // Blender's FBX import and export mostly ignore this legacy system,
+            // and as we only support recent versions of FBX anyway, we can do the same.
+            bool ok;
+
+            const aiColor3D& Diffuse = GetColorPropertyFromMaterial(props, "Diffuse", ok);
+            if (ok) {
+                out_mat->AddProperty(&Diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+            }
+
+            const aiColor3D& Emissive = GetColorPropertyFromMaterial(props, "Emissive", ok);
+            if (ok) {
+                out_mat->AddProperty(&Emissive, 1, AI_MATKEY_COLOR_EMISSIVE);
+            }
+
+            const aiColor3D& Ambient = GetColorPropertyFromMaterial(props, "Ambient", ok);
+            if (ok) {
+                out_mat->AddProperty(&Ambient, 1, AI_MATKEY_COLOR_AMBIENT);
+            }
+
+            // we store specular factor as SHININESS_STRENGTH, so just get the color
+            const aiColor3D& Specular = GetColorProperty(props, "SpecularColor", ok, true);
+            if (ok) {
+                out_mat->AddProperty(&Specular, 1, AI_MATKEY_COLOR_SPECULAR);
+            }
+
+            // and also try to get SHININESS_STRENGTH
+            const float SpecularFactor = PropertyGet<float>(props, "SpecularFactor", ok, true);
+            if (ok) {
+                out_mat->AddProperty(&SpecularFactor, 1, AI_MATKEY_SHININESS_STRENGTH);
+            }
+
+            // and the specular exponent
+            const float ShininessExponent = PropertyGet<float>(props, "ShininessExponent", ok);
+            if (ok) {
+                out_mat->AddProperty(&ShininessExponent, 1, AI_MATKEY_SHININESS);
+            }
+
+            // TransparentColor / TransparencyFactor... gee thanks FBX :rolleyes:
+            const aiColor3D& Transparent = GetColorPropertyFactored(props, "TransparentColor", "TransparencyFactor", ok);
+            float CalculatedOpacity = 1.0f;
+            if (ok) {
+                out_mat->AddProperty(&Transparent, 1, AI_MATKEY_COLOR_TRANSPARENT);
+                // as calculated by FBX SDK 2017:
+                CalculatedOpacity = 1.0f - ((Transparent.r + Transparent.g + Transparent.b) / 3.0f);
+            }
+
+            // use of TransparencyFactor is inconsistent.
+            // Maya always stores it as 1.0,
+            // so we can't use it to set AI_MATKEY_OPACITY.
+            // Blender is more sensible and stores it as the alpha value.
+            // However both the FBX SDK and Blender always write an additional
+            // legacy "Opacity" field, so we can try to use that.
+            //
+            // If we can't find it,
+            // we can fall back to the value which the FBX SDK calculates
+            // from transparency colour (RGB) and factor (F) as
+            // 1.0 - F*((R+G+B)/3).
+            //
+            // There's no consistent way to interpret this opacity value,
+            // so it's up to clients to do the correct thing.
+            const float Opacity = PropertyGet<float>(props, "Opacity", ok);
+            if (ok) {
+                out_mat->AddProperty(&Opacity, 1, AI_MATKEY_OPACITY);
+            }
+            else if (CalculatedOpacity != 1.0) {
+                out_mat->AddProperty(&CalculatedOpacity, 1, AI_MATKEY_OPACITY);
+            }
+
+            // reflection color and factor are stored separately
+            const aiColor3D& Reflection = GetColorProperty(props, "ReflectionColor", ok, true);
+            if (ok) {
+                out_mat->AddProperty(&Reflection, 1, AI_MATKEY_COLOR_REFLECTIVE);
+            }
+
+            float ReflectionFactor = PropertyGet<float>(props, "ReflectionFactor", ok, true);
+            if (ok) {
+                out_mat->AddProperty(&ReflectionFactor, 1, AI_MATKEY_REFLECTIVITY);
+            }
+
+            const float BumpFactor = PropertyGet<float>(props, "BumpFactor", ok);
+            if (ok) {
+                out_mat->AddProperty(&BumpFactor, 1, AI_MATKEY_BUMPSCALING);
+            }
+
+            const float DispFactor = PropertyGet<float>(props, "DisplacementFactor", ok);
+            if (ok) {
+                out_mat->AddProperty(&DispFactor, 1, "$mat.displacementscaling", 0, 0);
+    }
+}
+
+
+void FBXConverter::SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTable& props, const TextureMap& textures, const MeshGeometry* const mesh)
+{
+    // Add all the unparsed properties with a "$raw." prefix
+
+    const std::string prefix = "$raw.";
+
+    for (const DirectPropertyMap::value_type& prop : props.GetUnparsedProperties()) {
+
+        std::string name = prefix + prop.first;
+
+        if (const TypedProperty<aiVector3D>* interpreted = prop.second->As<TypedProperty<aiVector3D> >())
+        {
+            out_mat->AddProperty(&interpreted->Value(), 1, name.c_str(), 0, 0);
+        }
+        else if (const TypedProperty<aiColor3D>* interpreted = prop.second->As<TypedProperty<aiColor3D> >())
+        {
+            out_mat->AddProperty(&interpreted->Value(), 1, name.c_str(), 0, 0);
+        }
+        else if (const TypedProperty<aiColor4D>* interpreted = prop.second->As<TypedProperty<aiColor4D> >())
+        {
+            out_mat->AddProperty(&interpreted->Value(), 1, name.c_str(), 0, 0);
+        }
+        else if (const TypedProperty<float>* interpreted = prop.second->As<TypedProperty<float> >())
+        {
+            out_mat->AddProperty(&interpreted->Value(), 1, name.c_str(), 0, 0);
+        }
+        else if (const TypedProperty<int>* interpreted = prop.second->As<TypedProperty<int> >())
+        {
+            out_mat->AddProperty(&interpreted->Value(), 1, name.c_str(), 0, 0);
+        }
+        else if (const TypedProperty<bool>* interpreted = prop.second->As<TypedProperty<bool> >())
+        {
+            int value = interpreted->Value() ? 1 : 0;
+            out_mat->AddProperty(&value, 1, name.c_str(), 0, 0);
+        }
+        else if (const TypedProperty<std::string>* interpreted = prop.second->As<TypedProperty<std::string> >())
+        {
+            const aiString value = aiString(interpreted->Value());
+            out_mat->AddProperty(&value, name.c_str(), 0, 0);
+        }
+    }
+
+    // Add the textures' properties
+
+    for (TextureMap::const_iterator it = textures.begin(); it != textures.end(); it++) {
+
+        std::string name = prefix + it->first;
+
+        const Texture* const tex = (*it).second;
+        if (tex != nullptr)
+        {
+            aiString path;
+            path.Set(tex->RelativeFilename());
+
+            const Video* media = tex->Media();
+            if (media != nullptr && media->ContentLength() > 0) {
+                unsigned int index;
+
+                VideoMap::const_iterator it = textures_converted.find(media);
+                if (it != textures_converted.end()) {
+                    index = (*it).second;
+                }
+                else {
+                    index = ConvertVideo(*media);
+                    textures_converted[media] = index;
+                }
+
+                // setup texture reference string (copied from ColladaLoader::FindFilenameForEffectTexture)
+                path.data[0] = '*';
+                path.length = 1 + ASSIMP_itoa10(path.data + 1, MAXLEN - 1, index);
+            }
+
+            out_mat->AddProperty(&path, (name + "|file").c_str(), aiTextureType_UNKNOWN, 0);
+
+            aiUVTransform uvTrafo;
+            // XXX handle all kinds of UV transformations
+            uvTrafo.mScaling = tex->UVScaling();
+            uvTrafo.mTranslation = tex->UVTranslation();
+            out_mat->AddProperty(&uvTrafo, 1, (name + "|uvtrafo").c_str(), aiTextureType_UNKNOWN, 0);
+ 
+            int uvIndex = 0;
+
+            bool uvFound = false;
+            const std::string& uvSet = PropertyGet<std::string>(tex->Props(), "UVSet", uvFound);
+            if (uvFound) {
+                // "default" is the name which usually appears in the FbxFileTexture template
+                if (uvSet != "default" && uvSet.length()) {
+                    // this is a bit awkward - we need to find a mesh that uses this
+                    // material and scan its UV channels for the given UV name because
+                    // assimp references UV channels by index, not by name.
+
+                    // XXX: the case that UV channels may appear in different orders
+                    // in meshes is unhandled. A possible solution would be to sort
+                    // the UV channels alphabetically, but this would have the side
+                    // effect that the primary (first) UV channel would sometimes
+                    // be moved, causing trouble when users read only the first
+                    // UV channel and ignore UV channel assignments altogether.
+
+                    std::vector<aiMaterial*>::iterator materialIt = std::find(materials.begin(), materials.end(), out_mat);
+                    const unsigned int matIndex = static_cast<unsigned int>(std::distance(materials.begin(), materialIt));
+
+                    uvIndex = -1;
+                    if (!mesh)
+                    {
+                        for (const MeshMap::value_type& v : meshes_converted) {
+                            const MeshGeometry* const meshGeom = dynamic_cast<const MeshGeometry*>(v.first);
+                            if (!meshGeom) {
+                                continue;
+                            }
+
+                            const MatIndexArray& mats = meshGeom->GetMaterialIndices();
+                            if (std::find(mats.begin(), mats.end(), matIndex) == mats.end()) {
+                                continue;
+                            }
+
+                            int index = -1;
+                            for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                                if (meshGeom->GetTextureCoords(i).empty()) {
+                                    break;
+                                }
+                                const std::string& name = meshGeom->GetTextureCoordChannelName(i);
+                                if (name == uvSet) {
+                                    index = static_cast<int>(i);
+                                    break;
+                                }
+                            }
+                            if (index == -1) {
+                                FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
+                                continue;
+                            }
+
+                            if (uvIndex == -1) {
+                                uvIndex = index;
+                            }
+                            else {
+                                FBXImporter::LogWarn("the UV channel named " + uvSet + " appears at different positions in meshes, results will be wrong");
+                            }
+                        }
+                    }
+                    else
+                    {
+                        int index = -1;
+                        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++i) {
+                            if (mesh->GetTextureCoords(i).empty()) {
+                                break;
+                            }
+                            const std::string& name = mesh->GetTextureCoordChannelName(i);
+                            if (name == uvSet) {
+                                index = static_cast<int>(i);
+                                break;
+                            }
+                        }
+                        if (index == -1) {
+                            FBXImporter::LogWarn("did not find UV channel named " + uvSet + " in a mesh using this material");
+                        }
+
+                        if (uvIndex == -1) {
+                            uvIndex = index;
+                        }
+                    }
+
+                    if (uvIndex == -1) {
+                        FBXImporter::LogWarn("failed to resolve UV channel " + uvSet + ", using first UV channel");
+                        uvIndex = 0;
+                    }
+                }
+            }
+
+            out_mat->AddProperty(&uvIndex, 1, (name + "|uvwsrc").c_str(), aiTextureType_UNKNOWN, 0);
+        }
+            }
+        }
+
+
+        double FBXConverter::FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal) {
+            switch (fp) {
+            case FileGlobalSettings::FrameRate_DEFAULT:
+                return 1.0;
+
+            case FileGlobalSettings::FrameRate_120:
+                return 120.0;
+
+            case FileGlobalSettings::FrameRate_100:
+                return 100.0;
+
+            case FileGlobalSettings::FrameRate_60:
+                return 60.0;
+
+            case FileGlobalSettings::FrameRate_50:
+                return 50.0;
+
+            case FileGlobalSettings::FrameRate_48:
+                return 48.0;
+
+            case FileGlobalSettings::FrameRate_30:
+            case FileGlobalSettings::FrameRate_30_DROP:
+                return 30.0;
+
+            case FileGlobalSettings::FrameRate_NTSC_DROP_FRAME:
+            case FileGlobalSettings::FrameRate_NTSC_FULL_FRAME:
+                return 29.9700262;
+
+            case FileGlobalSettings::FrameRate_PAL:
+                return 25.0;
+
+            case FileGlobalSettings::FrameRate_CINEMA:
+                return 24.0;
+
+            case FileGlobalSettings::FrameRate_1000:
+                return 1000.0;
+
+            case FileGlobalSettings::FrameRate_CINEMA_ND:
+                return 23.976;
+
+            case FileGlobalSettings::FrameRate_CUSTOM:
+                return customFPSVal;
+
+            case FileGlobalSettings::FrameRate_MAX: // this is to silence compiler warnings
+                break;
+            }
+
+            ai_assert(false);
+
+            return -1.0f;
+        }
+
+
+        void FBXConverter::ConvertAnimations()
+        {
+            // first of all determine framerate
+            const FileGlobalSettings::FrameRate fps = doc.GlobalSettings().TimeMode();
+            const float custom = doc.GlobalSettings().CustomFrameRate();
+            anim_fps = FrameRateToDouble(fps, custom);
+
+            const std::vector<const AnimationStack*>& animations = doc.AnimationStacks();
+            for (const AnimationStack* stack : animations) {
+                ConvertAnimationStack(*stack);
+            }
+        }
+
+        std::string FBXConverter::FixNodeName(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().
+            if (name.substr(0, 7) == "Model::") {
+                std::string temp = name.substr(7);
+                return temp;
+            }
+
+            return name;
+        }
+
+        std::string FBXConverter::FixAnimMeshName(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";
+        }
+
+        void FBXConverter::ConvertAnimationStack(const AnimationStack& st)
+        {
+            const AnimationLayerList& layers = st.Layers();
+            if (layers.empty()) {
+                return;
+            }
+
+            aiAnimation* const anim = new aiAnimation();
+            animations.push_back(anim);
+
+            // strip AnimationStack:: prefix
+            std::string name = st.Name();
+            if (name.substr(0, 16) == "AnimationStack::") {
+                name = name.substr(16);
+            }
+            else if (name.substr(0, 11) == "AnimStack::") {
+                name = name.substr(11);
+            }
+
+            anim->mName.Set(name);
+
+            // need to find all nodes for which we need to generate node animations -
+            // it may happen that we need to merge multiple layers, though.
+            NodeMap node_map;
+
+            // reverse mapping from curves to layers, much faster than querying
+            // the FBX DOM for it.
+            LayerMap layer_map;
+
+            const char* prop_whitelist[] = {
+                "Lcl Scaling",
+                "Lcl Rotation",
+                "Lcl Translation",
+                "DeformPercent"
+            };
+
+            std::map<std::string, morphAnimData*> morphAnimDatas;
+
+            for (const AnimationLayer* layer : layers) {
+                ai_assert(layer);
+                const AnimationCurveNodeList& nodes = layer->Nodes(prop_whitelist, 4);
+                for (const AnimationCurveNode* node : nodes) {
+                    ai_assert(node);
+                    const Model* const model = dynamic_cast<const Model*>(node->Target());
+                    if (model) {
+                        const std::string& name = FixNodeName(model->Name());
+                        node_map[name].push_back(node);
+                        layer_map[node] = layer;
+                        continue;
+                    }
+                    const BlendShapeChannel* const bsc = dynamic_cast<const BlendShapeChannel*>(node->Target());
+                    if (bsc) {
+                        ProcessMorphAnimDatas(&morphAnimDatas, bsc, node);
+                    }
+                }
+            }
+
+            // generate node animations
+            std::vector<aiNodeAnim*> node_anims;
+
+            double min_time = 1e10;
+            double max_time = -1e10;
+
+            int64_t start_time = st.LocalStart();
+            int64_t stop_time = st.LocalStop();
+            bool has_local_startstop = start_time != 0 || stop_time != 0;
+            if (!has_local_startstop) {
+                // no time range given, so accept every keyframe and use the actual min/max time
+                // the numbers are INT64_MIN/MAX, the 20000 is for safety because GenerateNodeAnimations uses an epsilon of 10000
+                start_time = -9223372036854775807ll + 20000;
+                stop_time = 9223372036854775807ll - 20000;
+            }
+
+            try {
+                for (const NodeMap::value_type& kv : node_map) {
+                    GenerateNodeAnimations(node_anims,
+                        kv.first,
+                        kv.second,
+                        layer_map,
+                        start_time, stop_time,
+                        max_time,
+                        min_time);
+                }
+            }
+            catch (std::exception&) {
+                std::for_each(node_anims.begin(), node_anims.end(), Util::delete_fun<aiNodeAnim>());
+                throw;
+            }
+
+            if (node_anims.size() || morphAnimDatas.size()) {
+                if (node_anims.size()) {
+                    anim->mChannels = new aiNodeAnim*[node_anims.size()]();
+                    anim->mNumChannels = static_cast<unsigned int>(node_anims.size());
+                    std::swap_ranges(node_anims.begin(), node_anims.end(), anim->mChannels);
+                }
+                if (morphAnimDatas.size()) {
+                    unsigned int numMorphMeshChannels = static_cast<unsigned int>(morphAnimDatas.size());
+                    anim->mMorphMeshChannels = new aiMeshMorphAnim*[numMorphMeshChannels];
+                    anim->mNumMorphMeshChannels = numMorphMeshChannels;
+                    unsigned int i = 0;
+                    for (auto morphAnimIt : morphAnimDatas) {
+                        morphAnimData* animData = morphAnimIt.second;
+                        unsigned int numKeys = static_cast<unsigned int>(animData->size());
+                        aiMeshMorphAnim* meshMorphAnim = new aiMeshMorphAnim();
+                        meshMorphAnim->mName.Set(morphAnimIt.first);
+                        meshMorphAnim->mNumKeys = numKeys;
+                        meshMorphAnim->mKeys = new aiMeshMorphKey[numKeys];
+                        unsigned int j = 0;
+                        for (auto animIt : *animData) {
+                            morphKeyData* keyData = animIt.second;
+                            unsigned int numValuesAndWeights = static_cast<unsigned int>(keyData->values.size());
+                            meshMorphAnim->mKeys[j].mNumValuesAndWeights = numValuesAndWeights;
+                            meshMorphAnim->mKeys[j].mValues = new unsigned int[numValuesAndWeights];
+                            meshMorphAnim->mKeys[j].mWeights = new double[numValuesAndWeights];
+                            meshMorphAnim->mKeys[j].mTime = CONVERT_FBX_TIME(animIt.first) * anim_fps;
+                            for (unsigned int k = 0; k < numValuesAndWeights; k++) {
+                                meshMorphAnim->mKeys[j].mValues[k] = keyData->values.at(k);
+                                meshMorphAnim->mKeys[j].mWeights[k] = keyData->weights.at(k);
+                            }
+                            j++;
+                        }
+                        anim->mMorphMeshChannels[i++] = meshMorphAnim;
+                    }
+                }
+            }
+            else {
+                // empty animations would fail validation, so drop them
+                delete anim;
+                animations.pop_back();
+                FBXImporter::LogInfo("ignoring empty AnimationStack (using IK?): " + name);
+                return;
+            }
+
+            double start_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(start_time) * anim_fps) : min_time;
+            double stop_time_fps = has_local_startstop ? (CONVERT_FBX_TIME(stop_time) * anim_fps) : max_time;
+
+            // adjust relative timing for animation
+            for (unsigned int c = 0; c < anim->mNumChannels; c++) {
+                aiNodeAnim* channel = anim->mChannels[c];
+                for (uint32_t i = 0; i < channel->mNumPositionKeys; i++) {
+                    channel->mPositionKeys[i].mTime -= start_time_fps;
+                }
+                for (uint32_t i = 0; i < channel->mNumRotationKeys; i++) {
+                    channel->mRotationKeys[i].mTime -= start_time_fps;
+                }
+                for (uint32_t i = 0; i < channel->mNumScalingKeys; i++) {
+                    channel->mScalingKeys[i].mTime -= start_time_fps;
+                }
+            }
+            for (unsigned int c = 0; c < anim->mNumMorphMeshChannels; c++) {
+                aiMeshMorphAnim* channel = anim->mMorphMeshChannels[c];
+                for (uint32_t i = 0; i < channel->mNumKeys; i++) {
+                    channel->mKeys[i].mTime -= start_time_fps;
+                }
+            }
+
+            // for some mysterious reason, mDuration is simply the maximum key -- the
+            // validator always assumes animations to start at zero.
+            anim->mDuration = stop_time_fps - start_time_fps;
+            anim->mTicksPerSecond = anim_fps;
+        }
+
+        // ------------------------------------------------------------------------------------------------
+        void FBXConverter::ProcessMorphAnimDatas(std::map<std::string, morphAnimData*>* morphAnimDatas, const BlendShapeChannel* bsc, const AnimationCurveNode* node) {
+            std::vector<const Connection*> bscConnections = doc.GetConnectionsBySourceSequenced(bsc->ID(), "Deformer");
+            for (const Connection* bscConnection : bscConnections) {
+                auto bs = dynamic_cast<const BlendShape*>(bscConnection->DestinationObject());
+                if (bs) {
+                    auto channelIt = std::find(bs->BlendShapeChannels().begin(), bs->BlendShapeChannels().end(), bsc);
+                    if (channelIt != bs->BlendShapeChannels().end()) {
+                        auto channelIndex = static_cast<unsigned int>(std::distance(bs->BlendShapeChannels().begin(), channelIt));
+                        std::vector<const Connection*> bsConnections = doc.GetConnectionsBySourceSequenced(bs->ID(), "Geometry");
+                        for (const Connection* bsConnection : bsConnections) {
+                            auto geo = dynamic_cast<const Geometry*>(bsConnection->DestinationObject());
+                            if (geo) {
+                                std::vector<const Connection*> geoConnections = doc.GetConnectionsBySourceSequenced(geo->ID(), "Model");
+                                for (const Connection* geoConnection : geoConnections) {
+                                    auto model = dynamic_cast<const Model*>(geoConnection->DestinationObject());
+                                    if (model) {
+                                        auto geoIt = std::find(model->GetGeometry().begin(), model->GetGeometry().end(), geo);
+                                        auto geoIndex = static_cast<unsigned int>(std::distance(model->GetGeometry().begin(), geoIt));
+                                        auto name = aiString(FixNodeName(model->Name() + "*"));
+                                        name.length = 1 + ASSIMP_itoa10(name.data + name.length, MAXLEN - 1, geoIndex);
+                                        morphAnimData* animData;
+                                        auto animIt = morphAnimDatas->find(name.C_Str());
+                                        if (animIt == morphAnimDatas->end()) {
+                                            animData = new morphAnimData();
+                                            morphAnimDatas->insert(std::make_pair(name.C_Str(), animData));
+                                        }
+                                        else {
+                                            animData = animIt->second;
+                                        }
+                                        for (std::pair<std::string, const AnimationCurve*> curvesIt : node->Curves()) {
+                                            if (curvesIt.first == "d|DeformPercent") {
+                                                const AnimationCurve* animationCurve = curvesIt.second;
+                                                const KeyTimeList& keys = animationCurve->GetKeys();
+                                                const KeyValueList& values = animationCurve->GetValues();
+                                                unsigned int k = 0;
+                                                for (auto key : keys) {
+                                                    morphKeyData* keyData;
+                                                    auto keyIt = animData->find(key);
+                                                    if (keyIt == animData->end()) {
+                                                        keyData = new morphKeyData();
+                                                        animData->insert(std::make_pair(key, keyData));
+                                                    }
+                                                    else {
+                                                        keyData = keyIt->second;
+                                                    }
+                                                    keyData->values.push_back(channelIndex);
+                                                    keyData->weights.push_back(values.at(k) / 100.0f);
+                                                    k++;
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+
+        // ------------------------------------------------------------------------------------------------
+#ifdef ASSIMP_BUILD_DEBUG
+        // ------------------------------------------------------------------------------------------------
+        // sanity check whether the input is ok
+        static void validateAnimCurveNodes(const std::vector<const AnimationCurveNode*>& curves,
+            bool strictMode) {
+            const Object* target(NULL);
+            for (const AnimationCurveNode* node : curves) {
+                if (!target) {
+                    target = node->Target();
+                }
+                if (node->Target() != target) {
+                    FBXImporter::LogWarn("Node target is nullptr type.");
+                }
+                if (strictMode) {
+                    ai_assert(node->Target() == target);
+                }
+            }
+        }
+#endif // ASSIMP_BUILD_DEBUG
+
+        // ------------------------------------------------------------------------------------------------
+        void FBXConverter::GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims,
+            const std::string& fixed_name,
+            const std::vector<const AnimationCurveNode*>& curves,
+            const LayerMap& layer_map,
+            int64_t start, int64_t stop,
+            double& max_time,
+            double& min_time)
+        {
+
+            NodeMap node_property_map;
+            ai_assert(curves.size());
+
+#ifdef ASSIMP_BUILD_DEBUG
+            validateAnimCurveNodes(curves, doc.Settings().strictMode);
+#endif
+            const AnimationCurveNode* curve_node = NULL;
+            for (const AnimationCurveNode* node : curves) {
+                ai_assert(node);
+
+                if (node->TargetProperty().empty()) {
+                    FBXImporter::LogWarn("target property for animation curve not set: " + node->Name());
+                    continue;
+                }
+
+                curve_node = node;
+                if (node->Curves().empty()) {
+                    FBXImporter::LogWarn("no animation curves assigned to AnimationCurveNode: " + node->Name());
+                    continue;
+                }
+
+                node_property_map[node->TargetProperty()].push_back(node);
+            }
+
+            ai_assert(curve_node);
+            ai_assert(curve_node->TargetAsModel());
+
+            const Model& target = *curve_node->TargetAsModel();
+
+            // check for all possible transformation components
+            NodeMap::const_iterator chain[TransformationComp_MAXIMUM];
+
+            bool has_any = false;
+            bool has_complex = false;
+
+            for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i) {
+                const TransformationComp comp = static_cast<TransformationComp>(i);
+
+                // inverse pivots don't exist in the input, we just generate them
+                if (comp == TransformationComp_RotationPivotInverse || comp == TransformationComp_ScalingPivotInverse) {
+                    chain[i] = node_property_map.end();
+                    continue;
+                }
+
+                chain[i] = node_property_map.find(NameTransformationCompProperty(comp));
+                if (chain[i] != node_property_map.end()) {
+
+                    // check if this curves contains redundant information by looking
+                    // up the corresponding node's transformation chain.
+                    if (doc.Settings().optimizeEmptyAnimationCurves &&
+                        IsRedundantAnimationData(target, comp, (*chain[i]).second)) {
+
+                        FBXImporter::LogDebug("dropping redundant animation channel for node " + target.Name());
+                        continue;
+                    }
+
+                    has_any = true;
+
+                    if (comp != TransformationComp_Rotation && comp != TransformationComp_Scaling && comp != TransformationComp_Translation)
+                    {
+                        has_complex = true;
+                    }
+                }
+            }
+
+            if (!has_any) {
+                FBXImporter::LogWarn("ignoring node animation, did not find any transformation key frames");
+                return;
+            }
+
+            // this needs to play nicely with GenerateTransformationNodeChain() which will
+            // be invoked _later_ (animations come first). If this node has only rotation,
+            // scaling and translation _and_ there are no animated other components either,
+            // we can use a single node and also a single node animation channel.
+            if (!has_complex && !NeedsComplexTransformationChain(target)) {
+
+                aiNodeAnim* const nd = GenerateSimpleNodeAnim(fixed_name, target, chain,
+                    node_property_map.end(),
+                    layer_map,
+                    start, stop,
+                    max_time,
+                    min_time,
+                    true // input is TRS order, assimp is SRT
+                );
+
+                ai_assert(nd);
+                if (nd->mNumPositionKeys == 0 && nd->mNumRotationKeys == 0 && nd->mNumScalingKeys == 0) {
+                    delete nd;
+                }
+                else {
+                    node_anims.push_back(nd);
+                }
+                return;
+            }
+
+            // otherwise, things get gruesome and we need separate animation channels
+            // for each part of the transformation chain. Remember which channels
+            // we generated and pass this information to the node conversion
+            // code to avoid nodes that have identity transform, but non-identity
+            // animations, being dropped.
+            unsigned int flags = 0, bit = 0x1;
+            for (size_t i = 0; i < TransformationComp_MAXIMUM; ++i, bit <<= 1) {
+                const TransformationComp comp = static_cast<TransformationComp>(i);
+
+                if (chain[i] != node_property_map.end()) {
+                    flags |= bit;
+
+                    ai_assert(comp != TransformationComp_RotationPivotInverse);
+                    ai_assert(comp != TransformationComp_ScalingPivotInverse);
+
+                    const std::string& chain_name = NameTransformationChainNode(fixed_name, comp);
+
+                    aiNodeAnim* na = nullptr;
+                    switch (comp)
+                    {
+                    case TransformationComp_Rotation:
+                    case TransformationComp_PreRotation:
+                    case TransformationComp_PostRotation:
+                    case TransformationComp_GeometricRotation:
+                        na = GenerateRotationNodeAnim(chain_name,
+                            target,
+                            (*chain[i]).second,
+                            layer_map,
+                            start, stop,
+                            max_time,
+                            min_time);
+
+                        break;
+
+                    case TransformationComp_RotationOffset:
+                    case TransformationComp_RotationPivot:
+                    case TransformationComp_ScalingOffset:
+                    case TransformationComp_ScalingPivot:
+                    case TransformationComp_Translation:
+                    case TransformationComp_GeometricTranslation:
+                        na = GenerateTranslationNodeAnim(chain_name,
+                            target,
+                            (*chain[i]).second,
+                            layer_map,
+                            start, stop,
+                            max_time,
+                            min_time);
+
+                        // pivoting requires us to generate an implicit inverse channel to undo the pivot translation
+                        if (comp == TransformationComp_RotationPivot) {
+                            const std::string& invName = NameTransformationChainNode(fixed_name,
+                                TransformationComp_RotationPivotInverse);
+
+                            aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName,
+                                target,
+                                (*chain[i]).second,
+                                layer_map,
+                                start, stop,
+                                max_time,
+                                min_time,
+                                true);
+
+                            ai_assert(inv);
+                            if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) {
+                                delete inv;
+                            }
+                            else {
+                                node_anims.push_back(inv);
+                            }
+
+                            ai_assert(TransformationComp_RotationPivotInverse > i);
+                            flags |= bit << (TransformationComp_RotationPivotInverse - i);
+                        }
+                        else if (comp == TransformationComp_ScalingPivot) {
+                            const std::string& invName = NameTransformationChainNode(fixed_name,
+                                TransformationComp_ScalingPivotInverse);
+
+                            aiNodeAnim* const inv = GenerateTranslationNodeAnim(invName,
+                                target,
+                                (*chain[i]).second,
+                                layer_map,
+                                start, stop,
+                                max_time,
+                                min_time,
+                                true);
+
+                            ai_assert(inv);
+                            if (inv->mNumPositionKeys == 0 && inv->mNumRotationKeys == 0 && inv->mNumScalingKeys == 0) {
+                                delete inv;
+                            }
+                            else {
+                                node_anims.push_back(inv);
+                            }
+
+                            ai_assert(TransformationComp_RotationPivotInverse > i);
+                            flags |= bit << (TransformationComp_RotationPivotInverse - i);
+                        }
+
+                        break;
+
+                    case TransformationComp_Scaling:
+                    case TransformationComp_GeometricScaling:
+                        na = GenerateScalingNodeAnim(chain_name,
+                            target,
+                            (*chain[i]).second,
+                            layer_map,
+                            start, stop,
+                            max_time,
+                            min_time);
+
+                        break;
+
+                    default:
+                        ai_assert(false);
+                    }
+
+                    ai_assert(na);
+                    if (na->mNumPositionKeys == 0 && na->mNumRotationKeys == 0 && na->mNumScalingKeys == 0) {
+                        delete na;
+                    }
+                    else {
+                        node_anims.push_back(na);
+                    }
+                    continue;
+                }
+            }
+
+            node_anim_chain_bits[fixed_name] = flags;
+        }
+
+
+        bool FBXConverter::IsRedundantAnimationData(const Model& target,
+            TransformationComp comp,
+            const std::vector<const AnimationCurveNode*>& curves) {
+            ai_assert(curves.size());
+
+            // look for animation nodes with
+            //  * sub channels for all relevant components set
+            //  * one key/value pair per component
+            //  * combined values match up the corresponding value in the bind pose node transformation
+            // only such nodes are 'redundant' for this function.
+
+            if (curves.size() > 1) {
+                return false;
+            }
+
+            const AnimationCurveNode& nd = *curves.front();
+            const AnimationCurveMap& sub_curves = nd.Curves();
+
+            const AnimationCurveMap::const_iterator dx = sub_curves.find("d|X");
+            const AnimationCurveMap::const_iterator dy = sub_curves.find("d|Y");
+            const AnimationCurveMap::const_iterator dz = sub_curves.find("d|Z");
+
+            if (dx == sub_curves.end() || dy == sub_curves.end() || dz == sub_curves.end()) {
+                return false;
+            }
+
+            const KeyValueList& vx = (*dx).second->GetValues();
+            const KeyValueList& vy = (*dy).second->GetValues();
+            const KeyValueList& vz = (*dz).second->GetValues();
+
+            if (vx.size() != 1 || vy.size() != 1 || vz.size() != 1) {
+                return false;
+            }
+
+            const aiVector3D dyn_val = aiVector3D(vx[0], vy[0], vz[0]);
+            const aiVector3D& static_val = PropertyGet<aiVector3D>(target.Props(),
+                NameTransformationCompProperty(comp),
+                TransformationCompDefaultValue(comp)
+                );
+
+            const float epsilon = 1e-6f;
+            return (dyn_val - static_val).SquareLength() < epsilon;
+        }
+
+
+        aiNodeAnim* FBXConverter::GenerateRotationNodeAnim(const std::string& name,
+            const Model& target,
+            const std::vector<const AnimationCurveNode*>& curves,
+            const LayerMap& layer_map,
+            int64_t start, int64_t stop,
+            double& max_time,
+            double& min_time)
+        {
+            std::unique_ptr<aiNodeAnim> na(new aiNodeAnim());
+            na->mNodeName.Set(name);
+
+            ConvertRotationKeys(na.get(), curves, layer_map, start, stop, max_time, min_time, target.RotationOrder());
+
+            // dummy scaling key
+            na->mScalingKeys = new aiVectorKey[1];
+            na->mNumScalingKeys = 1;
+
+            na->mScalingKeys[0].mTime = 0.;
+            na->mScalingKeys[0].mValue = aiVector3D(1.0f, 1.0f, 1.0f);
+
+            // dummy position key
+            na->mPositionKeys = new aiVectorKey[1];
+            na->mNumPositionKeys = 1;
+
+            na->mPositionKeys[0].mTime = 0.;
+            na->mPositionKeys[0].mValue = aiVector3D();
+
+            return na.release();
+        }
+
+        aiNodeAnim* FBXConverter::GenerateScalingNodeAnim(const std::string& name,
+            const Model& /*target*/,
+            const std::vector<const AnimationCurveNode*>& curves,
+            const LayerMap& layer_map,
+            int64_t start, int64_t stop,
+            double& max_time,
+            double& min_time)
+        {
+            std::unique_ptr<aiNodeAnim> na(new aiNodeAnim());
+            na->mNodeName.Set(name);
+
+            ConvertScaleKeys(na.get(), curves, layer_map, start, stop, max_time, min_time);
+
+            // dummy rotation key
+            na->mRotationKeys = new aiQuatKey[1];
+            na->mNumRotationKeys = 1;
+
+            na->mRotationKeys[0].mTime = 0.;
+            na->mRotationKeys[0].mValue = aiQuaternion();
+
+            // dummy position key
+            na->mPositionKeys = new aiVectorKey[1];
+            na->mNumPositionKeys = 1;
+
+            na->mPositionKeys[0].mTime = 0.;
+            na->mPositionKeys[0].mValue = aiVector3D();
+
+            return na.release();
+        }
+
+        aiNodeAnim* FBXConverter::GenerateTranslationNodeAnim(const std::string& name,
+            const Model& /*target*/,
+            const std::vector<const AnimationCurveNode*>& curves,
+            const LayerMap& layer_map,
+            int64_t start, int64_t stop,
+            double& max_time,
+            double& min_time,
+            bool inverse) {
+            std::unique_ptr<aiNodeAnim> na(new aiNodeAnim());
+            na->mNodeName.Set(name);
+
+            ConvertTranslationKeys(na.get(), curves, layer_map, start, stop, max_time, min_time);
+
+            if (inverse) {
+                for (unsigned int i = 0; i < na->mNumPositionKeys; ++i) {
+                    na->mPositionKeys[i].mValue *= -1.0f;
+                }
+            }
+
+            // dummy scaling key
+            na->mScalingKeys = new aiVectorKey[1];
+            na->mNumScalingKeys = 1;
+
+            na->mScalingKeys[0].mTime = 0.;
+            na->mScalingKeys[0].mValue = aiVector3D(1.0f, 1.0f, 1.0f);
+
+            // dummy rotation key
+            na->mRotationKeys = new aiQuatKey[1];
+            na->mNumRotationKeys = 1;
+
+            na->mRotationKeys[0].mTime = 0.;
+            na->mRotationKeys[0].mValue = aiQuaternion();
+
+            return na.release();
+        }
+
+        aiNodeAnim* FBXConverter::GenerateSimpleNodeAnim(const std::string& name,
+            const Model& target,
+            NodeMap::const_iterator chain[TransformationComp_MAXIMUM],
+            NodeMap::const_iterator iter_end,
+            const LayerMap& layer_map,
+            int64_t start, int64_t stop,
+            double& max_time,
+            double& min_time,
+            bool reverse_order)
+
+        {
+            std::unique_ptr<aiNodeAnim> na(new aiNodeAnim());
+            na->mNodeName.Set(name);
+
+            const PropertyTable& props = target.Props();
+
+            // need to convert from TRS order to SRT?
+            if (reverse_order) {
+
+                aiVector3D def_scale = PropertyGet(props, "Lcl Scaling", aiVector3D(1.f, 1.f, 1.f));
+                aiVector3D def_translate = PropertyGet(props, "Lcl Translation", aiVector3D(0.f, 0.f, 0.f));
+                aiVector3D def_rot = PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f));
+
+                KeyFrameListList scaling;
+                KeyFrameListList translation;
+                KeyFrameListList rotation;
+
+                if (chain[TransformationComp_Scaling] != iter_end) {
+                    scaling = GetKeyframeList((*chain[TransformationComp_Scaling]).second, start, stop);
+                }
+
+                if (chain[TransformationComp_Translation] != iter_end) {
+                    translation = GetKeyframeList((*chain[TransformationComp_Translation]).second, start, stop);
+                }
+
+                if (chain[TransformationComp_Rotation] != iter_end) {
+                    rotation = GetKeyframeList((*chain[TransformationComp_Rotation]).second, start, stop);
+                }
+
+                KeyFrameListList joined;
+                joined.insert(joined.end(), scaling.begin(), scaling.end());
+                joined.insert(joined.end(), translation.begin(), translation.end());
+                joined.insert(joined.end(), rotation.begin(), rotation.end());
+
+                const KeyTimeList& times = GetKeyTimeList(joined);
+
+                aiQuatKey* out_quat = new aiQuatKey[times.size()];
+                aiVectorKey* out_scale = new aiVectorKey[times.size()];
+                aiVectorKey* out_translation = new aiVectorKey[times.size()];
+
+                if (times.size())
+                {
+                    ConvertTransformOrder_TRStoSRT(out_quat, out_scale, out_translation,
+                        scaling,
+                        translation,
+                        rotation,
+                        times,
+                        max_time,
+                        min_time,
+                        target.RotationOrder(),
+                        def_scale,
+                        def_translate,
+                        def_rot);
+                }
+
+                // XXX remove duplicates / redundant keys which this operation did
+                // likely produce if not all three channels were equally dense.
+
+                na->mNumScalingKeys = static_cast<unsigned int>(times.size());
+                na->mNumRotationKeys = na->mNumScalingKeys;
+                na->mNumPositionKeys = na->mNumScalingKeys;
+
+                na->mScalingKeys = out_scale;
+                na->mRotationKeys = out_quat;
+                na->mPositionKeys = out_translation;
+            }
+            else {
+
+                // if a particular transformation is not given, grab it from
+                // the corresponding node to meet the semantics of aiNodeAnim,
+                // which requires all of rotation, scaling and translation
+                // to be set.
+                if (chain[TransformationComp_Scaling] != iter_end) {
+                    ConvertScaleKeys(na.get(), (*chain[TransformationComp_Scaling]).second,
+                        layer_map,
+                        start, stop,
+                        max_time,
+                        min_time);
+                }
+                else {
+                    na->mScalingKeys = new aiVectorKey[1];
+                    na->mNumScalingKeys = 1;
+
+                    na->mScalingKeys[0].mTime = 0.;
+                    na->mScalingKeys[0].mValue = PropertyGet(props, "Lcl Scaling",
+                        aiVector3D(1.f, 1.f, 1.f));
+                }
+
+                if (chain[TransformationComp_Rotation] != iter_end) {
+                    ConvertRotationKeys(na.get(), (*chain[TransformationComp_Rotation]).second,
+                        layer_map,
+                        start, stop,
+                        max_time,
+                        min_time,
+                        target.RotationOrder());
+                }
+                else {
+                    na->mRotationKeys = new aiQuatKey[1];
+                    na->mNumRotationKeys = 1;
+
+                    na->mRotationKeys[0].mTime = 0.;
+                    na->mRotationKeys[0].mValue = EulerToQuaternion(
+                        PropertyGet(props, "Lcl Rotation", aiVector3D(0.f, 0.f, 0.f)),
+                        target.RotationOrder());
+                }
+
+                if (chain[TransformationComp_Translation] != iter_end) {
+                    ConvertTranslationKeys(na.get(), (*chain[TransformationComp_Translation]).second,
+                        layer_map,
+                        start, stop,
+                        max_time,
+                        min_time);
+                }
+                else {
+                    na->mPositionKeys = new aiVectorKey[1];
+                    na->mNumPositionKeys = 1;
+
+                    na->mPositionKeys[0].mTime = 0.;
+                    na->mPositionKeys[0].mValue = PropertyGet(props, "Lcl Translation",
+                        aiVector3D(0.f, 0.f, 0.f));
+                }
+
+            }
+            return na.release();
+        }
+
+        FBXConverter::KeyFrameListList FBXConverter::GetKeyframeList(const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop)
+        {
+            KeyFrameListList inputs;
+            inputs.reserve(nodes.size() * 3);
+
+            //give some breathing room for rounding errors
+            int64_t adj_start = start - 10000;
+            int64_t adj_stop = stop + 10000;
+
+            for (const AnimationCurveNode* node : nodes) {
+                ai_assert(node);
+
+                const AnimationCurveMap& curves = node->Curves();
+                for (const AnimationCurveMap::value_type& kv : curves) {
+
+                    unsigned int mapto;
+                    if (kv.first == "d|X") {
+                        mapto = 0;
+                    }
+                    else if (kv.first == "d|Y") {
+                        mapto = 1;
+                    }
+                    else if (kv.first == "d|Z") {
+                        mapto = 2;
+                    }
+                    else {
+                        FBXImporter::LogWarn("ignoring scale animation curve, did not recognize target component");
+                        continue;
+                    }
+
+                    const AnimationCurve* const curve = kv.second;
+                    ai_assert(curve->GetKeys().size() == curve->GetValues().size() && curve->GetKeys().size());
+
+                    //get values within the start/stop time window
+                    std::shared_ptr<KeyTimeList> Keys(new KeyTimeList());
+                    std::shared_ptr<KeyValueList> Values(new KeyValueList());
+                    const size_t count = curve->GetKeys().size();
+                    Keys->reserve(count);
+                    Values->reserve(count);
+                    for (size_t n = 0; n < count; n++)
+                    {
+                        int64_t k = curve->GetKeys().at(n);
+                        if (k >= adj_start && k <= adj_stop)
+                        {
+                            Keys->push_back(k);
+                            Values->push_back(curve->GetValues().at(n));
+                        }
+                    }
+
+                    inputs.push_back(std::make_tuple(Keys, Values, mapto));
+                }
+            }
+            return inputs; // pray for NRVO :-)
+        }
+
+
+        KeyTimeList FBXConverter::GetKeyTimeList(const KeyFrameListList& inputs) {
+            ai_assert(!inputs.empty());
+
+            // reserve some space upfront - it is likely that the key-frame lists
+            // have matching time values, so max(of all key-frame lists) should
+            // be a good estimate.
+            KeyTimeList keys;
+
+            size_t estimate = 0;
+            for (const KeyFrameList& kfl : inputs) {
+                estimate = std::max(estimate, std::get<0>(kfl)->size());
+            }
+
+            keys.reserve(estimate);
+
+            std::vector<unsigned int> next_pos;
+            next_pos.resize(inputs.size(), 0);
+
+            const size_t count = inputs.size();
+            while (true) {
+
+                int64_t min_tick = std::numeric_limits<int64_t>::max();
+                for (size_t i = 0; i < count; ++i) {
+                    const KeyFrameList& kfl = inputs[i];
+
+                    if (std::get<0>(kfl)->size() > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) < min_tick) {
+                        min_tick = std::get<0>(kfl)->at(next_pos[i]);
+                    }
+                }
+
+                if (min_tick == std::numeric_limits<int64_t>::max()) {
+                    break;
+                }
+                keys.push_back(min_tick);
+
+                for (size_t i = 0; i < count; ++i) {
+                    const KeyFrameList& kfl = inputs[i];
+
+
+                    while (std::get<0>(kfl)->size() > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) == min_tick) {
+                        ++next_pos[i];
+                    }
+                }
+            }
+
+            return keys;
+        }
+
+        void FBXConverter::InterpolateKeys(aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+            const aiVector3D& def_value,
+            double& max_time,
+            double& min_time) {
+            ai_assert(!keys.empty());
+            ai_assert(nullptr != valOut);
+
+            std::vector<unsigned int> next_pos;
+            const size_t count(inputs.size());
+
+            next_pos.resize(inputs.size(), 0);
+
+            for (KeyTimeList::value_type time : keys) {
+                ai_real result[3] = { def_value.x, def_value.y, def_value.z };
+
+                for (size_t i = 0; i < count; ++i) {
+                    const KeyFrameList& kfl = inputs[i];
+
+                    const size_t ksize = std::get<0>(kfl)->size();
+                    if (ksize == 0) {
+                        continue;
+                    }
+                    if (ksize > next_pos[i] && std::get<0>(kfl)->at(next_pos[i]) == time) {
+                        ++next_pos[i];
+                    }
+
+                    const size_t id0 = next_pos[i] > 0 ? next_pos[i] - 1 : 0;
+                    const size_t id1 = next_pos[i] == ksize ? ksize - 1 : next_pos[i];
+
+                    // use lerp for interpolation
+                    const KeyValueList::value_type valueA = std::get<1>(kfl)->at(id0);
+                    const KeyValueList::value_type valueB = std::get<1>(kfl)->at(id1);
+
+                    const KeyTimeList::value_type timeA = std::get<0>(kfl)->at(id0);
+                    const KeyTimeList::value_type timeB = std::get<0>(kfl)->at(id1);
+
+                    const ai_real factor = timeB == timeA ? ai_real(0.) : static_cast<ai_real>((time - timeA)) / (timeB - timeA);
+                    const ai_real interpValue = static_cast<ai_real>(valueA + (valueB - valueA) * factor);
+
+                    result[std::get<2>(kfl)] = interpValue;
+                }
+
+                // magic value to convert fbx times to seconds
+                valOut->mTime = CONVERT_FBX_TIME(time) * anim_fps;
+
+                min_time = std::min(min_time, valOut->mTime);
+                max_time = std::max(max_time, valOut->mTime);
+
+                valOut->mValue.x = result[0];
+                valOut->mValue.y = result[1];
+                valOut->mValue.z = result[2];
+
+                ++valOut;
+            }
+        }
+
+        void FBXConverter::InterpolateKeys(aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+            const aiVector3D& def_value,
+            double& maxTime,
+            double& minTime,
+            Model::RotOrder order)
+        {
+            ai_assert(!keys.empty());
+            ai_assert(nullptr != valOut);
+
+            std::unique_ptr<aiVectorKey[]> temp(new aiVectorKey[keys.size()]);
+            InterpolateKeys(temp.get(), keys, inputs, def_value, maxTime, minTime);
+
+            aiMatrix4x4 m;
+
+            aiQuaternion lastq;
+
+            for (size_t i = 0, c = keys.size(); i < c; ++i) {
+
+                valOut[i].mTime = temp[i].mTime;
+
+                GetRotationMatrix(order, temp[i].mValue, m);
+                aiQuaternion quat = aiQuaternion(aiMatrix3x3(m));
+
+                // take shortest path by checking the inner product
+                // http://www.3dkingdoms.com/weekly/weekly.php?a=36
+                if (quat.x * lastq.x + quat.y * lastq.y + quat.z * lastq.z + quat.w * lastq.w < 0)
+                {
+                    quat.x = -quat.x;
+                    quat.y = -quat.y;
+                    quat.z = -quat.z;
+                    quat.w = -quat.w;
+                }
+                lastq = quat;
+
+                valOut[i].mValue = quat;
+            }
+        }
+
+        void FBXConverter::ConvertTransformOrder_TRStoSRT(aiQuatKey* out_quat, aiVectorKey* out_scale,
+            aiVectorKey* out_translation,
+            const KeyFrameListList& scaling,
+            const KeyFrameListList& translation,
+            const KeyFrameListList& rotation,
+            const KeyTimeList& times,
+            double& maxTime,
+            double& minTime,
+            Model::RotOrder order,
+            const aiVector3D& def_scale,
+            const aiVector3D& def_translate,
+            const aiVector3D& def_rotation)
+        {
+            if (rotation.size()) {
+                InterpolateKeys(out_quat, times, rotation, def_rotation, maxTime, minTime, order);
+            }
+            else {
+                for (size_t i = 0; i < times.size(); ++i) {
+                    out_quat[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
+                    out_quat[i].mValue = EulerToQuaternion(def_rotation, order);
+                }
+            }
+
+            if (scaling.size()) {
+                InterpolateKeys(out_scale, times, scaling, def_scale, maxTime, minTime);
+            }
+            else {
+                for (size_t i = 0; i < times.size(); ++i) {
+                    out_scale[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
+                    out_scale[i].mValue = def_scale;
+                }
+            }
+
+            if (translation.size()) {
+                InterpolateKeys(out_translation, times, translation, def_translate, maxTime, minTime);
+            }
+            else {
+                for (size_t i = 0; i < times.size(); ++i) {
+                    out_translation[i].mTime = CONVERT_FBX_TIME(times[i]) * anim_fps;
+                    out_translation[i].mValue = def_translate;
+                }
+            }
+
+            const size_t count = times.size();
+            for (size_t i = 0; i < count; ++i) {
+                aiQuaternion& r = out_quat[i].mValue;
+                aiVector3D& s = out_scale[i].mValue;
+                aiVector3D& t = out_translation[i].mValue;
+
+                aiMatrix4x4 mat, temp;
+                aiMatrix4x4::Translation(t, mat);
+                mat *= aiMatrix4x4(r.GetMatrix());
+                mat *= aiMatrix4x4::Scaling(s, temp);
+
+                mat.Decompose(s, r, t);
+            }
+        }
+
+        aiQuaternion FBXConverter::EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order)
+        {
+            aiMatrix4x4 m;
+            GetRotationMatrix(order, rot, m);
+
+            return aiQuaternion(aiMatrix3x3(m));
+        }
+
+        void FBXConverter::ConvertScaleKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& /*layers*/,
+            int64_t start, int64_t stop,
+            double& maxTime,
+            double& minTime)
+        {
+            ai_assert(nodes.size());
+
+            // XXX for now, assume scale should be blended geometrically (i.e. two
+            // layers should be multiplied with each other). There is a FBX
+            // property in the layer to specify the behaviour, though.
+
+            const KeyFrameListList& inputs = GetKeyframeList(nodes, start, stop);
+            const KeyTimeList& keys = GetKeyTimeList(inputs);
+
+            na->mNumScalingKeys = static_cast<unsigned int>(keys.size());
+            na->mScalingKeys = new aiVectorKey[keys.size()];
+            if (keys.size() > 0)
+                InterpolateKeys(na->mScalingKeys, keys, inputs, aiVector3D(1.0f, 1.0f, 1.0f), maxTime, minTime);
+        }
+
+        void FBXConverter::ConvertTranslationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
+            const LayerMap& /*layers*/,
+            int64_t start, int64_t stop,
+            double& maxTime,
+            double& minTime)
+        {
+            ai_assert(nodes.size());
+
+            // XXX see notes in ConvertScaleKeys()
+            const KeyFrameListList& inputs = GetKeyframeList(nodes, start, stop);
+            const KeyTimeList& keys = GetKeyTimeList(inputs);
+
+            na->mNumPositionKeys = static_cast<unsigned int>(keys.size());
+            na->mPositionKeys = new aiVectorKey[keys.size()];
+            if (keys.size() > 0)
+                InterpolateKeys(na->mPositionKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime);
+        }
+
+        void FBXConverter::ConvertRotationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
+            const LayerMap& /*layers*/,
+            int64_t start, int64_t stop,
+            double& maxTime,
+            double& minTime,
+            Model::RotOrder order)
+        {
+            ai_assert(nodes.size());
+
+            // XXX see notes in ConvertScaleKeys()
+            const std::vector< KeyFrameList >& inputs = GetKeyframeList(nodes, start, stop);
+            const KeyTimeList& keys = GetKeyTimeList(inputs);
+
+            na->mNumRotationKeys = static_cast<unsigned int>(keys.size());
+            na->mRotationKeys = new aiQuatKey[keys.size()];
+            if (!keys.empty()) {
+                InterpolateKeys(na->mRotationKeys, keys, inputs, aiVector3D(0.0f, 0.0f, 0.0f), maxTime, minTime, order);
+            }
+        }
+
+        void FBXConverter::ConvertGlobalSettings() {
+            if (nullptr == out) {
+                return;
+            }
+
+            out->mMetaData = aiMetadata::Alloc(15);
+            out->mMetaData->Set(0, "UpAxis", doc.GlobalSettings().UpAxis());
+            out->mMetaData->Set(1, "UpAxisSign", doc.GlobalSettings().UpAxisSign());
+            out->mMetaData->Set(2, "FrontAxis", doc.GlobalSettings().FrontAxis());
+            out->mMetaData->Set(3, "FrontAxisSign", doc.GlobalSettings().FrontAxisSign());
+            out->mMetaData->Set(4, "CoordAxis", doc.GlobalSettings().CoordAxis());
+            out->mMetaData->Set(5, "CoordAxisSign", doc.GlobalSettings().CoordAxisSign());
+            out->mMetaData->Set(6, "OriginalUpAxis", doc.GlobalSettings().OriginalUpAxis());
+            out->mMetaData->Set(7, "OriginalUpAxisSign", doc.GlobalSettings().OriginalUpAxisSign());
+            out->mMetaData->Set(8, "UnitScaleFactor", (double)doc.GlobalSettings().UnitScaleFactor());
+            out->mMetaData->Set(9, "OriginalUnitScaleFactor", doc.GlobalSettings().OriginalUnitScaleFactor());
+            out->mMetaData->Set(10, "AmbientColor", doc.GlobalSettings().AmbientColor());
+            out->mMetaData->Set(11, "FrameRate", (int)doc.GlobalSettings().TimeMode());
+            out->mMetaData->Set(12, "TimeSpanStart", doc.GlobalSettings().TimeSpanStart());
+            out->mMetaData->Set(13, "TimeSpanStop", doc.GlobalSettings().TimeSpanStop());
+            out->mMetaData->Set(14, "CustomFrameRate", doc.GlobalSettings().CustomFrameRate());
+        }
+
+        void FBXConverter::TransferDataToScene()
+        {
+            ai_assert(!out->mMeshes);
+            ai_assert(!out->mNumMeshes);
+
+            // note: the trailing () ensures initialization with NULL - not
+            // many C++ users seem to know this, so pointing it out to avoid
+            // confusion why this code works.
+
+            if (meshes.size()) {
+                out->mMeshes = new aiMesh*[meshes.size()]();
+                out->mNumMeshes = static_cast<unsigned int>(meshes.size());
+
+                std::swap_ranges(meshes.begin(), meshes.end(), out->mMeshes);
+            }
+
+            if (materials.size()) {
+                out->mMaterials = new aiMaterial*[materials.size()]();
+                out->mNumMaterials = static_cast<unsigned int>(materials.size());
+
+                std::swap_ranges(materials.begin(), materials.end(), out->mMaterials);
+            }
+
+            if (animations.size()) {
+                out->mAnimations = new aiAnimation*[animations.size()]();
+                out->mNumAnimations = static_cast<unsigned int>(animations.size());
+
+                std::swap_ranges(animations.begin(), animations.end(), out->mAnimations);
+            }
+
+            if (lights.size()) {
+                out->mLights = new aiLight*[lights.size()]();
+                out->mNumLights = static_cast<unsigned int>(lights.size());
+
+                std::swap_ranges(lights.begin(), lights.end(), out->mLights);
+            }
+
+            if (cameras.size()) {
+                out->mCameras = new aiCamera*[cameras.size()]();
+                out->mNumCameras = static_cast<unsigned int>(cameras.size());
+
+                std::swap_ranges(cameras.begin(), cameras.end(), out->mCameras);
+            }
+
+            if (textures.size()) {
+                out->mTextures = new aiTexture*[textures.size()]();
+                out->mNumTextures = static_cast<unsigned int>(textures.size());
+
+                std::swap_ranges(textures.begin(), textures.end(), out->mTextures);
+            }
+        }
+
+        // ------------------------------------------------------------------------------------------------
+        void ConvertToAssimpScene(aiScene* out, const Document& doc)
+        {
+            FBXConverter converter(out, doc);
+        }
+
+    } // !FBX
+} // !Assimp
+
+#endif

+ 457 - 0
thirdparty/assimp/code/FBXConverter.h

@@ -0,0 +1,457 @@
+/*
+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  FBXDConverter.h
+ *  @brief FBX DOM to aiScene conversion
+ */
+#ifndef INCLUDED_AI_FBX_CONVERTER_H
+#define INCLUDED_AI_FBX_CONVERTER_H
+
+#include "FBXParser.h"
+#include "FBXMeshGeometry.h"
+#include "FBXDocument.h"
+#include "FBXUtil.h"
+#include "FBXProperties.h"
+#include "FBXImporter.h"
+#include <assimp/anim.h>
+#include <assimp/material.h>
+#include <assimp/light.h>
+#include <assimp/texture.h>
+#include <assimp/camera.h>
+#include <assimp/StringComparison.h>
+
+struct aiScene;
+struct aiNode;
+struct aiMaterial;
+
+struct morphKeyData {
+    std::vector<unsigned int> values;
+    std::vector<float> weights;
+};
+typedef std::map<int64_t, morphKeyData*> morphAnimData;
+
+namespace Assimp {
+namespace FBX {
+
+class Document;
+
+using NodeNameCache = std::set<std::string>;
+
+/** 
+ *  Convert a FBX #Document to #aiScene
+ *  @param out Empty scene to be populated
+ *  @param doc Parsed FBX document 
+ */
+void ConvertToAssimpScene(aiScene* out, const Document& doc);
+
+/** Dummy class to encapsulate the conversion process */
+class FBXConverter {
+public:
+    /**
+    *  The different parts that make up the final local transformation of a fbx-node
+    */
+    enum TransformationComp {
+        TransformationComp_GeometricScalingInverse = 0,
+        TransformationComp_GeometricRotationInverse,
+        TransformationComp_GeometricTranslationInverse,
+        TransformationComp_Translation,
+        TransformationComp_RotationOffset,
+        TransformationComp_RotationPivot,
+        TransformationComp_PreRotation,
+        TransformationComp_Rotation,
+        TransformationComp_PostRotation,
+        TransformationComp_RotationPivotInverse,
+        TransformationComp_ScalingOffset,
+        TransformationComp_ScalingPivot,
+        TransformationComp_Scaling,
+        TransformationComp_ScalingPivotInverse,
+        TransformationComp_GeometricTranslation,
+        TransformationComp_GeometricRotation,
+        TransformationComp_GeometricScaling,
+
+        TransformationComp_MAXIMUM
+    };
+
+public:
+    FBXConverter(aiScene* out, const Document& doc);
+    ~FBXConverter();
+
+private:
+    // ------------------------------------------------------------------------------------------------
+    // find scene root and trigger recursive scene conversion
+    void ConvertRootNode();
+
+    // ------------------------------------------------------------------------------------------------
+    // collect and assign child nodes
+    void ConvertNodes(uint64_t id, aiNode& parent, const aiMatrix4x4& parent_transform = aiMatrix4x4());
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertLights(const Model& model, const std::string &orig_name );
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertCameras(const Model& model, const std::string &orig_name );
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertLight( const Light& light, const std::string &orig_name );
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertCamera( const Camera& cam, const std::string &orig_name );
+
+    // ------------------------------------------------------------------------------------------------
+    void GetUniqueName( const std::string &name, std::string& uniqueName );
+
+    // ------------------------------------------------------------------------------------------------
+    // this returns unified names usable within assimp identifiers (i.e. no space characters -
+    // while these would be allowed, they are a potential trouble spot so better not use them).
+    const char* NameTransformationComp(TransformationComp comp);
+
+    // ------------------------------------------------------------------------------------------------
+    // note: this returns the REAL fbx property names
+    const char* NameTransformationCompProperty(TransformationComp comp);
+
+    // ------------------------------------------------------------------------------------------------
+    aiVector3D TransformationCompDefaultValue(TransformationComp comp);
+
+    // ------------------------------------------------------------------------------------------------
+    void GetRotationMatrix(Model::RotOrder mode, const aiVector3D& rotation, aiMatrix4x4& out);
+    // ------------------------------------------------------------------------------------------------
+    /**
+    *  checks if a node has more than just scaling, rotation and translation components
+    */
+    bool NeedsComplexTransformationChain(const Model& model);
+
+    // ------------------------------------------------------------------------------------------------
+    // note: name must be a FixNodeName() result
+    std::string NameTransformationChainNode(const std::string& name, TransformationComp comp);
+
+    // ------------------------------------------------------------------------------------------------
+    /**
+    *  note: memory for output_nodes will be managed by the caller
+    */
+    void GenerateTransformationNodeChain(const Model& model, std::vector<aiNode*>& output_nodes, std::vector<aiNode*>& post_output_nodes);
+
+    // ------------------------------------------------------------------------------------------------
+    void SetupNodeMetadata(const Model& model, aiNode& nd);
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertModel(const Model& model, aiNode& nd, const aiMatrix4x4& node_global_transform);
+    
+    // ------------------------------------------------------------------------------------------------
+    // MeshGeometry -> aiMesh, return mesh index + 1 or 0 if the conversion failed
+    std::vector<unsigned int> ConvertMesh(const MeshGeometry& mesh, const Model& model,
+        const aiMatrix4x4& node_global_transform, aiNode& nd);
+
+    // ------------------------------------------------------------------------------------------------
+    std::vector<unsigned int> ConvertLine(const LineGeometry& line, const Model& model,
+        const aiMatrix4x4& node_global_transform, aiNode& nd);
+
+    // ------------------------------------------------------------------------------------------------
+    aiMesh* SetupEmptyMesh(const Geometry& mesh, aiNode& nd);
+
+    // ------------------------------------------------------------------------------------------------
+    unsigned int ConvertMeshSingleMaterial(const MeshGeometry& mesh, const Model& model,
+        const aiMatrix4x4& node_global_transform, aiNode& nd);
+
+    // ------------------------------------------------------------------------------------------------
+    std::vector<unsigned int> ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
+        const aiMatrix4x4& node_global_transform, aiNode& nd);
+
+    // ------------------------------------------------------------------------------------------------
+    unsigned int ConvertMeshMultiMaterial(const MeshGeometry& mesh, const Model& model,
+        MatIndexArray::value_type index,
+        const aiMatrix4x4& node_global_transform, aiNode& nd);
+
+    // ------------------------------------------------------------------------------------------------
+    static const unsigned int NO_MATERIAL_SEPARATION = /* std::numeric_limits<unsigned int>::max() */
+        static_cast<unsigned int>(-1);
+
+    // ------------------------------------------------------------------------------------------------
+    /**
+    *  - if materialIndex == NO_MATERIAL_SEPARATION, materials are not taken into
+    *    account when determining which weights to include.
+    *  - outputVertStartIndices is only used when a material index is specified, it gives for
+    *    each output vertex the DOM index it maps to.
+    */
+    void ConvertWeights(aiMesh* out, const Model& model, const MeshGeometry& geo,
+        const aiMatrix4x4& node_global_transform = aiMatrix4x4(),
+        unsigned int materialIndex = NO_MATERIAL_SEPARATION,
+        std::vector<unsigned int>* outputVertStartIndices = NULL);
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertCluster(std::vector<aiBone*>& bones, const Model& /*model*/, const Cluster& cl,
+        std::vector<size_t>& out_indices,
+        std::vector<size_t>& index_out_indices,
+        std::vector<size_t>& count_out_indices,
+        const aiMatrix4x4& node_global_transform);
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertMaterialForMesh(aiMesh* out, const Model& model, const MeshGeometry& geo,
+        MatIndexArray::value_type materialIndex);
+
+    // ------------------------------------------------------------------------------------------------
+    unsigned int GetDefaultMaterial();
+
+    // ------------------------------------------------------------------------------------------------
+    // Material -> aiMaterial
+    unsigned int ConvertMaterial(const Material& material, const MeshGeometry* const mesh);
+
+    // ------------------------------------------------------------------------------------------------
+    // Video -> aiTexture
+    unsigned int ConvertVideo(const Video& video);
+
+    // ------------------------------------------------------------------------------------------------
+    // convert embedded texture if necessary and return actual texture path
+    aiString GetTexturePath(const Texture* tex);
+
+    // ------------------------------------------------------------------------------------------------
+    void TrySetTextureProperties(aiMaterial* out_mat, const TextureMap& textures,
+        const std::string& propName,
+        aiTextureType target, const MeshGeometry* const mesh);
+
+    // ------------------------------------------------------------------------------------------------
+    void TrySetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures,
+        const std::string& propName,
+        aiTextureType target, const MeshGeometry* const mesh);
+
+    // ------------------------------------------------------------------------------------------------
+    void SetTextureProperties(aiMaterial* out_mat, const TextureMap& textures, const MeshGeometry* const mesh);
+
+    // ------------------------------------------------------------------------------------------------
+    void SetTextureProperties(aiMaterial* out_mat, const LayeredTextureMap& layeredTextures, const MeshGeometry* const mesh);
+
+    // ------------------------------------------------------------------------------------------------
+    aiColor3D GetColorPropertyFromMaterial(const PropertyTable& props, const std::string& baseName,
+        bool& result);
+    aiColor3D GetColorPropertyFactored(const PropertyTable& props, const std::string& colorName,
+        const std::string& factorName, bool& result, bool useTemplate = true);
+    aiColor3D GetColorProperty(const PropertyTable& props, const std::string& colorName,
+        bool& result, bool useTemplate = true);
+
+    // ------------------------------------------------------------------------------------------------
+    void SetShadingPropertiesCommon(aiMaterial* out_mat, const PropertyTable& props);
+    void SetShadingPropertiesRaw(aiMaterial* out_mat, const PropertyTable& props, const TextureMap& textures, const MeshGeometry* const mesh);
+
+    // ------------------------------------------------------------------------------------------------
+    // get the number of fps for a FrameRate enumerated value
+    static double FrameRateToDouble(FileGlobalSettings::FrameRate fp, double customFPSVal = -1.0);
+
+    // ------------------------------------------------------------------------------------------------
+    // convert animation data to aiAnimation et al
+    void ConvertAnimations();
+
+    // ------------------------------------------------------------------------------------------------
+    // takes a fbx node name and returns the identifier to be used in the assimp output scene.
+    // the function is guaranteed to provide consistent results over multiple invocations
+    // UNLESS RenameNode() is called for a particular node name.
+    std::string FixNodeName(const std::string& name);
+    std::string FixAnimMeshName(const std::string& name);
+
+    typedef std::map<const AnimationCurveNode*, const AnimationLayer*> LayerMap;
+
+    // XXX: better use multi_map ..
+    typedef std::map<std::string, std::vector<const AnimationCurveNode*> > NodeMap;
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertAnimationStack(const AnimationStack& st);
+
+    // ------------------------------------------------------------------------------------------------
+    void ProcessMorphAnimDatas(std::map<std::string, morphAnimData*>* morphAnimDatas, const BlendShapeChannel* bsc, const AnimationCurveNode* node);
+
+    // ------------------------------------------------------------------------------------------------
+    void GenerateNodeAnimations(std::vector<aiNodeAnim*>& node_anims,
+        const std::string& fixed_name,
+        const std::vector<const AnimationCurveNode*>& curves,
+        const LayerMap& layer_map,
+        int64_t start, int64_t stop,
+        double& max_time,
+        double& min_time);
+
+    // ------------------------------------------------------------------------------------------------
+    bool IsRedundantAnimationData(const Model& target,
+        TransformationComp comp,
+        const std::vector<const AnimationCurveNode*>& curves);
+
+    // ------------------------------------------------------------------------------------------------
+    aiNodeAnim* GenerateRotationNodeAnim(const std::string& name,
+        const Model& target,
+        const std::vector<const AnimationCurveNode*>& curves,
+        const LayerMap& layer_map,
+        int64_t start, int64_t stop,
+        double& max_time,
+        double& min_time);
+
+    // ------------------------------------------------------------------------------------------------
+    aiNodeAnim* GenerateScalingNodeAnim(const std::string& name,
+        const Model& /*target*/,
+        const std::vector<const AnimationCurveNode*>& curves,
+        const LayerMap& layer_map,
+        int64_t start, int64_t stop,
+        double& max_time,
+        double& min_time);
+
+    // ------------------------------------------------------------------------------------------------
+    aiNodeAnim* GenerateTranslationNodeAnim(const std::string& name,
+        const Model& /*target*/,
+        const std::vector<const AnimationCurveNode*>& curves,
+        const LayerMap& layer_map,
+        int64_t start, int64_t stop,
+        double& max_time,
+        double& min_time,
+        bool inverse = false);
+
+    // ------------------------------------------------------------------------------------------------
+    // generate node anim, extracting only Rotation, Scaling and Translation from the given chain
+    aiNodeAnim* GenerateSimpleNodeAnim(const std::string& name,
+        const Model& target,
+        NodeMap::const_iterator chain[TransformationComp_MAXIMUM],
+        NodeMap::const_iterator iter_end,
+        const LayerMap& layer_map,
+        int64_t start, int64_t stop,
+        double& max_time,
+        double& min_time,
+        bool reverse_order = false);
+
+    // key (time), value, mapto (component index)
+    typedef std::tuple<std::shared_ptr<KeyTimeList>, std::shared_ptr<KeyValueList>, unsigned int > KeyFrameList;
+    typedef std::vector<KeyFrameList> KeyFrameListList;
+
+    // ------------------------------------------------------------------------------------------------
+    KeyFrameListList GetKeyframeList(const std::vector<const AnimationCurveNode*>& nodes, int64_t start, int64_t stop);
+
+    // ------------------------------------------------------------------------------------------------
+    KeyTimeList GetKeyTimeList(const KeyFrameListList& inputs);
+
+    // ------------------------------------------------------------------------------------------------
+    void InterpolateKeys(aiVectorKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+        const aiVector3D& def_value,
+        double& max_time,
+        double& min_time);
+
+    // ------------------------------------------------------------------------------------------------
+    void InterpolateKeys(aiQuatKey* valOut, const KeyTimeList& keys, const KeyFrameListList& inputs,
+        const aiVector3D& def_value,
+        double& maxTime,
+        double& minTime,
+        Model::RotOrder order);
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertTransformOrder_TRStoSRT(aiQuatKey* out_quat, aiVectorKey* out_scale,
+        aiVectorKey* out_translation,
+        const KeyFrameListList& scaling,
+        const KeyFrameListList& translation,
+        const KeyFrameListList& rotation,
+        const KeyTimeList& times,
+        double& maxTime,
+        double& minTime,
+        Model::RotOrder order,
+        const aiVector3D& def_scale,
+        const aiVector3D& def_translate,
+        const aiVector3D& def_rotation);
+
+    // ------------------------------------------------------------------------------------------------
+    // euler xyz -> quat
+    aiQuaternion EulerToQuaternion(const aiVector3D& rot, Model::RotOrder order);
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertScaleKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes, const LayerMap& /*layers*/,
+        int64_t start, int64_t stop,
+        double& maxTime,
+        double& minTime);
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertTranslationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
+        const LayerMap& /*layers*/,
+        int64_t start, int64_t stop,
+        double& maxTime,
+        double& minTime);
+
+    // ------------------------------------------------------------------------------------------------
+    void ConvertRotationKeys(aiNodeAnim* na, const std::vector<const AnimationCurveNode*>& nodes,
+        const LayerMap& /*layers*/,
+        int64_t start, int64_t stop,
+        double& maxTime,
+        double& minTime,
+        Model::RotOrder order);
+
+    void ConvertGlobalSettings();
+
+    // ------------------------------------------------------------------------------------------------
+    // copy generated meshes, animations, lights, cameras and textures to the output scene
+    void TransferDataToScene();
+
+private:
+
+    // 0: not assigned yet, others: index is value - 1
+    unsigned int defaultMaterialIndex;
+
+    std::vector<aiMesh*> meshes;
+    std::vector<aiMaterial*> materials;
+    std::vector<aiAnimation*> animations;
+    std::vector<aiLight*> lights;
+    std::vector<aiCamera*> cameras;
+    std::vector<aiTexture*> textures;
+    
+
+    typedef std::map<const Material*, unsigned int> MaterialMap;
+    MaterialMap materials_converted;
+
+    typedef std::map<const Video*, unsigned int> VideoMap;
+    VideoMap textures_converted;
+
+    typedef std::map<const Geometry*, std::vector<unsigned int> > MeshMap;
+    MeshMap meshes_converted;
+
+    // fixed node name -> which trafo chain components have animations?
+    typedef std::map<std::string, unsigned int> NodeAnimBitMap;
+    NodeAnimBitMap node_anim_chain_bits;
+
+    NodeNameCache mNodeNames;
+    double anim_fps;
+
+    aiScene* const out;
+    const FBX::Document& doc;
+};
+
+}
+}
+
+#endif // INCLUDED_AI_FBX_CONVERTER_H

+ 213 - 0
thirdparty/assimp/code/FBXDeformer.cpp

@@ -0,0 +1,213 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXParser.h"
+#include "FBXDocument.h"
+#include "FBXMeshGeometry.h"
+#include "FBXImporter.h"
+#include "FBXDocumentUtil.h"
+
+namespace Assimp {
+namespace FBX {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Deformer::Deformer(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+    : Object(id,element,name)
+{
+    const Scope& sc = GetRequiredScope(element);
+
+    const std::string& classname = ParseTokenAsString(GetRequiredToken(element,2));
+    props = GetPropertyTable(doc,"Deformer.Fbx" + classname,element,sc,true);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Deformer::~Deformer()
+{
+
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Cluster::Cluster(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: Deformer(id,element,doc,name)
+, node()
+{
+    const Scope& sc = GetRequiredScope(element);
+
+    const Element* const Indexes = sc["Indexes"];
+    const Element* const Weights = sc["Weights"];
+
+    const Element& Transform = GetRequiredElement(sc,"Transform",&element);
+    const Element& TransformLink = GetRequiredElement(sc,"TransformLink",&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* const mod = ProcessSimpleConnection<Model>(*con, false, "Model -> Cluster", element);
+        if(mod) {
+            node = mod;
+            break;
+        }
+    }
+
+    if (!node) {
+        DOMError("failed to read target Node for Cluster",&element);
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Cluster::~Cluster()
+{
+
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Skin::Skin(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: Deformer(id,element,doc,name)
+, accuracy( 0.0f ) {
+    const Scope& sc = GetRequiredScope(element);
+
+    const Element* const Link_DeformAcuracy = sc["Link_DeformAcuracy"];
+    if(Link_DeformAcuracy) {
+        accuracy = ParseTokenAsFloat(GetRequiredToken(*Link_DeformAcuracy,0));
+    }
+
+    // resolve assigned clusters
+    const std::vector<const Connection*>& conns = doc.GetConnectionsByDestinationSequenced(ID(),"Deformer");
+
+    clusters.reserve(conns.size());
+    for(const Connection* con : conns) {
+
+        const Cluster* const cluster = ProcessSimpleConnection<Cluster>(*con, false, "Cluster -> Skin", element);
+        if(cluster) {
+            clusters.push_back(cluster);
+            continue;
+        }
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Skin::~Skin()
+{
+
+}
+// ------------------------------------------------------------------------------------------------
+BlendShape::BlendShape(uint64_t id, const Element& 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* const bspc = ProcessSimpleConnection<BlendShapeChannel>(*con, false, "BlendShapeChannel -> BlendShape", element);
+        if (bspc) {
+            blendShapeChannels.push_back(bspc);
+            continue;
+        }
+    }
+}
+// ------------------------------------------------------------------------------------------------
+BlendShape::~BlendShape()
+{
+
+}
+// ------------------------------------------------------------------------------------------------
+BlendShapeChannel::BlendShapeChannel(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+    : Deformer(id, element, doc, name)
+{
+    const Scope& sc = GetRequiredScope(element);
+    const Element* const DeformPercent = sc["DeformPercent"];
+    if (DeformPercent) {
+        percent = ParseTokenAsFloat(GetRequiredToken(*DeformPercent, 0));
+    }
+    const Element* const FullWeights = sc["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()
+{
+
+}
+// ------------------------------------------------------------------------------------------------
+}
+}
+#endif
+

+ 726 - 0
thirdparty/assimp/code/FBXDocument.cpp

@@ -0,0 +1,726 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXDocument.h"
+#include "FBXMeshGeometry.h"
+#include "FBXParser.h"
+#include "FBXUtil.h"
+#include "FBXImporter.h"
+#include "FBXImportSettings.h"
+#include "FBXDocumentUtil.h"
+#include "FBXProperties.h"
+
+#include <memory>
+#include <functional>
+#include <map>
+
+namespace Assimp {
+namespace FBX {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+LazyObject::LazyObject(uint64_t id, const Element& element, const Document& doc)
+: doc(doc)
+, element(element)
+, id(id)
+, flags() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+LazyObject::~LazyObject()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+const Object* LazyObject::Get(bool dieOnError)
+{
+    if(IsBeingConstructed() || FailedToConstruct()) {
+        return nullptr;
+    }
+
+    if (object.get()) {
+        return object.get();
+    }
+
+    // if this is the root object, we return a dummy since there
+    // is no root object int he fbx file - it is just referenced
+    // with id 0.
+    if(id == 0L) {
+        object.reset(new Object(id, element, "Model::RootNode"));
+        return object.get();
+    }
+
+    const Token& key = element.KeyToken();
+    const TokenList& tokens = element.Tokens();
+
+    if(tokens.size() < 3) {
+        DOMError("expected at least 3 tokens: id, name and class tag",&element);
+    }
+
+    const char* err;
+    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;
+
+    try {
+        // 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());
+
+        // For debugging
+        //dumpObjectClassInfo( objtype, classtag );
+
+        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")) {
+                object.reset(new LimbNode(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 ) ) {
+            // FK and IK effectors are not supported
+            if ( strcmp( classtag.c_str(), "IKEffector" ) && strcmp( classtag.c_str(), "FKEffector" ) ) {
+                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));
+        }
+        // note: order matters for these two
+        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));
+        }
+    }
+    catch(std::exception& ex) {
+        flags &= ~BEING_CONSTRUCTED;
+        flags |= FAILED_TO_CONSTRUCT;
+
+        if(dieOnError || doc.Settings().strictMode) {
+            throw;
+        }
+
+        // note: the error message is already formatted, so raw logging is ok
+        if(!DefaultLogger::isNullLogger()) {
+            ASSIMP_LOG_ERROR(ex.what());
+        }
+        return NULL;
+    }
+
+    if (!object.get()) {
+        //DOMError("failed to convert element to DOM object, class: " + classtag + ", name: " + name,&element);
+    }
+
+    flags &= ~BEING_CONSTRUCTED;
+    return object.get();
+}
+
+// ------------------------------------------------------------------------------------------------
+Object::Object(uint64_t id, const Element& element, const std::string& name)
+: element(element)
+, name(name)
+, id(id)
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Object::~Object()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+FileGlobalSettings::FileGlobalSettings(const Document& doc, std::shared_ptr<const PropertyTable> props)
+: props(props)
+, doc(doc)
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+FileGlobalSettings::~FileGlobalSettings()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Document::Document(const Parser& parser, const ImportSettings& settings)
+: settings(settings)
+, parser(parser)
+{
+    // Cannot use array default initialization syntax because vc8 fails on it
+    for (auto &timeStamp : creationTimeStamp) {
+        timeStamp = 0;
+    }
+
+    ReadHeader();
+    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(ObjectMap::value_type& v : objects) {
+        delete v.second;
+    }
+
+    for(ConnectionMap::value_type& v : src_connections) {
+        delete v.second;
+    }
+    // |dest_connections| contain the same Connection objects as the |src_connections|
+}
+
+// ------------------------------------------------------------------------------------------------
+static const unsigned int LowerSupportedVersion = 7100;
+static const unsigned int UpperSupportedVersion = 7400;
+
+void Document::ReadHeader() {
+    // Read ID objects from "Objects" section
+    const Scope& sc = parser.GetRootScope();
+    const Element* const ehead = sc["FBXHeaderExtension"];
+    if(!ehead || !ehead->Compound()) {
+        DOMError("no FBXHeaderExtension dictionary found");
+    }
+
+    const Scope& 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 ) {
+        DOMError("unsupported, old format version, supported are only FBX 2011, FBX 2012 and FBX 2013");
+    }
+    if(fbxVersion > UpperSupportedVersion ) {
+        if(Settings().strictMode) {
+            DOMError("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013"
+                " (turn off strict mode to try anyhow) ");
+        }
+        else {
+            DOMWarning("unsupported, newer format version, supported are only FBX 2011, FBX 2012 and FBX 2013,"
+                " trying to read it nevertheless");
+        }
+    }
+
+    const Element* const ecreator = shead["Creator"];
+    if(ecreator) {
+        creator = ParseTokenAsString(GetRequiredToken(*ecreator,0));
+    }
+
+    const Element* const etimestamp = shead["CreationTimeStamp"];
+    if(etimestamp && etimestamp->Compound()) {
+        const Scope& 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));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadGlobalSettings()
+{
+    const Scope& sc = parser.GetRootScope();
+    const Element* const ehead = sc["GlobalSettings"];
+    if ( nullptr == ehead || !ehead->Compound() ) {
+        DOMWarning( "no GlobalSettings dictionary found" );
+        globals.reset(new FileGlobalSettings(*this, std::make_shared<const PropertyTable>()));
+        return;
+    }
+
+    std::shared_ptr<const PropertyTable> props = GetPropertyTable( *this, "", *ehead, *ehead->Compound(), true );
+
+    //double v = PropertyGet<float>( *props.get(), std::string("UnitScaleFactor"), 1.0 );
+
+    if(!props) {
+        DOMError("GlobalSettings dictionary contains no property table");
+    }
+
+    globals.reset(new FileGlobalSettings(*this, props));
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadObjects()
+{
+    // read ID objects from "Objects" section
+    const Scope& sc = parser.GetRootScope();
+    const Element* const eobjects = sc["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 Scope& sobjects = *eobjects->Compound();
+    for(const ElementMap::value_type& el : sobjects.Elements()) {
+
+        // extract ID
+        const TokenList& tok = el.second->Tokens();
+
+        if (tok.empty()) {
+            DOMError("expected ID after object key",el.second);
+        }
+
+        const char* err;
+        const uint64_t id = ParseTokenAsID(*tok[0], err);
+        if(err) {
+            DOMError(err,el.second);
+        }
+
+        // id=0 is normally implicit
+        if(id == 0L) {
+            DOMError("encountered object with implicitly defined id 0",el.second);
+        }
+
+        if(objects.find(id) != objects.end()) {
+            DOMWarning("encountered duplicate object id, ignoring first occurrence",el.second);
+        }
+
+        objects[id] = new LazyObject(id, *el.second, *this);
+
+        // grab all animation stacks upfront since there is no listing of them
+        if(!strcmp(el.first.c_str(),"AnimationStack")) {
+            animationStacks.push_back(id);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadPropertyTemplates()
+{
+    const Scope& sc = parser.GetRootScope();
+    // read property templates from "Definitions" section
+    const Element* const edefs = sc["Definitions"];
+    if(!edefs || !edefs->Compound()) {
+        DOMWarning("no Definitions dictionary found");
+        return;
+    }
+
+    const Scope& sdefs = *edefs->Compound();
+    const ElementCollection otypes = sdefs.GetCollection("ObjectType");
+    for(ElementMap::const_iterator it = otypes.first; it != otypes.second; ++it) {
+        const Element& el = *(*it).second;
+        const Scope* sc = el.Compound();
+        if(!sc) {
+            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->GetCollection("PropertyTemplate");
+        for(ElementMap::const_iterator it = templs.first; it != templs.second; ++it) {
+            const Element& el = *(*it).second;
+            const Scope* sc = el.Compound();
+            if(!sc) {
+                DOMWarning("expected nested scope in PropertyTemplate, ignoring",&el);
+                continue;
+            }
+
+            const TokenList& tok = el.Tokens();
+            if(tok.empty()) {
+                DOMWarning("expected name for PropertyTemplate element, ignoring",&el);
+                continue;
+            }
+
+            const std::string& pname = ParseTokenAsString(*tok[0]);
+
+            const Element* Properties70 = (*sc)["Properties70"];
+            if(Properties70) {
+                std::shared_ptr<const PropertyTable> props = std::make_shared<const PropertyTable>(
+                    *Properties70,std::shared_ptr<const PropertyTable>(static_cast<const PropertyTable*>(NULL))
+                );
+
+                templates[oname+"."+pname] = props;
+            }
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void Document::ReadConnections()
+{
+    const Scope& sc = parser.GetRootScope();
+    // read property templates from "Definitions" section
+    const Element* const econns = sc["Connections"];
+    if(!econns || !econns->Compound()) {
+        DOMError("no Connections dictionary found");
+    }
+
+    uint64_t insertionOrder = 0l;
+    const Scope& sconns = *econns->Compound();
+    const ElementCollection conns = sconns.GetCollection("C");
+    for(ElementMap::const_iterator it = conns.first; it != conns.second; ++it) {
+        const Element& 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* const lazy = GetObject(id);
+        const AnimationStack* stack;
+        if(!lazy || !(stack = lazy->Get<AnimationStack>())) {
+            DOMWarning("failed to read AnimationStack object");
+            continue;
+        }
+        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
+
+{
+    ai_assert(classnames);
+    ai_assert( count != 0 );
+    ai_assert( count <= MAX_CLASSNAMES);
+
+    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) {
+        const Token& 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)
+{
+    ai_assert(doc.Objects().find(src) != doc.Objects().end());
+    // dest may be 0 (root node)
+    ai_assert(!dest || doc.Objects().find(dest) != doc.Objects().end());
+}
+
+// ------------------------------------------------------------------------------------------------
+Connection::~Connection()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+LazyObject& Connection::LazySourceObject() const
+{
+    LazyObject* const lazy = doc.GetObject(src);
+    ai_assert(lazy);
+    return *lazy;
+}
+
+// ------------------------------------------------------------------------------------------------
+LazyObject& Connection::LazyDestinationObject() const
+{
+    LazyObject* const lazy = doc.GetObject(dest);
+    ai_assert(lazy);
+    return *lazy;
+}
+
+// ------------------------------------------------------------------------------------------------
+const Object* Connection::SourceObject() const
+{
+    LazyObject* const lazy = doc.GetObject(src);
+    ai_assert(lazy);
+    return lazy->Get();
+}
+
+// ------------------------------------------------------------------------------------------------
+const Object* Connection::DestinationObject() const
+{
+    LazyObject* const lazy = doc.GetObject(dest);
+    ai_assert(lazy);
+    return lazy->Get();
+}
+
+} // !FBX
+} // !Assimp
+
+#endif

+ 1180 - 0
thirdparty/assimp/code/FBXDocument.h

@@ -0,0 +1,1180 @@
+/*
+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.h
+ *  @brief FBX DOM
+ */
+#ifndef INCLUDED_AI_FBX_DOCUMENT_H
+#define INCLUDED_AI_FBX_DOCUMENT_H
+
+#include <numeric>
+#include <stdint.h>
+#include <assimp/mesh.h>
+#include "FBXProperties.h"
+#include "FBXParser.h"
+
+#define _AI_CONCAT(a,b)  a ## b
+#define  AI_CONCAT(a,b)  _AI_CONCAT(a,b)
+
+namespace Assimp {
+namespace FBX {
+
+class Parser;
+class Object;
+struct ImportSettings;
+
+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;
+
+
+/** 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 Element& element, const Document& doc);
+
+    ~LazyObject();
+
+    const Object* Get(bool dieOnError = false);
+
+    template <typename T>
+    const T* Get(bool dieOnError = false) {
+        const Object* const ob = Get(dieOnError);
+        return ob ? dynamic_cast<const T*>(ob) : NULL;
+    }
+
+    uint64_t ID() const {
+        return id;
+    }
+
+    bool IsBeingConstructed() const {
+        return (flags & BEING_CONSTRUCTED) != 0;
+    }
+
+    bool FailedToConstruct() const {
+        return (flags & FAILED_TO_CONSTRUCT) != 0;
+    }
+
+    const Element& GetElement() const {
+        return element;
+    }
+
+    const Document& GetDocument() const {
+        return doc;
+    }
+
+private:
+    const Document& doc;
+    const Element& element;
+    std::unique_ptr<const Object> object;
+
+    const uint64_t id;
+
+    enum Flags {
+        BEING_CONSTRUCTED = 0x1,
+        FAILED_TO_CONSTRUCT = 0x2
+    };
+
+    unsigned int flags;
+};
+
+/** Base class for in-memory (DOM) representations of FBX objects */
+class Object {
+public:
+    Object(uint64_t id, const Element& element, const std::string& name);
+
+    virtual ~Object();
+
+    const Element& SourceElement() const {
+        return element;
+    }
+
+    const std::string& Name() const {
+        return name;
+    }
+
+    uint64_t ID() const {
+        return id;
+    }
+
+protected:
+    const Element& element;
+    const std::string name;
+    const uint64_t id;
+};
+
+/** 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 Element& element, const Document& doc, const std::string& name);
+
+    virtual ~NodeAttribute();
+
+    const PropertyTable& Props() const {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+private:
+    std::shared_ptr<const PropertyTable> props;
+};
+
+/** DOM base class for FBX camera settings attached to a node */
+class CameraSwitcher : public NodeAttribute {
+public:
+    CameraSwitcher(uint64_t id, const Element& 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)) { \
+            ai_assert(static_cast<int>(default_value) >= 0 && static_cast<int>(default_value) < AI_CONCAT(type, _MAX)); \
+            return static_cast<type>(default_value); \
+        } \
+        return static_cast<type>(ival); \
+}
+
+
+/** DOM base class for FBX cameras attached to a node */
+class Camera : public NodeAttribute {
+public:
+    Camera(uint64_t id, const Element& element, const Document& doc, const std::string& name);
+
+    virtual  ~Camera();
+
+    fbx_simple_property(Position, aiVector3D, aiVector3D(0,0,0))
+    fbx_simple_property(UpVector, aiVector3D, aiVector3D(0,1,0))
+    fbx_simple_property(InterestPosition, aiVector3D, aiVector3D(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 Element& 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 Element& 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 Element& 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, aiVector3D, aiVector3D(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, aiVector3D, aiVector3D(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)
+};
+
+/** 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
+    };
+
+    enum TransformInheritance {
+        TransformInheritance_RrSs = 0,
+        TransformInheritance_RSrs,
+        TransformInheritance_Rrs,
+
+        TransformInheritance_MAX // end-of-enum sentinel
+    };
+
+    Model(uint64_t id, const Element& element, const Document& doc, const std::string& name);
+
+    virtual ~Model();
+
+    fbx_simple_property(QuaternionInterpolate, int, 0)
+
+    fbx_simple_property(RotationOffset, aiVector3D, aiVector3D())
+    fbx_simple_property(RotationPivot, aiVector3D, aiVector3D())
+    fbx_simple_property(ScalingOffset, aiVector3D, aiVector3D())
+    fbx_simple_property(ScalingPivot, aiVector3D, aiVector3D())
+    fbx_simple_property(TranslationActive, bool, false)
+
+    fbx_simple_property(TranslationMin, aiVector3D, aiVector3D())
+    fbx_simple_property(TranslationMax, aiVector3D, aiVector3D())
+
+    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, aiVector3D, aiVector3D())
+    fbx_simple_property(PostRotation, aiVector3D, aiVector3D())
+    fbx_simple_property(RotationActive, bool, false)
+
+    fbx_simple_property(RotationMin, aiVector3D, aiVector3D())
+    fbx_simple_property(RotationMax, aiVector3D, aiVector3D())
+
+    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, aiVector3D, aiVector3D())
+    fbx_simple_property(ScalingMax, aiVector3D, aiVector3D(1.f,1.f,1.f))
+    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, aiVector3D, aiVector3D())
+    fbx_simple_property(GeometricRotation, aiVector3D, aiVector3D())
+    fbx_simple_property(GeometricScaling, aiVector3D, aiVector3D(1.f, 1.f, 1.f))
+
+    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 {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+    /** 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 Element& 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;
+    std::shared_ptr<const PropertyTable> props;
+};
+
+/** DOM class for generic FBX textures */
+class Texture : public Object {
+public:
+    Texture(uint64_t id, const Element& 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 aiVector2D& UVTranslation() const {
+        return uvTrans;
+    }
+
+    const aiVector2D& UVScaling() const {
+        return uvScaling;
+    }
+
+    const PropertyTable& Props() const {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+    // return a 4-tuple
+    const unsigned int* Crop() const {
+        return crop;
+    }
+
+    const Video* Media() const {
+        return media;
+    }
+
+private:
+    aiVector2D uvTrans;
+    aiVector2D uvScaling;
+
+    std::string type;
+    std::string relativeFileName;
+    std::string fileName;
+    std::string alphaSource;
+    std::shared_ptr<const PropertyTable> props;
+
+    unsigned int crop[4];
+
+    const Video* media;
+};
+
+/** DOM class for layered FBX textures */
+class LayeredTexture : public Object {
+public:
+    LayeredTexture(uint64_t id, const Element& 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::fbx_unordered_map<std::string, const Texture*> TextureMap;
+typedef std::fbx_unordered_map<std::string, const LayeredTexture*> LayeredTextureMap;
+
+
+/** DOM class for generic FBX videos */
+class Video : public Object {
+public:
+    Video(uint64_t id, const Element& element, const Document& doc, const std::string& name);
+
+    virtual ~Video();
+
+    const std::string& Type() const {
+        return type;
+    }
+
+    const std::string& FileName() const {
+        return fileName;
+    }
+
+    const std::string& RelativeFilename() const {
+        return relativeFileName;
+    }
+
+    const PropertyTable& Props() const {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+    const uint8_t* Content() const {
+        ai_assert(content);
+        return content;
+    }
+
+    uint32_t ContentLength() const {
+        return contentLength;
+    }
+
+    uint8_t* RelinquishContent() {
+        uint8_t* ptr = content;
+        content = 0;
+        return ptr;
+    }
+
+private:
+    std::string type;
+    std::string relativeFileName;
+    std::string fileName;
+    std::shared_ptr<const PropertyTable> props;
+
+    uint32_t contentLength;
+    uint8_t* content;
+};
+
+/** DOM class for generic FBX materials */
+class Material : public Object {
+public:
+    Material(uint64_t id, const Element& 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 {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+    const TextureMap& Textures() const {
+        return textures;
+    }
+
+    const LayeredTextureMap& LayeredTextures() const {
+        return layeredTextures;
+    }
+
+private:
+    std::string shading;
+    bool multilayer;
+    std::shared_ptr<const PropertyTable> props;
+
+    TextureMap textures;
+    LayeredTextureMap layeredTextures;
+};
+
+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 Element& 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::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::vector<unsigned int> flags;
+};
+
+// property-name -> animation curve
+typedef std::map<std::string, const AnimationCurve*> AnimationCurveMap;
+
+/** 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 Element& element, const std::string& name, const Document& doc,
+        const char* const * target_prop_whitelist = NULL, size_t whitelist_size = 0);
+
+    virtual ~AnimationCurveNode();
+
+    const PropertyTable& Props() const {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+
+    const AnimationCurveMap& 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.*/
+    const Object* Target() const {
+        return target;
+    }
+
+    const Model* TargetAsModel() const {
+        return dynamic_cast<const Model*>(target);
+    }
+
+    const NodeAttribute* TargetAsNodeAttribute() const {
+        return dynamic_cast<const NodeAttribute*>(target);
+    }
+
+    /** Property of Target() that is being animated*/
+    const std::string& TargetProperty() const {
+        return prop;
+    }
+
+private:
+    const Object* target;
+    std::shared_ptr<const PropertyTable> props;
+    mutable AnimationCurveMap curves;
+
+    std::string prop;
+    const Document& doc;
+};
+
+typedef std::vector<const AnimationCurveNode*> AnimationCurveNodeList;
+
+/** Represents a FBX animation layer (i.e. a list of node animations) */
+class AnimationLayer : public Object {
+public:
+    AnimationLayer(uint64_t id, const Element& element, const std::string& name, const Document& doc);
+    virtual ~AnimationLayer();
+
+    const PropertyTable& Props() const {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+    /* 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. */
+    AnimationCurveNodeList Nodes(const char* const * target_prop_whitelist = nullptr, size_t whitelist_size = 0) const;
+
+private:
+    std::shared_ptr<const PropertyTable> props;
+    const Document& doc;
+};
+
+typedef std::vector<const AnimationLayer*> AnimationLayerList;
+
+/** Represents a FBX animation stack (i.e. a list of animation layers) */
+class AnimationStack : public Object {
+public:
+    AnimationStack(uint64_t id, const Element& 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 {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+    const AnimationLayerList& Layers() const {
+        return layers;
+    }
+
+private:
+    std::shared_ptr<const PropertyTable> props;
+    AnimationLayerList layers;
+};
+
+
+/** DOM class for deformers */
+class Deformer : public Object {
+public:
+    Deformer(uint64_t id, const Element& element, const Document& doc, const std::string& name);
+    virtual ~Deformer();
+
+    const PropertyTable& Props() const {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+private:
+    std::shared_ptr<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 Element& 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 Element& 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 Element& 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 WeightArray& 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 WeightIndexArray& GetIndices() const {
+        return indices;
+    }
+
+    /** */
+    const aiMatrix4x4& Transform() const {
+        return transform;
+    }
+
+    const aiMatrix4x4& TransformLink() const {
+        return transformLink;
+    }
+
+    const Model* TargetNode() const {
+        return node;
+    }
+
+private:
+    WeightArray weights;
+    WeightIndexArray indices;
+
+    aiMatrix4x4 transform;
+    aiMatrix4x4 transformLink;
+
+    const Model* node;
+};
+
+/** DOM class for skin deformers */
+class Skin : public Deformer {
+public:
+    Skin(uint64_t id, const Element& element, const Document& doc, const std::string& name);
+
+    virtual ~Skin();
+
+    float DeformAccuracy() const {
+        return accuracy;
+    }
+
+    const std::vector<const Cluster*>& Clusters() const {
+        return clusters;
+    }
+
+private:
+    float accuracy;
+    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.
+    const Object* SourceObject() const;
+    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::fbx_unordered_map<std::string, std::shared_ptr<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, std::shared_ptr<const PropertyTable> props);
+
+    ~FileGlobalSettings();
+
+    const PropertyTable& Props() const {
+        ai_assert(props.get());
+        return *props.get();
+    }
+
+    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, aiVector3D, aiVector3D(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:
+    std::shared_ptr<const PropertyTable> props;
+    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 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& GlobalSettings() const {
+        ai_assert(globals.get());
+        return *globals.get();
+    }
+
+    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;
+
+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;
+    void ReadHeader();
+    void ReadObjects();
+    void ReadPropertyTemplates();
+    void ReadConnections();
+    void ReadGlobalSettings();
+
+private:
+    const ImportSettings& settings;
+
+    ObjectMap objects;
+    const Parser& parser;
+
+    PropertyTemplateMap templates;
+    ConnectionMap src_connections;
+    ConnectionMap dest_connections;
+
+    unsigned int fbxVersion;
+    std::string creator;
+    unsigned int creationTimeStamp[7];
+
+    std::vector<uint64_t> animationStacks;
+    mutable std::vector<const AnimationStack*> animationStacksResolved;
+
+    std::unique_ptr<FileGlobalSettings> globals;
+};
+
+} // Namespace FBX
+} // Namespace Assimp
+
+#endif // INCLUDED_AI_FBX_DOCUMENT_H

+ 135 - 0
thirdparty/assimp/code/FBXDocumentUtil.cpp

@@ -0,0 +1,135 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXParser.h"
+#include "FBXDocument.h"
+#include "FBXUtil.h"
+#include "FBXDocumentUtil.h"
+#include "FBXProperties.h"
+
+
+namespace Assimp {
+namespace FBX {
+namespace Util {
+
+// ------------------------------------------------------------------------------------------------
+// signal DOM construction error, this is always unrecoverable. Throws DeadlyImportError.
+void DOMError(const std::string& message, const Token& token)
+{
+    throw DeadlyImportError(Util::AddTokenText("FBX-DOM",message,&token));
+}
+
+// ------------------------------------------------------------------------------------------------
+void DOMError(const std::string& message, const Element* element /*= NULL*/)
+{
+    if(element) {
+        DOMError(message,element->KeyToken());
+    }
+    throw DeadlyImportError("FBX-DOM " + message);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// print warning, do return
+void DOMWarning(const std::string& message, const Token& token)
+{
+    if(DefaultLogger::get()) {
+        ASSIMP_LOG_WARN(Util::AddTokenText("FBX-DOM",message,&token));
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void DOMWarning(const std::string& message, const Element* element /*= NULL*/)
+{
+    if(element) {
+        DOMWarning(message,element->KeyToken());
+        return;
+    }
+    if(DefaultLogger::get()) {
+        ASSIMP_LOG_WARN("FBX-DOM: " + message);
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// fetch a property table and the corresponding property template
+std::shared_ptr<const PropertyTable> GetPropertyTable(const Document& doc,
+    const std::string& templateName,
+    const Element &element,
+    const Scope& sc,
+    bool no_warn /*= false*/)
+{
+    const Element* const Properties70 = sc["Properties70"];
+    std::shared_ptr<const PropertyTable> templateProps = std::shared_ptr<const PropertyTable>(
+        static_cast<const PropertyTable*>(NULL));
+
+    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 std::make_shared<const PropertyTable>();
+        }
+    }
+    return std::make_shared<const PropertyTable>(*Properties70,templateProps);
+}
+} // !Util
+} // !FBX
+} // !Assimp
+
+#endif

+ 120 - 0
thirdparty/assimp/code/FBXDocumentUtil.h

@@ -0,0 +1,120 @@
+/*
+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 INCLUDED_AI_FBX_DOCUMENT_UTIL_H
+#define INCLUDED_AI_FBX_DOCUMENT_UTIL_H
+
+#include <assimp/defs.h>
+#include <string>
+#include <memory>
+#include "FBXDocument.h"
+
+struct Token;
+struct Element;
+
+namespace Assimp {
+namespace FBX {
+namespace Util {
+
+/* DOM/Parse error reporting - does not return */
+AI_WONT_RETURN void DOMError(const std::string& message, const Token& token) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN void DOMError(const std::string& message, const Element* element = NULL) AI_WONT_RETURN_SUFFIX;
+
+// does return
+void DOMWarning(const std::string& message, const Token& token);
+void DOMWarning(const std::string& message, const Element* element = NULL);
+
+
+// fetch a property table and the corresponding property template
+std::shared_ptr<const PropertyTable> GetPropertyTable(const Document& doc,
+    const std::string& templateName,
+    const Element &element,
+    const Scope& sc,
+    bool no_warn = false);
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+inline
+const T* ProcessSimpleConnection(const Connection& con,
+    bool is_object_property_conn,
+    const char* name,
+    const Element& 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();
+    }
+
+    const Object* const ob = con.SourceObject();
+    if(!ob) {
+        DOMWarning("failed to read source object for incoming " + std::string(name) +
+            " link, ignoring",
+            &element);
+        return nullptr;
+    }
+
+    return dynamic_cast<const T*>(ob);
+}
+
+} //!Util
+} //!FBX
+} //!Assimp
+
+#endif

+ 568 - 0
thirdparty/assimp/code/FBXExportNode.cpp

@@ -0,0 +1,568 @@
+/*
+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 ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportNode.h"
+#include "FBXCommon.h"
+
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/ai_assert.h>
+#include <assimp/StringUtils.h> // ai_snprintf
+
+#include <string>
+#include <ostream>
+#include <sstream> // ostringstream
+#include <memory> // shared_ptr
+
+// AddP70<type> helpers... there's no usable pattern here,
+// so all are defined as separate functions.
+// Even "animatable" properties are often completely different
+// from the standard (nonanimated) property definition,
+// so they are specified with an 'A' suffix.
+
+void FBX::Node::AddP70int(
+    const std::string& name, int32_t value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "int", "Integer", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70bool(
+    const std::string& name, bool value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "bool", "", "", int32_t(value));
+    AddChild(n);
+}
+
+void FBX::Node::AddP70double(
+    const std::string& name, double value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "double", "Number", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70numberA(
+    const std::string& name, double value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Number", "", "A", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70color(
+    const std::string& name, double r, double g, double b
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "ColorRGB", "Color", "", r, g, b);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70colorA(
+    const std::string& name, double r, double g, double b
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Color", "", "A", r, g, b);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70vector(
+    const std::string& name, double x, double y, double z
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Vector3D", "Vector", "", x, y, z);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70vectorA(
+    const std::string& name, double x, double y, double z
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "Vector", "", "A", x, y, z);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70string(
+    const std::string& name, const std::string& value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "KString", "", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70enum(
+    const std::string& name, int32_t value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "enum", "", "", value);
+    AddChild(n);
+}
+
+void FBX::Node::AddP70time(
+    const std::string& name, int64_t value
+) {
+    FBX::Node n("P");
+    n.AddProperties(name, "KTime", "Time", "", value);
+    AddChild(n);
+}
+
+
+// public member functions for writing nodes to stream
+
+void FBX::Node::Dump(
+    std::shared_ptr<Assimp::IOStream> outfile,
+    bool binary, int indent
+) {
+    if (binary) {
+        Assimp::StreamWriterLE outstream(outfile);
+        DumpBinary(outstream);
+    } else {
+        std::ostringstream ss;
+        DumpAscii(ss, indent);
+        std::string s = ss.str();
+        outfile->Write(s.c_str(), s.size(), 1);
+    }
+}
+
+void FBX::Node::Dump(
+    Assimp::StreamWriterLE &outstream,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpBinary(outstream);
+    } else {
+        std::ostringstream ss;
+        DumpAscii(ss, indent);
+        outstream.PutString(ss.str());
+    }
+}
+
+
+// public member functions for low-level writing
+
+void FBX::Node::Begin(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    if (binary) {
+        BeginBinary(s);
+    } else {
+        // assume we're at the correct place to start already
+        (void)indent;
+        std::ostringstream ss;
+        BeginAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::DumpProperties(
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpPropertiesBinary(s);
+    } else {
+        std::ostringstream ss;
+        DumpPropertiesAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::EndProperties(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    EndProperties(s, binary, indent, properties.size());
+}
+
+void FBX::Node::EndProperties(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent,
+    size_t num_properties
+) {
+    if (binary) {
+        EndPropertiesBinary(s, num_properties);
+    } else {
+        // nothing to do
+        (void)indent;
+    }
+}
+
+void FBX::Node::BeginChildren(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent
+) {
+    if (binary) {
+        // nothing to do
+    } else {
+        std::ostringstream ss;
+        BeginChildrenAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::DumpChildren(
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+) {
+    if (binary) {
+        DumpChildrenBinary(s);
+    } else {
+        std::ostringstream ss;
+        DumpChildrenAscii(ss, indent);
+        s.PutString(ss.str());
+    }
+}
+
+void FBX::Node::End(
+    Assimp::StreamWriterLE &s,
+    bool binary, int indent,
+    bool has_children
+) {
+    if (binary) {
+        EndBinary(s, has_children);
+    } else {
+        std::ostringstream ss;
+        EndAscii(ss, indent, has_children);
+        s.PutString(ss.str());
+    }
+}
+
+
+// public member functions for writing to binary fbx
+
+void FBX::Node::DumpBinary(Assimp::StreamWriterLE &s)
+{
+    // write header section (with placeholders for some things)
+    BeginBinary(s);
+
+    // write properties
+    DumpPropertiesBinary(s);
+
+    // go back and fill in property related placeholders
+    EndPropertiesBinary(s, properties.size());
+
+    // write children
+    DumpChildrenBinary(s);
+
+    // finish, filling in end offset placeholder
+    EndBinary(s, force_has_children || !children.empty());
+}
+
+
+// public member functions for writing to ascii fbx
+
+void FBX::Node::DumpAscii(std::ostream &s, int indent)
+{
+    // write name
+    BeginAscii(s, indent);
+
+    // write properties
+    DumpPropertiesAscii(s, indent);
+
+    if (force_has_children || !children.empty()) {
+        // begin children (with a '{')
+        BeginChildrenAscii(s, indent + 1);
+        // write children
+        DumpChildrenAscii(s, indent + 1);
+    }
+
+    // finish (also closing the children bracket '}')
+    EndAscii(s, indent, force_has_children || !children.empty());
+}
+
+
+// private member functions for low-level writing to fbx
+
+void FBX::Node::BeginBinary(Assimp::StreamWriterLE &s)
+{
+    // remember start pos so we can come back and write the end pos
+    this->start_pos = s.Tell();
+
+    // placeholders for end pos and property section info
+    s.PutU4(0); // end pos
+    s.PutU4(0); // number of properties
+    s.PutU4(0); // total property section length
+
+    // node name
+    s.PutU1(uint8_t(name.size())); // length of node name
+    s.PutString(name); // node name as raw bytes
+
+    // property data comes after here
+    this->property_start = s.Tell();
+}
+
+void FBX::Node::DumpPropertiesBinary(Assimp::StreamWriterLE& s)
+{
+    for (auto &p : properties) {
+        p.DumpBinary(s);
+    }
+}
+
+void FBX::Node::EndPropertiesBinary(
+    Assimp::StreamWriterLE &s,
+    size_t num_properties
+) {
+    if (num_properties == 0) { return; }
+    size_t pos = s.Tell();
+    ai_assert(pos > property_start);
+    size_t property_section_size = pos - property_start;
+    s.Seek(start_pos + 4);
+    s.PutU4(uint32_t(num_properties));
+    s.PutU4(uint32_t(property_section_size));
+    s.Seek(pos);
+}
+
+void FBX::Node::DumpChildrenBinary(Assimp::StreamWriterLE& s)
+{
+    for (FBX::Node& child : children) {
+        child.DumpBinary(s);
+    }
+}
+
+void FBX::Node::EndBinary(
+    Assimp::StreamWriterLE &s,
+    bool has_children
+) {
+    // if there were children, add a null record
+    if (has_children) { s.PutString(FBX::NULL_RECORD); }
+
+    // now go back and write initial pos
+    this->end_pos = s.Tell();
+    s.Seek(start_pos);
+    s.PutU4(uint32_t(end_pos));
+    s.Seek(end_pos);
+}
+
+
+void FBX::Node::BeginAscii(std::ostream& s, int indent)
+{
+    s << '\n';
+    for (int i = 0; i < indent; ++i) { s << '\t'; }
+    s << name << ": ";
+}
+
+void FBX::Node::DumpPropertiesAscii(std::ostream &s, int indent)
+{
+    for (size_t i = 0; i < properties.size(); ++i) {
+        if (i > 0) { s << ", "; }
+        properties[i].DumpAscii(s, indent);
+    }
+}
+
+void FBX::Node::BeginChildrenAscii(std::ostream& s, int indent)
+{
+    // only call this if there are actually children
+    s << " {";
+    (void)indent;
+}
+
+void FBX::Node::DumpChildrenAscii(std::ostream& s, int indent)
+{
+    // children will need a lot of padding and corralling
+    if (children.size() || force_has_children) {
+        for (size_t i = 0; i < children.size(); ++i) {
+            // no compression in ascii files, so skip this node if it exists
+            if (children[i].name == "EncryptionType") { continue; }
+            // the child can dump itself
+            children[i].DumpAscii(s, indent);
+        }
+    }
+}
+
+void FBX::Node::EndAscii(std::ostream& s, int indent, bool has_children)
+{
+    if (!has_children) { return; } // nothing to do
+    s << '\n';
+    for (int i = 0; i < indent; ++i) { s << '\t'; }
+    s << "}";
+}
+
+// private helpers for static member functions
+
+// ascii property node from vector of doubles
+void FBX::Node::WritePropertyNodeAscii(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s,
+    int indent
+){
+    char buffer[32];
+    FBX::Node node(name);
+    node.Begin(s, false, indent);
+    std::string vsize = to_string(v.size());
+    // *<size> {
+    s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n");
+    // indent + 1
+    for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); }
+    // a: value,value,value,...
+    s.PutString("a: ");
+    int count = 0;
+    for (size_t i = 0; i < v.size(); ++i) {
+        if (i > 0) { s.PutChar(','); }
+        int len = ai_snprintf(buffer, sizeof(buffer), "%f", v[i]);
+        count += len;
+        if (count > 2048) { s.PutChar('\n'); count = 0; }
+        if (len < 0 || len > 31) {
+            // this should never happen
+            throw DeadlyExportError("failed to convert double to string");
+        }
+        for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); }
+    }
+    // }
+    s.PutChar('\n');
+    for (int i = 0; i < indent; ++i) { s.PutChar('\t'); }
+    s.PutChar('}'); s.PutChar(' ');
+    node.End(s, false, indent, false);
+}
+
+// ascii property node from vector of int32_t
+void FBX::Node::WritePropertyNodeAscii(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s,
+    int indent
+){
+    char buffer[32];
+    FBX::Node node(name);
+    node.Begin(s, false, indent);
+    std::string vsize = to_string(v.size());
+    // *<size> {
+    s.PutChar('*'); s.PutString(vsize); s.PutString(" {\n");
+    // indent + 1
+    for (int i = 0; i < indent + 1; ++i) { s.PutChar('\t'); }
+    // a: value,value,value,...
+    s.PutString("a: ");
+    int count = 0;
+    for (size_t i = 0; i < v.size(); ++i) {
+        if (i > 0) { s.PutChar(','); }
+        int len = ai_snprintf(buffer, sizeof(buffer), "%d", v[i]);
+        count += len;
+        if (count > 2048) { s.PutChar('\n'); count = 0; }
+        if (len < 0 || len > 31) {
+            // this should never happen
+            throw DeadlyExportError("failed to convert double to string");
+        }
+        for (int j = 0; j < len; ++j) { s.PutChar(buffer[j]); }
+    }
+    // }
+    s.PutChar('\n');
+    for (int i = 0; i < indent; ++i) { s.PutChar('\t'); }
+    s.PutChar('}'); s.PutChar(' ');
+    node.End(s, false, indent, false);
+}
+
+// binary property node from vector of doubles
+// TODO: optional zip compression!
+void FBX::Node::WritePropertyNodeBinary(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s
+){
+    FBX::Node node(name);
+    node.BeginBinary(s);
+    s.PutU1('d');
+    s.PutU4(uint32_t(v.size())); // number of elements
+    s.PutU4(0); // no encoding (1 would be zip-compressed)
+    s.PutU4(uint32_t(v.size()) * 8); // data size
+    for (auto it = v.begin(); it != v.end(); ++it) { s.PutF8(*it); }
+    node.EndPropertiesBinary(s, 1);
+    node.EndBinary(s, false);
+}
+
+// binary property node from vector of int32_t
+// TODO: optional zip compression!
+void FBX::Node::WritePropertyNodeBinary(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s
+){
+    FBX::Node node(name);
+    node.BeginBinary(s);
+    s.PutU1('i');
+    s.PutU4(uint32_t(v.size())); // number of elements
+    s.PutU4(0); // no encoding (1 would be zip-compressed)
+    s.PutU4(uint32_t(v.size()) * 4); // data size
+    for (auto it = v.begin(); it != v.end(); ++it) { s.PutI4(*it); }
+    node.EndPropertiesBinary(s, 1);
+    node.EndBinary(s, false);
+}
+
+// public static member functions
+
+// convenience function to create and write a property node,
+// holding a single property which is an array of values.
+// does not copy the data, so is efficient for large arrays.
+void FBX::Node::WritePropertyNode(
+    const std::string& name,
+    const std::vector<double>& v,
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+){
+    if (binary) {
+        FBX::Node::WritePropertyNodeBinary(name, v, s);
+    } else {
+        FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
+    }
+}
+
+// convenience function to create and write a property node,
+// holding a single property which is an array of values.
+// does not copy the data, so is efficient for large arrays.
+void FBX::Node::WritePropertyNode(
+    const std::string& name,
+    const std::vector<int32_t>& v,
+    Assimp::StreamWriterLE& s,
+    bool binary, int indent
+){
+    if (binary) {
+        FBX::Node::WritePropertyNodeBinary(name, v, s);
+    } else {
+        FBX::Node::WritePropertyNodeAscii(name, v, s, indent);
+    }
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 271 - 0
thirdparty/assimp/code/FBXExportNode.h

@@ -0,0 +1,271 @@
+/*
+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 FBXExportNode.h
+* Declares the FBX::Node helper class for fbx export.
+*/
+#ifndef AI_FBXEXPORTNODE_H_INC
+#define AI_FBXEXPORTNODE_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportProperty.h"
+
+#include <assimp/StreamWriter.h> // StreamWriterLE
+
+#include <string>
+#include <vector>
+
+namespace FBX {
+    class Node;
+}
+
+class FBX::Node
+{
+public: // public data members
+    // TODO: accessors
+    std::string name; // node name
+    std::vector<FBX::Property> properties; // node properties
+    std::vector<FBX::Node> children; // child nodes
+
+    // some nodes always pretend they have children...
+    bool force_has_children = false;
+
+public: // constructors
+    /// The default class constructor.
+    Node() = default;
+
+    /// The class constructor with the name.
+    Node(const std::string& n)
+    : name(n)
+    , properties()
+    , children()
+    , force_has_children( false ) {
+        // empty
+    }
+
+    // convenience template to construct with properties directly
+    template <typename... More>
+    Node(const std::string& n, const More... more)
+    : name(n)
+    , properties()
+    , children()
+    , force_has_children(false) {
+        AddProperties(more...);
+    }
+
+public: // functions to add properties or children
+    // add a single property to the node
+    template <typename T>
+    void AddProperty(T value) {
+        properties.emplace_back(value);
+    }
+
+    // convenience function to add multiple properties at once
+    template <typename T, typename... More>
+    void AddProperties(T value, More... more) {
+        properties.emplace_back(value);
+        AddProperties(more...);
+    }
+    void AddProperties() {}
+
+    // add a child node directly
+    void AddChild(const Node& node) { children.push_back(node); }
+
+    // convenience function to add a child node with a single property
+    template <typename... More>
+    void AddChild(
+        const std::string& name,
+        More... more
+    ) {
+        FBX::Node c(name);
+        c.AddProperties(more...);
+        children.push_back(c);
+    }
+
+public: // support specifically for dealing with Properties70 nodes
+
+    // it really is simpler to make these all separate functions.
+    // the versions with 'A' suffixes are for animatable properties.
+    // those often follow a completely different format internally in FBX.
+    void AddP70int(const std::string& name, int32_t value);
+    void AddP70bool(const std::string& name, bool value);
+    void AddP70double(const std::string& name, double value);
+    void AddP70numberA(const std::string& name, double value);
+    void AddP70color(const std::string& name, double r, double g, double b);
+    void AddP70colorA(const std::string& name, double r, double g, double b);
+    void AddP70vector(const std::string& name, double x, double y, double z);
+    void AddP70vectorA(const std::string& name, double x, double y, double z);
+    void AddP70string(const std::string& name, const std::string& value);
+    void AddP70enum(const std::string& name, int32_t value);
+    void AddP70time(const std::string& name, int64_t value);
+
+    // template for custom P70 nodes.
+    // anything that doesn't fit in the above can be created manually.
+    template <typename... More>
+    void AddP70(
+        const std::string& name,
+        const std::string& type,
+        const std::string& type2,
+        const std::string& flags,
+        More... more
+    ) {
+        Node n("P");
+        n.AddProperties(name, type, type2, flags, more...);
+        AddChild(n);
+    }
+
+public: // member functions for writing data to a file or stream
+
+    // write the full node to the given file or stream
+    void Dump(
+        std::shared_ptr<Assimp::IOStream> outfile,
+        bool binary, int indent
+    );
+    void Dump(Assimp::StreamWriterLE &s, bool binary, int indent);
+
+    // these other functions are for writing data piece by piece.
+    // they must be used carefully.
+    // for usage examples see FBXExporter.cpp.
+    void Begin(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void DumpProperties(Assimp::StreamWriterLE& s, bool binary, int indent);
+    void EndProperties(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void EndProperties(
+        Assimp::StreamWriterLE &s, bool binary, int indent,
+        size_t num_properties
+    );
+    void BeginChildren(Assimp::StreamWriterLE &s, bool binary, int indent);
+    void DumpChildren(Assimp::StreamWriterLE& s, bool binary, int indent);
+    void End(
+        Assimp::StreamWriterLE &s, bool binary, int indent,
+        bool has_children
+    );
+
+private: // internal functions used for writing
+
+    void DumpBinary(Assimp::StreamWriterLE &s);
+    void DumpAscii(Assimp::StreamWriterLE &s, int indent);
+    void DumpAscii(std::ostream &s, int indent);
+
+    void BeginBinary(Assimp::StreamWriterLE &s);
+    void DumpPropertiesBinary(Assimp::StreamWriterLE& s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s);
+    void EndPropertiesBinary(Assimp::StreamWriterLE &s, size_t num_properties);
+    void DumpChildrenBinary(Assimp::StreamWriterLE& s);
+    void EndBinary(Assimp::StreamWriterLE &s, bool has_children);
+
+    void BeginAscii(std::ostream &s, int indent);
+    void DumpPropertiesAscii(std::ostream &s, int indent);
+    void BeginChildrenAscii(std::ostream &s, int indent);
+    void DumpChildrenAscii(std::ostream &s, int indent);
+    void EndAscii(std::ostream &s, int indent, bool has_children);
+
+private: // data used for binary dumps
+    size_t start_pos; // starting position in stream
+    size_t end_pos; // ending position in stream
+    size_t property_start; // starting position of property section
+
+public: // static member functions
+
+    // convenience function to create a node with a single property,
+    // and write it to the stream.
+    template <typename T>
+    static void WritePropertyNode(
+        const std::string& name,
+        const T value,
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
+    ) {
+        FBX::Property p(value);
+        FBX::Node node(name, p);
+        node.Dump(s, binary, indent);
+    }
+
+    // convenience function to create and write a property node,
+    // holding a single property which is an array of values.
+    // does not copy the data, so is efficient for large arrays.
+    static void WritePropertyNode(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
+    );
+
+    // convenience function to create and write a property node,
+    // holding a single property which is an array of values.
+    // does not copy the data, so is efficient for large arrays.
+    static void WritePropertyNode(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s,
+        bool binary, int indent
+    );
+
+private: // static helper functions
+    static void WritePropertyNodeAscii(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s,
+        int indent
+    );
+    static void WritePropertyNodeAscii(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s,
+        int indent
+    );
+    static void WritePropertyNodeBinary(
+        const std::string& name,
+        const std::vector<double>& v,
+        Assimp::StreamWriterLE& s
+    );
+    static void WritePropertyNodeBinary(
+        const std::string& name,
+        const std::vector<int32_t>& v,
+        Assimp::StreamWriterLE& s
+    );
+
+};
+
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXEXPORTNODE_H_INC

+ 364 - 0
thirdparty/assimp/code/FBXExportProperty.cpp

@@ -0,0 +1,364 @@
+/*
+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 ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportProperty.h"
+
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+
+#include <string>
+#include <vector>
+#include <ostream>
+#include <locale>
+#include <sstream> // ostringstream
+
+
+// constructors for single element properties
+
+FBX::Property::Property(bool v)
+    : type('C'), data(1)
+{
+    data = {uint8_t(v)};
+}
+
+FBX::Property::Property(int16_t v) : type('Y'), data(2)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<int16_t*>(d))[0] = v;
+}
+
+FBX::Property::Property(int32_t v) : type('I'), data(4)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<int32_t*>(d))[0] = v;
+}
+
+FBX::Property::Property(float v) : type('F'), data(4)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<float*>(d))[0] = v;
+}
+
+FBX::Property::Property(double v) : type('D'), data(8)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<double*>(d))[0] = v;
+}
+
+FBX::Property::Property(int64_t v) : type('L'), data(8)
+{
+    uint8_t* d = data.data();
+    (reinterpret_cast<int64_t*>(d))[0] = v;
+}
+
+
+// constructors for array-type properties
+
+FBX::Property::Property(const char* c, bool raw)
+    : Property(std::string(c), raw)
+{}
+
+// strings can either be saved as "raw" (R) data, or "string" (S) data
+FBX::Property::Property(const std::string& s, bool raw)
+    : type(raw ? 'R' : 'S'), data(s.size())
+{
+    for (size_t i = 0; i < s.size(); ++i) {
+        data[i] = uint8_t(s[i]);
+    }
+}
+
+FBX::Property::Property(const std::vector<uint8_t>& r)
+    : type('R'), data(r)
+{}
+
+FBX::Property::Property(const std::vector<int32_t>& va)
+    : type('i'), data(4*va.size())
+{
+    int32_t* d = reinterpret_cast<int32_t*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const std::vector<int64_t>& va)
+    : type('l'), data(8*va.size())
+{
+    int64_t* d = reinterpret_cast<int64_t*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const std::vector<float>& va)
+    : type('f'), data(4*va.size())
+{
+    float* d = reinterpret_cast<float*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const std::vector<double>& va)
+    : type('d'), data(8*va.size())
+{
+    double* d = reinterpret_cast<double*>(data.data());
+    for (size_t i = 0; i < va.size(); ++i) { d[i] = va[i]; }
+}
+
+FBX::Property::Property(const aiMatrix4x4& vm)
+    : type('d'), data(8*16)
+{
+    double* d = reinterpret_cast<double*>(data.data());
+    for (unsigned int c = 0; c < 4; ++c) {
+        for (unsigned int r = 0; r < 4; ++r) {
+            d[4*c+r] = vm[r][c];
+        }
+    }
+}
+
+// public member functions
+
+size_t FBX::Property::size()
+{
+    switch (type) {
+    case 'C': case 'Y': case 'I': case 'F': case 'D': case 'L':
+        return data.size() + 1;
+    case 'S': case 'R':
+        return data.size() + 5;
+    case 'i': case 'd':
+        return data.size() + 13;
+    default:
+        throw DeadlyExportError("Requested size on property of unknown type");
+    }
+}
+
+void FBX::Property::DumpBinary(Assimp::StreamWriterLE &s)
+{
+    s.PutU1(type);
+    uint8_t* d = data.data();
+    size_t N;
+    switch (type) {
+    case 'C': s.PutU1(*(reinterpret_cast<uint8_t*>(d))); return;
+    case 'Y': s.PutI2(*(reinterpret_cast<int16_t*>(d))); return;
+    case 'I': s.PutI4(*(reinterpret_cast<int32_t*>(d))); return;
+    case 'F': s.PutF4(*(reinterpret_cast<float*>(d))); return;
+    case 'D': s.PutF8(*(reinterpret_cast<double*>(d))); return;
+    case 'L': s.PutI8(*(reinterpret_cast<int64_t*>(d))); return;
+    case 'S':
+    case 'R':
+        s.PutU4(uint32_t(data.size()));
+        for (size_t i = 0; i < data.size(); ++i) { s.PutU1(data[i]); }
+        return;
+    case 'i':
+        N = data.size() / 4;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutI4((reinterpret_cast<int32_t*>(d))[i]);
+        }
+        return;
+    case 'l':
+        N = data.size() / 8;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutI8((reinterpret_cast<int64_t*>(d))[i]);
+        }
+        return;
+    case 'f':
+        N = data.size() / 4;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutF4((reinterpret_cast<float*>(d))[i]);
+        }
+        return;
+    case 'd':
+        N = data.size() / 8;
+        s.PutU4(uint32_t(N)); // number of elements
+        s.PutU4(0); // no encoding (1 would be zip-compressed)
+        // TODO: compress if large?
+        s.PutU4(uint32_t(data.size())); // data size
+        for (size_t i = 0; i < N; ++i) {
+            s.PutF8((reinterpret_cast<double*>(d))[i]);
+        }
+        return;
+    default:
+        std::ostringstream err;
+        err << "Tried to dump property with invalid type '";
+        err << type << "'!";
+        throw DeadlyExportError(err.str());
+    }
+}
+
+void FBX::Property::DumpAscii(Assimp::StreamWriterLE &outstream, int indent)
+{
+    std::ostringstream ss;
+    ss.imbue(std::locale::classic());
+    ss.precision(15); // this seems to match official FBX SDK exports
+    DumpAscii(ss, indent);
+    outstream.PutString(ss.str());
+}
+
+void FBX::Property::DumpAscii(std::ostream& s, int indent)
+{
+    // no writing type... or anything. just shove it into the stream.
+    uint8_t* d = data.data();
+    size_t N;
+    size_t swap = data.size();
+    size_t count = 0;
+    switch (type) {
+    case 'C':
+        if (*(reinterpret_cast<uint8_t*>(d))) { s << 'T'; }
+        else { s << 'F'; }
+        return;
+    case 'Y': s << *(reinterpret_cast<int16_t*>(d)); return;
+    case 'I': s << *(reinterpret_cast<int32_t*>(d)); return;
+    case 'F': s << *(reinterpret_cast<float*>(d)); return;
+    case 'D': s << *(reinterpret_cast<double*>(d)); return;
+    case 'L': s << *(reinterpret_cast<int64_t*>(d)); return;
+    case 'S':
+        // first search to see if it has "\x00\x01" in it -
+        // which separates fields which are reversed in the ascii version.
+        // yeah.
+        // FBX, yeah.
+        for (size_t i = 0; i < data.size(); ++i) {
+            if (data[i] == '\0') {
+                swap = i;
+                break;
+            }
+        }
+    case 'R':
+        s << '"';
+        // we might as well check this now,
+        // probably it will never happen
+        for (size_t i = 0; i < data.size(); ++i) {
+            char c = data[i];
+            if (c == '"') {
+                throw runtime_error("can't handle quotes in property string");
+            }
+        }
+        // first write the SWAPPED member (if any)
+        for (size_t i = swap + 2; i < data.size(); ++i) {
+            char c = data[i];
+            s << c;
+        }
+        // then a separator
+        if (swap != data.size()) {
+            s << "::";
+        }
+        // then the initial member
+        for (size_t i = 0; i < swap; ++i) {
+            char c = data[i];
+            s << c;
+        }
+        s << '"';
+        return;
+    case 'i':
+        N = data.size() / 4; // number of elements
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<int32_t*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'l':
+        N = data.size() / 8;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<int64_t*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'f':
+        N = data.size() / 4;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<float*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    case 'd':
+        N = data.size() / 8;
+        s << '*' << N << " {\n";
+        for (int i = 0; i < indent + 1; ++i) { s << '\t'; }
+        s << "a: ";
+        // set precision to something that can handle doubles
+        s.precision(15);
+        for (size_t i = 0; i < N; ++i) {
+            if (i > 0) { s << ','; }
+            if (count++ > 120) { s << '\n'; count = 0; }
+            s << (reinterpret_cast<double*>(d))[i];
+        }
+        s << '\n';
+        for (int i = 0; i < indent; ++i) { s << '\t'; }
+        s << "} ";
+        return;
+    default:
+        std::ostringstream err;
+        err << "Tried to dump property with invalid type '";
+        err << type << "'!";
+        throw runtime_error(err.str());
+    }
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 129 - 0
thirdparty/assimp/code/FBXExportProperty.h

@@ -0,0 +1,129 @@
+/*
+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 FBXExportProperty.h
+* Declares the FBX::Property helper class for fbx export.
+*/
+#ifndef AI_FBXEXPORTPROPERTY_H_INC
+#define AI_FBXEXPORTPROPERTY_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+
+#include <assimp/types.h> // aiMatrix4x4
+#include <assimp/StreamWriter.h> // StreamWriterLE
+
+#include <string>
+#include <vector>
+#include <ostream>
+#include <type_traits> // is_void
+
+namespace FBX {
+    class Property;
+}
+
+/** FBX::Property
+ *
+ *  Holds a value of any of FBX's recognized types,
+ *  each represented by a particular one-character code.
+ *  C : 1-byte uint8, usually 0x00 or 0x01 to represent boolean false and true
+ *  Y : 2-byte int16
+ *  I : 4-byte int32
+ *  F : 4-byte float
+ *  D : 8-byte double
+ *  L : 8-byte int64
+ *  i : array of int32
+ *  f : array of float
+ *  d : array of double
+ *  l : array of int64
+ *  b : array of 1-byte booleans (0x00 or 0x01)
+ *  S : string (array of 1-byte char)
+ *  R : raw data (array of bytes)
+ */
+class FBX::Property
+{
+public:
+    // constructors for basic types.
+    // all explicit to avoid accidental typecasting
+    explicit Property(bool v);
+    // TODO: determine if there is actually a byte type,
+    // or if this always means <bool>. 'C' seems to imply <char>,
+    // so possibly the above was intended to represent both.
+    explicit Property(int16_t v);
+    explicit Property(int32_t v);
+    explicit Property(float v);
+    explicit Property(double v);
+    explicit Property(int64_t v);
+    // strings can either be stored as 'R' (raw) or 'S' (string) type
+    explicit Property(const char* c, bool raw=false);
+    explicit Property(const std::string& s, bool raw=false);
+    explicit Property(const std::vector<uint8_t>& r);
+    explicit Property(const std::vector<int32_t>& va);
+    explicit Property(const std::vector<int64_t>& va);
+    explicit Property(const std::vector<double>& va);
+    explicit Property(const std::vector<float>& va);
+    explicit Property(const aiMatrix4x4& vm);
+
+    // this will catch any type not defined above,
+    // so that we don't accidentally convert something we don't want.
+    // for example (const char*) --> (bool)... seriously wtf C++
+    template <class T>
+    explicit Property(T v) : type('X') {
+        static_assert(std::is_void<T>::value, "TRIED TO CREATE FBX PROPERTY WITH UNSUPPORTED TYPE, CHECK YOUR PROPERTY INSTANTIATION");
+    } // note: no line wrap so it appears verbatim on the compiler error
+
+    // the size of this property node in a binary file, in bytes
+    size_t size();
+
+    // write this property node as binary data to the given stream
+    void DumpBinary(Assimp::StreamWriterLE &s);
+    void DumpAscii(Assimp::StreamWriterLE &s, int indent=0);
+    void DumpAscii(std::ostream &s, int indent=0);
+    // note: make sure the ostream is in classic "C" locale
+
+private:
+    char type;
+    std::vector<uint8_t> data;
+};
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXEXPORTPROPERTY_H_INC

+ 2480 - 0
thirdparty/assimp/code/FBXExporter.cpp

@@ -0,0 +1,2480 @@
+/*
+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 ASSIMP_BUILD_NO_EXPORT
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExporter.h"
+#include "FBXExportNode.h"
+#include "FBXExportProperty.h"
+#include "FBXCommon.h"
+
+#include <assimp/version.h> // aiGetVersion
+#include <assimp/IOSystem.hpp>
+#include <assimp/Exporter.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+#include <assimp/material.h> // aiTextureType
+#include <assimp/scene.h>
+#include <assimp/mesh.h>
+
+// Header files, standard library.
+#include <memory> // shared_ptr
+#include <string>
+#include <sstream> // stringstream
+#include <ctime> // localtime, tm_*
+#include <map>
+#include <set>
+#include <vector>
+#include <array>
+#include <unordered_set>
+
+// RESOURCES:
+// https://code.blender.org/2013/08/fbx-binary-file-format-specification/
+// https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
+
+const ai_real DEG = ai_real( 57.29577951308232087679815481 ); // degrees per radian
+
+// some constants that we'll use for writing metadata
+namespace FBX {
+    const std::string EXPORT_VERSION_STR = "7.4.0";
+    const uint32_t EXPORT_VERSION_INT = 7400; // 7.4 == 2014/2015
+    // FBX files have some hashed values that depend on the creation time field,
+    // but for now we don't actually know how to generate these.
+    // what we can do is set them to a known-working version.
+    // this is the data that Blender uses in their FBX export process.
+    const std::string GENERIC_CTIME = "1970-01-01 10:00:00:000";
+    const std::string GENERIC_FILEID =
+        "\x28\xb3\x2a\xeb\xb6\x24\xcc\xc2\xbf\xc8\xb0\x2a\xa9\x2b\xfc\xf1";
+    const std::string GENERIC_FOOTID =
+        "\xfa\xbc\xab\x09\xd0\xc8\xd4\x66\xb1\x76\xfb\x83\x1c\xf7\x26\x7e";
+    const std::string FOOT_MAGIC =
+        "\xf8\x5a\x8c\x6a\xde\xf5\xd9\x7e\xec\xe9\x0c\xe3\x75\x8f\x29\x0b";
+    const std::string COMMENT_UNDERLINE =
+        ";------------------------------------------------------------------";
+}
+
+using namespace Assimp;
+using namespace FBX;
+
+namespace Assimp {
+
+    // ---------------------------------------------------------------------
+    // Worker function for exporting a scene to binary FBX.
+    // Prototyped and registered in Exporter.cpp
+    void ExportSceneFBX (
+        const char* pFile,
+        IOSystem* pIOSystem,
+        const aiScene* pScene,
+        const ExportProperties* pProperties
+    ){
+        // initialize the exporter
+        FBXExporter exporter(pScene, pProperties);
+
+        // perform binary export
+        exporter.ExportBinary(pFile, pIOSystem);
+    }
+
+    // ---------------------------------------------------------------------
+    // Worker function for exporting a scene to ASCII FBX.
+    // Prototyped and registered in Exporter.cpp
+    void ExportSceneFBXA (
+        const char* pFile,
+        IOSystem* pIOSystem,
+        const aiScene* pScene,
+        const ExportProperties* pProperties
+    ){
+        // initialize the exporter
+        FBXExporter exporter(pScene, pProperties);
+
+        // perform ascii export
+        exporter.ExportAscii(pFile, pIOSystem);
+    }
+
+} // end of namespace Assimp
+
+FBXExporter::FBXExporter ( const aiScene* pScene, const ExportProperties* pProperties )
+: binary(false)
+, mScene(pScene)
+, mProperties(pProperties)
+, outfile()
+, connections()
+, mesh_uids()
+, material_uids()
+, node_uids() {
+    // will probably need to determine UIDs, connections, etc here.
+    // basically anything that needs to be known
+    // before we start writing sections to the stream.
+}
+
+void FBXExporter::ExportBinary (
+    const char* pFile,
+    IOSystem* pIOSystem
+){
+    // remember that we're exporting in binary mode
+    binary = true;
+
+    // we're not currently using these preferences,
+    // but clang will cry about it if we never touch it.
+    // TODO: some of these might be relevant to export
+    (void)mProperties;
+
+    // open the indicated file for writing (in binary mode)
+    outfile.reset(pIOSystem->Open(pFile,"wb"));
+    if (!outfile) {
+        throw DeadlyExportError(
+            "could not open output .fbx file: " + std::string(pFile)
+        );
+    }
+
+    // first a binary-specific file header
+    WriteBinaryHeader();
+
+    // the rest of the file is in node entries.
+    // we have to serialize each entry before we write to the output,
+    // as the first thing we write is the byte offset of the _next_ entry.
+    // Either that or we can skip back to write the offset when we finish.
+    WriteAllNodes();
+
+    // finally we have a binary footer to the file
+    WriteBinaryFooter();
+
+    // explicitly release file pointer,
+    // so we don't have to rely on class destruction.
+    outfile.reset();
+}
+
+void FBXExporter::ExportAscii (
+    const char* pFile,
+    IOSystem* pIOSystem
+){
+    // remember that we're exporting in ascii mode
+    binary = false;
+
+    // open the indicated file for writing in text mode
+    outfile.reset(pIOSystem->Open(pFile,"wt"));
+    if (!outfile) {
+        throw DeadlyExportError(
+            "could not open output .fbx file: " + std::string(pFile)
+        );
+    }
+
+    // write the ascii header
+    WriteAsciiHeader();
+
+    // write all the sections
+    WriteAllNodes();
+
+    // make sure the file ends with a newline.
+    // note: if the file is opened in text mode,
+    // this should do the right cross-platform thing.
+    outfile->Write("\n", 1, 1);
+
+    // explicitly release file pointer,
+    // so we don't have to rely on class destruction.
+    outfile.reset();
+}
+
+void FBXExporter::WriteAsciiHeader()
+{
+    // basically just a comment at the top of the file
+    std::stringstream head;
+    head << "; FBX " << EXPORT_VERSION_STR << " project file\n";
+    head << "; Created by the Open Asset Import Library (Assimp)\n";
+    head << "; http://assimp.org\n";
+    head << "; -------------------------------------------------\n";
+    const std::string ascii_header = head.str();
+    outfile->Write(ascii_header.c_str(), ascii_header.size(), 1);
+}
+
+void FBXExporter::WriteAsciiSectionHeader(const std::string& title)
+{
+    StreamWriterLE outstream(outfile);
+    std::stringstream s;
+    s << "\n\n; " << title << '\n';
+    s << FBX::COMMENT_UNDERLINE << "\n";
+    outstream.PutString(s.str());
+}
+
+void FBXExporter::WriteBinaryHeader()
+{
+    // first a specific sequence of 23 bytes, always the same
+    const char binary_header[24] = "Kaydara FBX Binary\x20\x20\x00\x1a\x00";
+    outfile->Write(binary_header, 1, 23);
+
+    // then FBX version number, "multiplied" by 1000, as little-endian uint32.
+    // so 7.3 becomes 7300 == 0x841C0000, 7.4 becomes 7400 == 0xE81C0000, etc
+    {
+        StreamWriterLE outstream(outfile);
+        outstream.PutU4(EXPORT_VERSION_INT);
+    } // StreamWriter destructor writes the data to the file
+
+    // after this the node data starts immediately
+    // (probably with the FBXHEaderExtension node)
+}
+
+void FBXExporter::WriteBinaryFooter()
+{
+    outfile->Write(NULL_RECORD.c_str(), NULL_RECORD.size(), 1);
+
+    outfile->Write(GENERIC_FOOTID.c_str(), GENERIC_FOOTID.size(), 1);
+
+    // here some padding is added for alignment to 16 bytes.
+    // if already aligned, the full 16 bytes is added.
+    size_t pos = outfile->Tell();
+    size_t pad = 16 - (pos % 16);
+    for (size_t i = 0; i < pad; ++i) {
+        outfile->Write("\x00", 1, 1);
+    }
+
+    // not sure what this is, but it seems to always be 0 in modern files
+    for (size_t i = 0; i < 4; ++i) {
+        outfile->Write("\x00", 1, 1);
+    }
+
+    // now the file version again
+    {
+        StreamWriterLE outstream(outfile);
+        outstream.PutU4(EXPORT_VERSION_INT);
+    } // StreamWriter destructor writes the data to the file
+
+    // and finally some binary footer added to all files
+    for (size_t i = 0; i < 120; ++i) {
+        outfile->Write("\x00", 1, 1);
+    }
+    outfile->Write(FOOT_MAGIC.c_str(), FOOT_MAGIC.size(), 1);
+}
+
+void FBXExporter::WriteAllNodes ()
+{
+    // header
+    // (and fileid, creation time, creator, if binary)
+    WriteHeaderExtension();
+
+    // global settings
+    WriteGlobalSettings();
+
+    // documents
+    WriteDocuments();
+
+    // references
+    WriteReferences();
+
+    // definitions
+    WriteDefinitions();
+
+    // objects
+    WriteObjects();
+
+    // connections
+    WriteConnections();
+
+    // WriteTakes? (deprecated since at least 2015 (fbx 7.4))
+}
+
+//FBXHeaderExtension top-level node
+void FBXExporter::WriteHeaderExtension ()
+{
+    if (!binary) {
+        // no title, follows directly from the top comment
+    }
+    FBX::Node n("FBXHeaderExtension");
+    StreamWriterLE outstream(outfile);
+    int indent = 0;
+
+    // begin node
+    n.Begin(outstream, binary, indent);
+
+    // write properties
+    // (none)
+
+    // finish properties
+    n.EndProperties(outstream, binary, indent, 0);
+
+    // begin children
+    n.BeginChildren(outstream, binary, indent);
+
+    indent = 1;
+
+    // write child nodes
+    FBX::Node::WritePropertyNode(
+        "FBXHeaderVersion", int32_t(1003), outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "FBXVersion", int32_t(EXPORT_VERSION_INT), outstream, binary, indent
+    );
+    if (binary) {
+        FBX::Node::WritePropertyNode(
+            "EncryptionType", int32_t(0), outstream, binary, indent
+        );
+    }
+
+    FBX::Node CreationTimeStamp("CreationTimeStamp");
+    time_t rawtime;
+    time(&rawtime);
+    struct tm * now = localtime(&rawtime);
+    CreationTimeStamp.AddChild("Version", int32_t(1000));
+    CreationTimeStamp.AddChild("Year", int32_t(now->tm_year + 1900));
+    CreationTimeStamp.AddChild("Month", int32_t(now->tm_mon + 1));
+    CreationTimeStamp.AddChild("Day", int32_t(now->tm_mday));
+    CreationTimeStamp.AddChild("Hour", int32_t(now->tm_hour));
+    CreationTimeStamp.AddChild("Minute", int32_t(now->tm_min));
+    CreationTimeStamp.AddChild("Second", int32_t(now->tm_sec));
+    CreationTimeStamp.AddChild("Millisecond", int32_t(0));
+    CreationTimeStamp.Dump(outstream, binary, indent);
+
+    std::stringstream creator;
+    creator << "Open Asset Import Library (Assimp) " << aiGetVersionMajor()
+            << "." << aiGetVersionMinor() << "." << aiGetVersionRevision();
+    FBX::Node::WritePropertyNode(
+        "Creator", creator.str(), outstream, binary, indent
+    );
+
+    //FBX::Node sceneinfo("SceneInfo");
+    //sceneinfo.AddProperty("GlobalInfo" + FBX::SEPARATOR + "SceneInfo");
+    // not sure if any of this is actually needed,
+    // so just write an empty node for now.
+    //sceneinfo.Dump(outstream, binary, indent);
+
+    indent = 0;
+
+    // finish node
+    n.End(outstream, binary, indent, true);
+
+    // that's it for FBXHeaderExtension...
+    if (!binary) { return; }
+
+    // but binary files also need top-level FileID, CreationTime, Creator:
+    std::vector<uint8_t> raw(GENERIC_FILEID.size());
+    for (size_t i = 0; i < GENERIC_FILEID.size(); ++i) {
+        raw[i] = uint8_t(GENERIC_FILEID[i]);
+    }
+    FBX::Node::WritePropertyNode(
+        "FileId", raw, outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "CreationTime", GENERIC_CTIME, outstream, binary, indent
+    );
+    FBX::Node::WritePropertyNode(
+        "Creator", creator.str(), outstream, binary, indent
+    );
+}
+
+void FBXExporter::WriteGlobalSettings ()
+{
+    if (!binary) {
+        // no title, follows directly from the header extension
+    }
+    FBX::Node gs("GlobalSettings");
+    gs.AddChild("Version", int32_t(1000));
+
+    FBX::Node p("Properties70");
+    p.AddP70int("UpAxis", 1);
+    p.AddP70int("UpAxisSign", 1);
+    p.AddP70int("FrontAxis", 2);
+    p.AddP70int("FrontAxisSign", 1);
+    p.AddP70int("CoordAxis", 0);
+    p.AddP70int("CoordAxisSign", 1);
+    p.AddP70int("OriginalUpAxis", 1);
+    p.AddP70int("OriginalUpAxisSign", 1);
+    p.AddP70double("UnitScaleFactor", 1.0);
+    p.AddP70double("OriginalUnitScaleFactor", 1.0);
+    p.AddP70color("AmbientColor", 0.0, 0.0, 0.0);
+    p.AddP70string("DefaultCamera", "Producer Perspective");
+    p.AddP70enum("TimeMode", 11);
+    p.AddP70enum("TimeProtocol", 2);
+    p.AddP70enum("SnapOnFrameMode", 0);
+    p.AddP70time("TimeSpanStart", 0); // TODO: animation support
+    p.AddP70time("TimeSpanStop", FBX::SECOND); // TODO: animation support
+    p.AddP70double("CustomFrameRate", -1.0);
+    p.AddP70("TimeMarker", "Compound", "", ""); // not sure what this is
+    p.AddP70int("CurrentTimeMarker", -1);
+    gs.AddChild(p);
+
+    gs.Dump(outfile, binary, 0);
+}
+
+void FBXExporter::WriteDocuments ()
+{
+    if (!binary) {
+        WriteAsciiSectionHeader("Documents Description");
+    }
+    
+    // not sure what the use of multiple documents would be,
+    // or whether any end-application supports it
+    FBX::Node docs("Documents");
+    docs.AddChild("Count", int32_t(1));
+    FBX::Node doc("Document");
+
+    // generate uid
+    int64_t uid = generate_uid();
+    doc.AddProperties(uid, "", "Scene");
+    FBX::Node p("Properties70");
+    p.AddP70("SourceObject", "object", "", ""); // what is this even for?
+    p.AddP70string("ActiveAnimStackName", ""); // should do this properly?
+    doc.AddChild(p);
+
+    // UID for root node in scene hierarchy.
+    // always set to 0 in the case of a single document.
+    // not sure what happens if more than one document exists,
+    // but that won't matter to us as we're exporting a single scene.
+    doc.AddChild("RootNode", int64_t(0));
+
+    docs.AddChild(doc);
+    docs.Dump(outfile, binary, 0);
+}
+
+void FBXExporter::WriteReferences ()
+{
+    if (!binary) {
+        WriteAsciiSectionHeader("Document References");
+    }
+    // always empty for now.
+    // not really sure what this is for.
+    FBX::Node n("References");
+    n.force_has_children = true;
+    n.Dump(outfile, binary, 0);
+}
+
+
+// ---------------------------------------------------------------
+// some internal helper functions used for writing the definitions
+// (before any actual data is written)
+// ---------------------------------------------------------------
+
+size_t count_nodes(const aiNode* n) {
+    size_t count = 1;
+    for (size_t i = 0; i < n->mNumChildren; ++i) {
+        count += count_nodes(n->mChildren[i]);
+    }
+    return count;
+}
+
+bool has_phong_mat(const aiScene* scene)
+{
+    // just search for any material with a shininess exponent
+    for (size_t i = 0; i < scene->mNumMaterials; ++i) {
+        aiMaterial* mat = scene->mMaterials[i];
+        float shininess = 0;
+        mat->Get(AI_MATKEY_SHININESS, shininess);
+        if (shininess > 0) {
+            return true;
+        }
+    }
+    return false;
+}
+
+size_t count_images(const aiScene* scene) {
+    std::unordered_set<std::string> images;
+    aiString texpath;
+    for (size_t i = 0; i < scene->mNumMaterials; ++i) {
+        aiMaterial* mat = scene->mMaterials[i];
+        for (
+            size_t tt = aiTextureType_DIFFUSE;
+            tt < aiTextureType_UNKNOWN;
+            ++tt
+        ){
+            const aiTextureType textype = static_cast<aiTextureType>(tt);
+            const size_t texcount = mat->GetTextureCount(textype);
+            for (unsigned int j = 0; j < texcount; ++j) {
+                mat->GetTexture(textype, j, &texpath);
+                images.insert(std::string(texpath.C_Str()));
+            }
+        }
+    }
+    return images.size();
+}
+
+size_t count_textures(const aiScene* scene) {
+    size_t count = 0;
+    for (size_t i = 0; i < scene->mNumMaterials; ++i) {
+        aiMaterial* mat = scene->mMaterials[i];
+        for (
+            size_t tt = aiTextureType_DIFFUSE;
+            tt < aiTextureType_UNKNOWN;
+            ++tt
+        ){
+            // TODO: handle layered textures
+            if (mat->GetTextureCount(static_cast<aiTextureType>(tt)) > 0) {
+                count += 1;
+            }
+        }
+    }
+    return count;
+}
+
+size_t count_deformers(const aiScene* scene) {
+    size_t count = 0;
+    for (size_t i = 0; i < scene->mNumMeshes; ++i) {
+        const size_t n = scene->mMeshes[i]->mNumBones;
+        if (n) {
+            // 1 main deformer, 1 subdeformer per bone
+            count += n + 1;
+        }
+    }
+    return count;
+}
+
+void FBXExporter::WriteDefinitions ()
+{
+    // basically this is just bookkeeping:
+    // determining how many of each type of object there are
+    // and specifying the base properties to use when otherwise unspecified.
+
+    // ascii section header
+    if (!binary) {
+        WriteAsciiSectionHeader("Object definitions");
+    }
+
+    // we need to count the objects
+    int32_t count;
+    int32_t total_count = 0;
+
+    // and store them
+    std::vector<FBX::Node> object_nodes;
+    FBX::Node n, pt, p;
+
+    // GlobalSettings
+    // this seems to always be here in Maya exports
+    n = FBX::Node("ObjectType", "GlobalSettings");
+    count = 1;
+    n.AddChild("Count", count);
+    object_nodes.push_back(n);
+    total_count += count;
+
+    // AnimationStack / FbxAnimStack
+    // this seems to always be here in Maya exports,
+    // but no harm seems to come of leaving it out.
+    count = mScene->mNumAnimations;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationStack");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxAnimStack");
+        p = FBX::Node("Properties70");
+        p.AddP70string("Description", "");
+        p.AddP70time("LocalStart", 0);
+        p.AddP70time("LocalStop", 0);
+        p.AddP70time("ReferenceStart", 0);
+        p.AddP70time("ReferenceStop", 0);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // AnimationLayer / FbxAnimLayer
+    // this seems to always be here in Maya exports,
+    // but no harm seems to come of leaving it out.
+    // Assimp doesn't support animation layers,
+    // so there will be one per aiAnimation
+    count = mScene->mNumAnimations;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationLayer");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FBXAnimLayer");
+        p = FBX::Node("Properties70");
+        p.AddP70("Weight", "Number", "", "A", double(100));
+        p.AddP70bool("Mute", 0);
+        p.AddP70bool("Solo", 0);
+        p.AddP70bool("Lock", 0);
+        p.AddP70color("Color", 0.8, 0.8, 0.8);
+        p.AddP70("BlendMode", "enum", "", "", int32_t(0));
+        p.AddP70("RotationAccumulationMode", "enum", "", "", int32_t(0));
+        p.AddP70("ScaleAccumulationMode", "enum", "", "", int32_t(0));
+        p.AddP70("BlendModeBypass", "ULongLong", "", "", int64_t(0));
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // NodeAttribute
+    // this is completely absurd.
+    // there can only be one "NodeAttribute" template,
+    // but FbxSkeleton, FbxCamera, FbxLight all are "NodeAttributes".
+    // so if only one exists we should set the template for that,
+    // otherwise... we just pick one :/.
+    // the others have to set all their properties every instance,
+    // because there's no template.
+    count = 1; // TODO: select properly
+    if (count) {
+        // FbxSkeleton
+        n = FBX::Node("ObjectType", "NodeAttribute");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxSkeleton");
+        p = FBX::Node("Properties70");
+        p.AddP70color("Color", 0.8, 0.8, 0.8);
+        p.AddP70double("Size", 33.333333333333);
+        p.AddP70("LimbLength", "double", "Number", "H", double(1));
+        // note: not sure what the "H" flag is for - hidden?
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Model / FbxNode
+    // <~~ node hierarchy
+    count = int32_t(count_nodes(mScene->mRootNode)) - 1; // (not counting root node)
+    if (count) {
+        n = FBX::Node("ObjectType", "Model");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxNode");
+        p = FBX::Node("Properties70");
+        p.AddP70enum("QuaternionInterpolate", 0);
+        p.AddP70vector("RotationOffset", 0.0, 0.0, 0.0);
+        p.AddP70vector("RotationPivot", 0.0, 0.0, 0.0);
+        p.AddP70vector("ScalingOffset", 0.0, 0.0, 0.0);
+        p.AddP70vector("ScalingPivot", 0.0, 0.0, 0.0);
+        p.AddP70bool("TranslationActive", 0);
+        p.AddP70vector("TranslationMin", 0.0, 0.0, 0.0);
+        p.AddP70vector("TranslationMax", 0.0, 0.0, 0.0);
+        p.AddP70bool("TranslationMinX", 0);
+        p.AddP70bool("TranslationMinY", 0);
+        p.AddP70bool("TranslationMinZ", 0);
+        p.AddP70bool("TranslationMaxX", 0);
+        p.AddP70bool("TranslationMaxY", 0);
+        p.AddP70bool("TranslationMaxZ", 0);
+        p.AddP70enum("RotationOrder", 0);
+        p.AddP70bool("RotationSpaceForLimitOnly", 0);
+        p.AddP70double("RotationStiffnessX", 0.0);
+        p.AddP70double("RotationStiffnessY", 0.0);
+        p.AddP70double("RotationStiffnessZ", 0.0);
+        p.AddP70double("AxisLen", 10.0);
+        p.AddP70vector("PreRotation", 0.0, 0.0, 0.0);
+        p.AddP70vector("PostRotation", 0.0, 0.0, 0.0);
+        p.AddP70bool("RotationActive", 0);
+        p.AddP70vector("RotationMin", 0.0, 0.0, 0.0);
+        p.AddP70vector("RotationMax", 0.0, 0.0, 0.0);
+        p.AddP70bool("RotationMinX", 0);
+        p.AddP70bool("RotationMinY", 0);
+        p.AddP70bool("RotationMinZ", 0);
+        p.AddP70bool("RotationMaxX", 0);
+        p.AddP70bool("RotationMaxY", 0);
+        p.AddP70bool("RotationMaxZ", 0);
+        p.AddP70enum("InheritType", 0);
+        p.AddP70bool("ScalingActive", 0);
+        p.AddP70vector("ScalingMin", 0.0, 0.0, 0.0);
+        p.AddP70vector("ScalingMax", 1.0, 1.0, 1.0);
+        p.AddP70bool("ScalingMinX", 0);
+        p.AddP70bool("ScalingMinY", 0);
+        p.AddP70bool("ScalingMinZ", 0);
+        p.AddP70bool("ScalingMaxX", 0);
+        p.AddP70bool("ScalingMaxY", 0);
+        p.AddP70bool("ScalingMaxZ", 0);
+        p.AddP70vector("GeometricTranslation", 0.0, 0.0, 0.0);
+        p.AddP70vector("GeometricRotation", 0.0, 0.0, 0.0);
+        p.AddP70vector("GeometricScaling", 1.0, 1.0, 1.0);
+        p.AddP70double("MinDampRangeX", 0.0);
+        p.AddP70double("MinDampRangeY", 0.0);
+        p.AddP70double("MinDampRangeZ", 0.0);
+        p.AddP70double("MaxDampRangeX", 0.0);
+        p.AddP70double("MaxDampRangeY", 0.0);
+        p.AddP70double("MaxDampRangeZ", 0.0);
+        p.AddP70double("MinDampStrengthX", 0.0);
+        p.AddP70double("MinDampStrengthY", 0.0);
+        p.AddP70double("MinDampStrengthZ", 0.0);
+        p.AddP70double("MaxDampStrengthX", 0.0);
+        p.AddP70double("MaxDampStrengthY", 0.0);
+        p.AddP70double("MaxDampStrengthZ", 0.0);
+        p.AddP70double("PreferedAngleX", 0.0);
+        p.AddP70double("PreferedAngleY", 0.0);
+        p.AddP70double("PreferedAngleZ", 0.0);
+        p.AddP70("LookAtProperty", "object", "", "");
+        p.AddP70("UpVectorProperty", "object", "", "");
+        p.AddP70bool("Show", 1);
+        p.AddP70bool("NegativePercentShapeSupport", 1);
+        p.AddP70int("DefaultAttributeIndex", -1);
+        p.AddP70bool("Freeze", 0);
+        p.AddP70bool("LODBox", 0);
+        p.AddP70(
+            "Lcl Translation", "Lcl Translation", "", "A",
+            double(0), double(0), double(0)
+        );
+        p.AddP70(
+            "Lcl Rotation", "Lcl Rotation", "", "A",
+            double(0), double(0), double(0)
+        );
+        p.AddP70(
+            "Lcl Scaling", "Lcl Scaling", "", "A",
+            double(1), double(1), double(1)
+        );
+        p.AddP70("Visibility", "Visibility", "", "A", double(1));
+        p.AddP70(
+            "Visibility Inheritance", "Visibility Inheritance", "", "",
+            int32_t(1)
+        );
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Geometry / FbxMesh
+    // <~~ aiMesh
+    count = mScene->mNumMeshes;
+    if (count) {
+        n = FBX::Node("ObjectType", "Geometry");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxMesh");
+        p = FBX::Node("Properties70");
+        p.AddP70color("Color", 0, 0, 0);
+        p.AddP70vector("BBoxMin", 0, 0, 0);
+        p.AddP70vector("BBoxMax", 0, 0, 0);
+        p.AddP70bool("Primary Visibility", 1);
+        p.AddP70bool("Casts Shadows", 1);
+        p.AddP70bool("Receive Shadows", 1);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Material / FbxSurfacePhong, FbxSurfaceLambert, FbxSurfaceMaterial
+    // <~~ aiMaterial
+    // basically if there's any phong material this is defined as phong,
+    // and otherwise lambert.
+    // More complex materials cause a bare-bones FbxSurfaceMaterial definition
+    // and are treated specially, as they're not really supported by FBX.
+    // TODO: support Maya's Stingray PBS material
+    count = mScene->mNumMaterials;
+    if (count) {
+        bool has_phong = has_phong_mat(mScene);
+        n = FBX::Node("ObjectType", "Material");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate");
+        if (has_phong) {
+            pt.AddProperty("FbxSurfacePhong");
+        } else {
+            pt.AddProperty("FbxSurfaceLambert");
+        }
+        p = FBX::Node("Properties70");
+        if (has_phong) {
+            p.AddP70string("ShadingModel", "Phong");
+        } else {
+            p.AddP70string("ShadingModel", "Lambert");
+        }
+        p.AddP70bool("MultiLayer", 0);
+        p.AddP70colorA("EmissiveColor", 0.0, 0.0, 0.0);
+        p.AddP70numberA("EmissiveFactor", 1.0);
+        p.AddP70colorA("AmbientColor", 0.2, 0.2, 0.2);
+        p.AddP70numberA("AmbientFactor", 1.0);
+        p.AddP70colorA("DiffuseColor", 0.8, 0.8, 0.8);
+        p.AddP70numberA("DiffuseFactor", 1.0);
+        p.AddP70vector("Bump", 0.0, 0.0, 0.0);
+        p.AddP70vector("NormalMap", 0.0, 0.0, 0.0);
+        p.AddP70double("BumpFactor", 1.0);
+        p.AddP70colorA("TransparentColor", 0.0, 0.0, 0.0);
+        p.AddP70numberA("TransparencyFactor", 0.0);
+        p.AddP70color("DisplacementColor", 0.0, 0.0, 0.0);
+        p.AddP70double("DisplacementFactor", 1.0);
+        p.AddP70color("VectorDisplacementColor", 0.0, 0.0, 0.0);
+        p.AddP70double("VectorDisplacementFactor", 1.0);
+        if (has_phong) {
+            p.AddP70colorA("SpecularColor", 0.2, 0.2, 0.2);
+            p.AddP70numberA("SpecularFactor", 1.0);
+            p.AddP70numberA("ShininessExponent", 20.0);
+            p.AddP70colorA("ReflectionColor", 0.0, 0.0, 0.0);
+            p.AddP70numberA("ReflectionFactor", 1.0);
+        }
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Video / FbxVideo
+    // one for each image file.
+    count = int32_t(count_images(mScene));
+    if (count) {
+        n = FBX::Node("ObjectType", "Video");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxVideo");
+        p = FBX::Node("Properties70");
+        p.AddP70bool("ImageSequence", 0);
+        p.AddP70int("ImageSequenceOffset", 0);
+        p.AddP70double("FrameRate", 0.0);
+        p.AddP70int("LastFrame", 0);
+        p.AddP70int("Width", 0);
+        p.AddP70int("Height", 0);
+        p.AddP70("Path", "KString", "XRefUrl", "", "");
+        p.AddP70int("StartFrame", 0);
+        p.AddP70int("StopFrame", 0);
+        p.AddP70double("PlaySpeed", 0.0);
+        p.AddP70time("Offset", 0);
+        p.AddP70enum("InterlaceMode", 0);
+        p.AddP70bool("FreeRunning", 0);
+        p.AddP70bool("Loop", 0);
+        p.AddP70enum("AccessMode", 0);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Texture / FbxFileTexture
+    // <~~ aiTexture
+    count = int32_t(count_textures(mScene));
+    if (count) {
+        n = FBX::Node("ObjectType", "Texture");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxFileTexture");
+        p = FBX::Node("Properties70");
+        p.AddP70enum("TextureTypeUse", 0);
+        p.AddP70numberA("Texture alpha", 1.0);
+        p.AddP70enum("CurrentMappingType", 0);
+        p.AddP70enum("WrapModeU", 0);
+        p.AddP70enum("WrapModeV", 0);
+        p.AddP70bool("UVSwap", 0);
+        p.AddP70bool("PremultiplyAlpha", 1);
+        p.AddP70vectorA("Translation", 0.0, 0.0, 0.0);
+        p.AddP70vectorA("Rotation", 0.0, 0.0, 0.0);
+        p.AddP70vectorA("Scaling", 1.0, 1.0, 1.0);
+        p.AddP70vector("TextureRotationPivot", 0.0, 0.0, 0.0);
+        p.AddP70vector("TextureScalingPivot", 0.0, 0.0, 0.0);
+        p.AddP70enum("CurrentTextureBlendMode", 1);
+        p.AddP70string("UVSet", "default");
+        p.AddP70bool("UseMaterial", 0);
+        p.AddP70bool("UseMipMap", 0);
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // AnimationCurveNode / FbxAnimCurveNode
+    count = mScene->mNumAnimations * 3;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationCurveNode");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "FbxAnimCurveNode");
+        p = FBX::Node("Properties70");
+        p.AddP70("d", "Compound", "", "");
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // AnimationCurve / FbxAnimCurve
+    count = mScene->mNumAnimations * 9;
+    if (count) {
+        n = FBX::Node("ObjectType", "AnimationCurve");
+        n.AddChild("Count", count);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Pose
+    count = 0;
+    for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
+        aiMesh* mesh = mScene->mMeshes[i];
+        if (mesh->HasBones()) { ++count; }
+    }
+    if (count) {
+        n = FBX::Node("ObjectType", "Pose");
+        n.AddChild("Count", count);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // Deformer
+    count = int32_t(count_deformers(mScene));
+    if (count) {
+        n = FBX::Node("ObjectType", "Deformer");
+        n.AddChild("Count", count);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // (template)
+    count = 0;
+    if (count) {
+        n = FBX::Node("ObjectType", "");
+        n.AddChild("Count", count);
+        pt = FBX::Node("PropertyTemplate", "");
+        p = FBX::Node("Properties70");
+        pt.AddChild(p);
+        n.AddChild(pt);
+        object_nodes.push_back(n);
+        total_count += count;
+    }
+
+    // now write it all
+    FBX::Node defs("Definitions");
+    defs.AddChild("Version", int32_t(100));
+    defs.AddChild("Count", int32_t(total_count));
+    for (auto &n : object_nodes) { defs.AddChild(n); }
+    defs.Dump(outfile, binary, 0);
+}
+
+
+// -------------------------------------------------------------------
+// some internal helper functions used for writing the objects section
+// (which holds the actual data)
+// -------------------------------------------------------------------
+
+aiNode* get_node_for_mesh(unsigned int meshIndex, aiNode* node)
+{
+    for (size_t i = 0; i < node->mNumMeshes; ++i) {
+        if (node->mMeshes[i] == meshIndex) {
+            return node;
+        }
+    }
+    for (size_t i = 0; i < node->mNumChildren; ++i) {
+        aiNode* ret = get_node_for_mesh(meshIndex, node->mChildren[i]);
+        if (ret) { return ret; }
+    }
+    return nullptr;
+}
+
+aiMatrix4x4 get_world_transform(const aiNode* node, const aiScene* scene)
+{
+    std::vector<const aiNode*> node_chain;
+    while (node != scene->mRootNode) {
+        node_chain.push_back(node);
+        node = node->mParent;
+    }
+    aiMatrix4x4 transform;
+    for (auto n = node_chain.rbegin(); n != node_chain.rend(); ++n) {
+        transform *= (*n)->mTransformation;
+    }
+    return transform;
+}
+
+int64_t to_ktime(double ticks, const aiAnimation* anim) {
+    if (anim->mTicksPerSecond <= 0) {
+        return static_cast<int64_t>(ticks) * FBX::SECOND;
+    }
+    return (static_cast<int64_t>(ticks) / static_cast<int64_t>(anim->mTicksPerSecond)) * FBX::SECOND;
+}
+
+int64_t to_ktime(double time) {
+    return (static_cast<int64_t>(time * FBX::SECOND));
+}
+
+void FBXExporter::WriteObjects ()
+{
+    if (!binary) {
+        WriteAsciiSectionHeader("Object properties");
+    }
+    // numbers should match those given in definitions! make sure to check
+    StreamWriterLE outstream(outfile);
+    FBX::Node object_node("Objects");
+    int indent = 0;
+    object_node.Begin(outstream, binary, indent);
+    object_node.EndProperties(outstream, binary, indent);
+    object_node.BeginChildren(outstream, binary, indent);
+
+    // geometry (aiMesh)
+    mesh_uids.clear();
+    indent = 1;
+    for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        // it's all about this mesh
+        aiMesh* m = mScene->mMeshes[mi];
+
+        // start the node record
+        FBX::Node n("Geometry");
+        int64_t uid = generate_uid();
+        mesh_uids.push_back(uid);
+        n.AddProperty(uid);
+        n.AddProperty(FBX::SEPARATOR + "Geometry");
+        n.AddProperty("Mesh");
+        n.Begin(outstream, binary, indent);
+        n.DumpProperties(outstream, binary, indent);
+        n.EndProperties(outstream, binary, indent);
+        n.BeginChildren(outstream, binary, indent);
+        indent = 2;
+
+        // output vertex data - each vertex should be unique (probably)
+        std::vector<double> flattened_vertices;
+        // index of original vertex in vertex data vector
+        std::vector<int32_t> vertex_indices;
+        // map of vertex value to its index in the data vector
+        std::map<aiVector3D,size_t> index_by_vertex_value;
+        int32_t index = 0;
+        for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
+            aiVector3D vtx = m->mVertices[vi];
+            auto elem = index_by_vertex_value.find(vtx);
+            if (elem == index_by_vertex_value.end()) {
+                vertex_indices.push_back(index);
+                index_by_vertex_value[vtx] = index;
+                flattened_vertices.push_back(vtx[0]);
+                flattened_vertices.push_back(vtx[1]);
+                flattened_vertices.push_back(vtx[2]);
+                ++index;
+            } else {
+                vertex_indices.push_back(int32_t(elem->second));
+            }
+        }
+        FBX::Node::WritePropertyNode(
+            "Vertices", flattened_vertices, outstream, binary, indent
+        );
+
+        // output polygon data as a flattened array of vertex indices.
+        // the last vertex index of each polygon is negated and - 1
+        std::vector<int32_t> polygon_data;
+        for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
+            const aiFace &f = m->mFaces[fi];
+            for (size_t pvi = 0; pvi < f.mNumIndices - 1; ++pvi) {
+                polygon_data.push_back(vertex_indices[f.mIndices[pvi]]);
+            }
+            polygon_data.push_back(
+                -1 - vertex_indices[f.mIndices[f.mNumIndices-1]]
+            );
+        }
+        FBX::Node::WritePropertyNode(
+            "PolygonVertexIndex", polygon_data, outstream, binary, indent
+        );
+
+        // here could be edges but they're insane.
+        // it's optional anyway, so let's ignore it.
+
+        FBX::Node::WritePropertyNode(
+            "GeometryVersion", int32_t(124), outstream, binary, indent
+        );
+
+        // normals, if any
+        if (m->HasNormals()) {
+            FBX::Node normals("LayerElementNormal", int32_t(0));
+            normals.Begin(outstream, binary, indent);
+            normals.DumpProperties(outstream, binary, indent);
+            normals.EndProperties(outstream, binary, indent);
+            normals.BeginChildren(outstream, binary, indent);
+            indent = 3;
+            FBX::Node::WritePropertyNode(
+                "Version", int32_t(101), outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "Name", "", outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "MappingInformationType", "ByPolygonVertex",
+                outstream, binary, indent
+            );
+            // TODO: vertex-normals or indexed normals when appropriate
+            FBX::Node::WritePropertyNode(
+                "ReferenceInformationType", "Direct",
+                outstream, binary, indent
+            );
+            std::vector<double> normal_data;
+            normal_data.reserve(3 * polygon_data.size());
+            for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
+                const aiFace &f = m->mFaces[fi];
+                for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
+                    const aiVector3D &n = m->mNormals[f.mIndices[pvi]];
+                    normal_data.push_back(n.x);
+                    normal_data.push_back(n.y);
+                    normal_data.push_back(n.z);
+                }
+            }
+            FBX::Node::WritePropertyNode(
+                "Normals", normal_data, outstream, binary, indent
+            );
+            // note: version 102 has a NormalsW also... not sure what it is,
+            // so we can stick with version 101 for now.
+            indent = 2;
+            normals.End(outstream, binary, indent, true);
+        }
+
+        // uvs, if any
+        for (size_t uvi = 0; uvi < m->GetNumUVChannels(); ++uvi) {
+            if (m->mNumUVComponents[uvi] > 2) {
+                // FBX only supports 2-channel UV maps...
+                // or at least i'm not sure how to indicate a different number
+                std::stringstream err;
+                err << "Only 2-channel UV maps supported by FBX,";
+                err << " but mesh " << mi;
+                if (m->mName.length) {
+                    err << " (" << m->mName.C_Str() << ")";
+                }
+                err << " UV map " << uvi;
+                err << " has " << m->mNumUVComponents[uvi];
+                err << " components! Data will be preserved,";
+                err << " but may be incorrectly interpreted on load.";
+                ASSIMP_LOG_WARN(err.str());
+            }
+            FBX::Node uv("LayerElementUV", int32_t(uvi));
+            uv.Begin(outstream, binary, indent);
+            uv.DumpProperties(outstream, binary, indent);
+            uv.EndProperties(outstream, binary, indent);
+            uv.BeginChildren(outstream, binary, indent);
+            indent = 3;
+            FBX::Node::WritePropertyNode(
+                "Version", int32_t(101), outstream, binary, indent
+            );
+            // it doesn't seem like assimp keeps the uv map name,
+            // so just leave it blank.
+            FBX::Node::WritePropertyNode(
+                "Name", "", outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "MappingInformationType", "ByPolygonVertex",
+                outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "ReferenceInformationType", "IndexToDirect",
+                outstream, binary, indent
+            );
+
+            std::vector<double> uv_data;
+            std::vector<int32_t> uv_indices;
+            std::map<aiVector3D,int32_t> index_by_uv;
+            int32_t index = 0;
+            for (size_t fi = 0; fi < m->mNumFaces; ++fi) {
+                const aiFace &f = m->mFaces[fi];
+                for (size_t pvi = 0; pvi < f.mNumIndices; ++pvi) {
+                    const aiVector3D &uv =
+                        m->mTextureCoords[uvi][f.mIndices[pvi]];
+                    auto elem = index_by_uv.find(uv);
+                    if (elem == index_by_uv.end()) {
+                        index_by_uv[uv] = index;
+                        uv_indices.push_back(index);
+                        for (unsigned int x = 0; x < m->mNumUVComponents[uvi]; ++x) {
+                            uv_data.push_back(uv[x]);
+                        }
+                        ++index;
+                    } else {
+                        uv_indices.push_back(elem->second);
+                    }
+                }
+            }
+            FBX::Node::WritePropertyNode(
+                "UV", uv_data, outstream, binary, indent
+            );
+            FBX::Node::WritePropertyNode(
+                "UVIndex", uv_indices, outstream, binary, indent
+            );
+            indent = 2;
+            uv.End(outstream, binary, indent, true);
+        }
+
+        // i'm not really sure why this material section exists,
+        // as the material is linked via "Connections".
+        // it seems to always have the same "0" value.
+        FBX::Node mat("LayerElementMaterial", int32_t(0));
+        mat.AddChild("Version", int32_t(101));
+        mat.AddChild("Name", "");
+        mat.AddChild("MappingInformationType", "AllSame");
+        mat.AddChild("ReferenceInformationType", "IndexToDirect");
+        std::vector<int32_t> mat_indices = {0};
+        mat.AddChild("Materials", mat_indices);
+        mat.Dump(outstream, binary, indent);
+
+        // finally we have the layer specifications,
+        // which select the normals / UV set / etc to use.
+        // TODO: handle multiple uv sets correctly?
+        FBX::Node layer("Layer", int32_t(0));
+        layer.AddChild("Version", int32_t(100));
+        FBX::Node le("LayerElement");
+        le.AddChild("Type", "LayerElementNormal");
+        le.AddChild("TypedIndex", int32_t(0));
+        layer.AddChild(le);
+        le = FBX::Node("LayerElement");
+        le.AddChild("Type", "LayerElementMaterial");
+        le.AddChild("TypedIndex", int32_t(0));
+        layer.AddChild(le);
+        le = FBX::Node("LayerElement");
+        le.AddChild("Type", "LayerElementUV");
+        le.AddChild("TypedIndex", int32_t(0));
+        layer.AddChild(le);
+        layer.Dump(outstream, binary, indent);
+
+        // finish the node record
+        indent = 1;
+        n.End(outstream, binary, indent, true);
+    }
+
+    // aiMaterial
+    material_uids.clear();
+    for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
+        // it's all about this material
+        aiMaterial* m = mScene->mMaterials[i];
+
+        // these are used to receive material data
+        float f; aiColor3D c;
+
+        // start the node record
+        FBX::Node n("Material");
+
+        int64_t uid = generate_uid();
+        material_uids.push_back(uid);
+        n.AddProperty(uid);
+
+        aiString name;
+        m->Get(AI_MATKEY_NAME, name);
+        n.AddProperty(name.C_Str() + FBX::SEPARATOR + "Material");
+
+        n.AddProperty("");
+
+        n.AddChild("Version", int32_t(102));
+        f = 0;
+        m->Get(AI_MATKEY_SHININESS, f);
+        bool phong = (f > 0);
+        if (phong) {
+            n.AddChild("ShadingModel", "phong");
+        } else {
+            n.AddChild("ShadingModel", "lambert");
+        }
+        n.AddChild("MultiLayer", int32_t(0));
+
+        FBX::Node p("Properties70");
+
+        // materials exported using the FBX SDK have two sets of fields.
+        // there are the properties specified in the PropertyTemplate,
+        // which are those supported by the modernFBX SDK,
+        // and an extra set of properties with simpler names.
+        // The extra properties are a legacy material system from pre-2009.
+        //
+        // In the modern system, each property has "color" and "factor".
+        // Generally the interpretation of these seems to be
+        // that the colour is multiplied by the factor before use,
+        // but this is not always clear-cut.
+        //
+        // Usually assimp only stores the colour,
+        // so we can just leave the factors at the default "1.0".
+
+        // first we can export the "standard" properties
+        if (m->Get(AI_MATKEY_COLOR_AMBIENT, c) == aiReturn_SUCCESS) {
+            p.AddP70colorA("AmbientColor", c.r, c.g, c.b);
+            //p.AddP70numberA("AmbientFactor", 1.0);
+        }
+        if (m->Get(AI_MATKEY_COLOR_DIFFUSE, c) == aiReturn_SUCCESS) {
+            p.AddP70colorA("DiffuseColor", c.r, c.g, c.b);
+            //p.AddP70numberA("DiffuseFactor", 1.0);
+        }
+        if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
+            // "TransparentColor" / "TransparencyFactor"...
+            // thanks FBX, for your insightful interpretation of consistency
+            p.AddP70colorA("TransparentColor", c.r, c.g, c.b);
+            // TransparencyFactor defaults to 0.0, so set it to 1.0.
+            // note: Maya always sets this to 1.0,
+            // so we can't use it sensibly as "Opacity".
+            // In stead we rely on the legacy "Opacity" value, below.
+            // Blender also relies on "Opacity" not "TransparencyFactor",
+            // probably for a similar reason.
+            p.AddP70numberA("TransparencyFactor", 1.0);
+        }
+        if (m->Get(AI_MATKEY_COLOR_REFLECTIVE, c) == aiReturn_SUCCESS) {
+            p.AddP70colorA("ReflectionColor", c.r, c.g, c.b);
+        }
+        if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
+            p.AddP70numberA("ReflectionFactor", f);
+        }
+        if (phong) {
+            if (m->Get(AI_MATKEY_COLOR_SPECULAR, c) == aiReturn_SUCCESS) {
+                p.AddP70colorA("SpecularColor", c.r, c.g, c.b);
+            }
+            if (m->Get(AI_MATKEY_SHININESS_STRENGTH, f) == aiReturn_SUCCESS) {
+                p.AddP70numberA("ShininessFactor", f);
+            }
+            if (m->Get(AI_MATKEY_SHININESS, f) == aiReturn_SUCCESS) {
+                p.AddP70numberA("ShininessExponent", f);
+            }
+            if (m->Get(AI_MATKEY_REFLECTIVITY, f) == aiReturn_SUCCESS) {
+                p.AddP70numberA("ReflectionFactor", f);
+            }
+        }
+
+        // Now the legacy system.
+        // For safety let's include it.
+        // thrse values don't exist in the property template,
+        // and usually are completely ignored when loading.
+        // One notable exception is the "Opacity" property,
+        // which Blender uses as (1.0 - alpha).
+        c.r = 0.0f; c.g = 0.0f; c.b = 0.0f;
+        m->Get(AI_MATKEY_COLOR_EMISSIVE, c);
+        p.AddP70vector("Emissive", c.r, c.g, c.b);
+        c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
+        m->Get(AI_MATKEY_COLOR_AMBIENT, c);
+        p.AddP70vector("Ambient", c.r, c.g, c.b);
+        c.r = 0.8f; c.g = 0.8f; c.b = 0.8f;
+        m->Get(AI_MATKEY_COLOR_DIFFUSE, c);
+        p.AddP70vector("Diffuse", c.r, c.g, c.b);
+        // The FBX SDK determines "Opacity" from transparency colour (RGB)
+        // and factor (F) as: O = (1.0 - F * ((R + G + B) / 3)).
+        // However we actually have an opacity value,
+        // so we should take it from AI_MATKEY_OPACITY if possible.
+        // It might make more sense to use TransparencyFactor,
+        // but Blender actually loads "Opacity" correctly, so let's use it.
+        f = 1.0f;
+        if (m->Get(AI_MATKEY_COLOR_TRANSPARENT, c) == aiReturn_SUCCESS) {
+            f = 1.0f - ((c.r + c.g + c.b) / 3.0f);
+        }
+        m->Get(AI_MATKEY_OPACITY, f);
+        p.AddP70double("Opacity", f);
+        if (phong) {
+            // specular color is multiplied by shininess_strength
+            c.r = 0.2f; c.g = 0.2f; c.b = 0.2f;
+            m->Get(AI_MATKEY_COLOR_SPECULAR, c);
+            f = 1.0f;
+            m->Get(AI_MATKEY_SHININESS_STRENGTH, f);
+            p.AddP70vector("Specular", f*c.r, f*c.g, f*c.b);
+            f = 20.0f;
+            m->Get(AI_MATKEY_SHININESS, f);
+            p.AddP70double("Shininess", f);
+            // Legacy "Reflectivity" is F*F*((R+G+B)/3),
+            // where F is the proportion of light reflected (AKA reflectivity),
+            // and RGB is the reflective colour of the material.
+            // No idea why, but we might as well set it the same way.
+            f = 0.0f;
+            m->Get(AI_MATKEY_REFLECTIVITY, f);
+            c.r = 1.0f, c.g = 1.0f, c.b = 1.0f;
+            m->Get(AI_MATKEY_COLOR_REFLECTIVE, c);
+            p.AddP70double("Reflectivity", f*f*((c.r+c.g+c.b)/3.0));
+        }
+
+        n.AddChild(p);
+
+        n.Dump(outstream, binary, indent);
+    }
+
+    // we need to look up all the images we're using,
+    // so we can generate uids, and eliminate duplicates.
+    std::map<std::string, int64_t> uid_by_image;
+    for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
+        aiString texpath;
+        aiMaterial* mat = mScene->mMaterials[i];
+        for (
+            size_t tt = aiTextureType_DIFFUSE;
+            tt < aiTextureType_UNKNOWN;
+            ++tt
+        ){
+            const aiTextureType textype = static_cast<aiTextureType>(tt);
+            const size_t texcount = mat->GetTextureCount(textype);
+            for (size_t j = 0; j < texcount; ++j) {
+                mat->GetTexture(textype, (unsigned int)j, &texpath);
+                const std::string texstring = texpath.C_Str();
+                auto elem = uid_by_image.find(texstring);
+                if (elem == uid_by_image.end()) {
+                    uid_by_image[texstring] = generate_uid();
+                }
+            }
+        }
+    }
+
+    // FbxVideo - stores images used by textures.
+    for (const auto &it : uid_by_image) {
+        if (it.first.compare(0, 1, "*") == 0) {
+            // TODO: embedded textures
+            continue;
+        }
+        FBX::Node n("Video");
+        const int64_t& uid = it.second;
+        const std::string name = ""; // TODO: ... name???
+        n.AddProperties(uid, name + FBX::SEPARATOR + "Video", "Clip");
+        n.AddChild("Type", "Clip");
+        FBX::Node p("Properties70");
+        // TODO: get full path... relative path... etc... ugh...
+        // for now just use the same path for everything,
+        // and hopefully one of them will work out.
+        const std::string& path = it.first;
+        p.AddP70("Path", "KString", "XRefUrl", "", path);
+        n.AddChild(p);
+        n.AddChild("UseMipMap", int32_t(0));
+        n.AddChild("Filename", path);
+        n.AddChild("RelativeFilename", path);
+        n.Dump(outstream, binary, indent);
+    }
+
+    // Textures
+    // referenced by material_index/texture_type pairs.
+    std::map<std::pair<size_t,size_t>,int64_t> texture_uids;
+    const std::map<aiTextureType,std::string> prop_name_by_tt = {
+        {aiTextureType_DIFFUSE, "DiffuseColor"},
+        {aiTextureType_SPECULAR, "SpecularColor"},
+        {aiTextureType_AMBIENT, "AmbientColor"},
+        {aiTextureType_EMISSIVE, "EmissiveColor"},
+        {aiTextureType_HEIGHT, "Bump"},
+        {aiTextureType_NORMALS, "NormalMap"},
+        {aiTextureType_SHININESS, "ShininessExponent"},
+        {aiTextureType_OPACITY, "TransparentColor"},
+        {aiTextureType_DISPLACEMENT, "DisplacementColor"},
+        //{aiTextureType_LIGHTMAP, "???"},
+        {aiTextureType_REFLECTION, "ReflectionColor"}
+        //{aiTextureType_UNKNOWN, ""}
+    };
+    for (size_t i = 0; i < mScene->mNumMaterials; ++i) {
+        // textures are attached to materials
+        aiMaterial* mat = mScene->mMaterials[i];
+        int64_t material_uid = material_uids[i];
+
+        for (
+            size_t j = aiTextureType_DIFFUSE;
+            j < aiTextureType_UNKNOWN;
+            ++j
+        ) {
+            const aiTextureType tt = static_cast<aiTextureType>(j);
+            size_t n = mat->GetTextureCount(tt);
+
+            if (n < 1) { // no texture of this type
+                continue;
+            }
+
+            if (n > 1) {
+                // TODO: multilayer textures
+                std::stringstream err;
+                err << "Multilayer textures not supported (for now),";
+                err << " skipping texture type " << j;
+                err << " of material " << i;
+                ASSIMP_LOG_WARN(err.str());
+            }
+
+            // get image path for this (single-image) texture
+            aiString tpath;
+            if (mat->GetTexture(tt, 0, &tpath) != aiReturn_SUCCESS) {
+                std::stringstream err;
+                err << "Failed to get texture 0 for texture of type " << tt;
+                err << " on material " << i;
+                err << ", however GetTextureCount returned 1.";
+                throw DeadlyExportError(err.str());
+            }
+            const std::string texture_path(tpath.C_Str());
+
+            // get connected image uid
+            auto elem = uid_by_image.find(texture_path);
+            if (elem == uid_by_image.end()) {
+                // this should never happen
+                std::stringstream err;
+                err << "Failed to find video element for texture with path";
+                err << " \"" << texture_path << "\"";
+                err << ", type " << j << ", material " << i;
+                throw DeadlyExportError(err.str());
+            }
+            const int64_t image_uid = elem->second;
+
+            // get the name of the material property to connect to
+            auto elem2 = prop_name_by_tt.find(tt);
+            if (elem2 == prop_name_by_tt.end()) {
+                // don't know how to handle this type of texture,
+                // so skip it.
+                std::stringstream err;
+                err << "Not sure how to handle texture of type " << j;
+                err << " on material " << i;
+                err << ", skipping...";
+                ASSIMP_LOG_WARN(err.str());
+                continue;
+            }
+            const std::string& prop_name = elem2->second;
+
+            // generate a uid for this texture
+            const int64_t texture_uid = generate_uid();
+
+            // link the texture to the material
+            connections.emplace_back(
+                "C", "OP", texture_uid, material_uid, prop_name
+            );
+
+            // link the image data to the texture
+            connections.emplace_back("C", "OO", image_uid, texture_uid);
+
+            // now write the actual texture node
+            FBX::Node tnode("Texture");
+            // TODO: some way to determine texture name?
+            const std::string texture_name = "" + FBX::SEPARATOR + "Texture";
+            tnode.AddProperties(texture_uid, texture_name, "");
+            // there really doesn't seem to be a better type than this:
+            tnode.AddChild("Type", "TextureVideoClip");
+            tnode.AddChild("Version", int32_t(202));
+            tnode.AddChild("TextureName", texture_name);
+            FBX::Node p("Properties70");
+            p.AddP70enum("CurrentTextureBlendMode", 0); // TODO: verify
+            //p.AddP70string("UVSet", ""); // TODO: how should this work?
+            p.AddP70bool("UseMaterial", 1);
+            tnode.AddChild(p);
+            // can't easily detrmine which texture path will be correct,
+            // so just store what we have in every field.
+            // these being incorrect is a common problem with FBX anyway.
+            tnode.AddChild("FileName", texture_path);
+            tnode.AddChild("RelativeFilename", texture_path);
+            tnode.AddChild("ModelUVTranslation", double(0.0), double(0.0));
+            tnode.AddChild("ModelUVScaling", double(1.0), double(1.0));
+            tnode.AddChild("Texture_Alpha_Source", "None");
+            tnode.AddChild(
+                "Cropping", int32_t(0), int32_t(0), int32_t(0), int32_t(0)
+            );
+            tnode.Dump(outstream, binary, indent);
+        }
+    }
+
+    // bones.
+    //
+    // output structure:
+    // subset of node hierarchy that are "skeleton",
+    // i.e. do not have meshes but only bones.
+    // but.. i'm not sure how anyone could guarantee that...
+    //
+    // input...
+    // well, for each mesh it has "bones",
+    // and the bone names correspond to nodes.
+    // of course we also need the parent nodes,
+    // as they give some of the transform........
+    //
+    // well. we can assume a sane input, i suppose.
+    //
+    // so input is the bone node hierarchy,
+    // with an extra thing for the transformation of the MESH in BONE space.
+    //
+    // output is a set of bone nodes,
+    // a "bindpose" which indicates the default local transform of all bones,
+    // and a set of "deformers".
+    // each deformer is parented to a mesh geometry,
+    // and has one or more "subdeformer"s as children.
+    // each subdeformer has one bone node as a child,
+    // and represents the influence of that bone on the grandparent mesh.
+    // the subdeformer has a list of indices, and weights,
+    // with indices specifying vertex indices,
+    // and weights specifying the corresponding influence of this bone.
+    // it also has Transform and TransformLink elements,
+    // specifying the transform of the MESH in BONE space,
+    // and the transformation of the BONE in WORLD space,
+    // likely in the bindpose.
+    //
+    // the input bone structure is different but similar,
+    // storing the number of weights for this bone,
+    // and an array of (vertex index, weight) pairs.
+    //
+    // one sticky point is that the number of vertices may not match,
+    // because assimp splits vertices by normal, uv, etc.
+
+    // first we should mark the skeleton for each mesh.
+    // the skeleton must include not only the aiBones,
+    // but also all their parent nodes.
+    // anything that affects the position of any bone node must be included.
+    std::vector<std::set<const aiNode*>> skeleton_by_mesh(mScene->mNumMeshes);
+    // at the same time we can build a list of all the skeleton nodes,
+    // which will be used later to mark them as type "limbNode".
+    std::unordered_set<const aiNode*> limbnodes;
+    // and a map of nodes by bone name, as finding them is annoying.
+    std::map<std::string,aiNode*> node_by_bone;
+    for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        const aiMesh* m = mScene->mMeshes[mi];
+        std::set<const aiNode*> skeleton;
+        for (size_t bi =0; bi < m->mNumBones; ++bi) {
+            const aiBone* b = m->mBones[bi];
+            const std::string name(b->mName.C_Str());
+            auto elem = node_by_bone.find(name);
+            aiNode* n;
+            if (elem != node_by_bone.end()) {
+                n = elem->second;
+            } else {
+                n = mScene->mRootNode->FindNode(b->mName);
+                if (!n) {
+                    // this should never happen
+                    std::stringstream err;
+                    err << "Failed to find node for bone: \"" << name << "\"";
+                    throw DeadlyExportError(err.str());
+                }
+                node_by_bone[name] = n;
+                limbnodes.insert(n);
+            }
+            skeleton.insert(n);
+            // mark all parent nodes as skeleton as well,
+            // up until we find the root node,
+            // or else the node containing the mesh,
+            // or else the parent of a node containig the mesh.
+            for (
+                const aiNode* parent = n->mParent;
+                parent && parent != mScene->mRootNode;
+                parent = parent->mParent
+            ) {
+                // if we've already done this node we can skip it all
+                if (skeleton.count(parent)) {
+                    break;
+                }
+                // ignore fbx transform nodes as these will be collapsed later
+                // TODO: cache this by aiNode*
+                const std::string node_name(parent->mName.C_Str());
+                if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
+                    continue;
+                }
+                // otherwise check if this is the root of the skeleton
+                bool end = false;
+                // is the mesh part of this node?
+                for (size_t i = 0; i < parent->mNumMeshes; ++i) {
+                    if (parent->mMeshes[i] == mi) {
+                        end = true;
+                        break;
+                    }
+                }
+                // is the mesh in one of the children of this node?
+                for (size_t j = 0; j < parent->mNumChildren; ++j) {
+                    aiNode* child = parent->mChildren[j];
+                    for (size_t i = 0; i < child->mNumMeshes; ++i) {
+                        if (child->mMeshes[i] == mi) {
+                            end = true;
+                            break;
+                        }
+                    }
+                    if (end) { break; }
+                }
+                limbnodes.insert(parent);
+                skeleton.insert(parent);
+                // if it was the skeleton root we can finish here
+                if (end) { break; }
+            }
+        }
+        skeleton_by_mesh[mi] = skeleton;
+    }
+
+    // we'll need the uids for the bone nodes, so generate them now
+    for (size_t i = 0; i < mScene->mNumMeshes; ++i) {
+        auto &s = skeleton_by_mesh[i];
+        for (const aiNode* n : s) {
+            auto elem = node_uids.find(n);
+            if (elem == node_uids.end()) {
+                node_uids[n] = generate_uid();
+            }
+        }
+    }
+
+    // now, for each aiMesh, we need to export a deformer,
+    // and for each aiBone a subdeformer,
+    // which should have all the skinning info.
+    // these will need to be connected properly to the mesh,
+    // and we can do that all now.
+    for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        const aiMesh* m = mScene->mMeshes[mi];
+        if (!m->HasBones()) {
+            continue;
+        }
+        // make a deformer for this mesh
+        int64_t deformer_uid = generate_uid();
+        FBX::Node dnode("Deformer");
+        dnode.AddProperties(deformer_uid, FBX::SEPARATOR + "Deformer", "Skin");
+        dnode.AddChild("Version", int32_t(101));
+        // "acuracy"... this is not a typo....
+        dnode.AddChild("Link_DeformAcuracy", double(50));
+        dnode.AddChild("SkinningType", "Linear"); // TODO: other modes?
+        dnode.Dump(outstream, binary, indent);
+
+        // connect it
+        connections.emplace_back("C", "OO", deformer_uid, mesh_uids[mi]);
+
+        // we will be indexing by vertex...
+        // but there might be a different number of "vertices"
+        // between assimp and our output FBX.
+        // this code is cut-and-pasted from the geometry section above...
+        // ideally this should not be so.
+        // ---
+        // index of original vertex in vertex data vector
+        std::vector<int32_t> vertex_indices;
+        // map of vertex value to its index in the data vector
+        std::map<aiVector3D,size_t> index_by_vertex_value;
+        int32_t index = 0;
+        for (size_t vi = 0; vi < m->mNumVertices; ++vi) {
+            aiVector3D vtx = m->mVertices[vi];
+            auto elem = index_by_vertex_value.find(vtx);
+            if (elem == index_by_vertex_value.end()) {
+                vertex_indices.push_back(index);
+                index_by_vertex_value[vtx] = index;
+                ++index;
+            } else {
+                vertex_indices.push_back(int32_t(elem->second));
+            }
+        }
+
+        // TODO, FIXME: this won't work if anything is not in the bind pose.
+        // for now if such a situation is detected, we throw an exception.
+        std::set<const aiBone*> not_in_bind_pose;
+        std::set<const aiNode*> no_offset_matrix;
+
+        // first get this mesh's position in world space,
+        // as we'll need it for each subdeformer.
+        //
+        // ...of course taking the position of the MESH doesn't make sense,
+        // as it can be instanced to many nodes.
+        // All we can do is assume no instancing,
+        // and take the first node we find that contains the mesh.
+        aiNode* mesh_node = get_node_for_mesh((unsigned int)mi, mScene->mRootNode);
+        aiMatrix4x4 mesh_xform = get_world_transform(mesh_node, mScene);
+
+        // now make a subdeformer for each bone in the skeleton
+        const std::set<const aiNode*> &skeleton = skeleton_by_mesh[mi];
+        for (const aiNode* bone_node : skeleton) {
+            // if there's a bone for this node, find it
+            const aiBone* b = nullptr;
+            for (size_t bi = 0; bi < m->mNumBones; ++bi) {
+                // TODO: this probably should index by something else
+                const std::string name(m->mBones[bi]->mName.C_Str());
+                if (node_by_bone[name] == bone_node) {
+                    b = m->mBones[bi];
+                    break;
+                }
+            }
+            if (!b) {
+                no_offset_matrix.insert(bone_node);
+            }
+
+            // start the subdeformer node
+            const int64_t subdeformer_uid = generate_uid();
+            FBX::Node sdnode("Deformer");
+            sdnode.AddProperties(
+                subdeformer_uid, FBX::SEPARATOR + "SubDeformer", "Cluster"
+            );
+            sdnode.AddChild("Version", int32_t(100));
+            sdnode.AddChild("UserData", "", "");
+
+            // add indices and weights, if any
+            if (b) {
+                std::vector<int32_t> subdef_indices;
+                std::vector<double> subdef_weights;
+                int32_t last_index = -1;
+                for (size_t wi = 0; wi < b->mNumWeights; ++wi) {
+                    int32_t vi = vertex_indices[b->mWeights[wi].mVertexId];
+                    if (vi == last_index) {
+                        // only for vertices we exported to fbx
+                        // TODO, FIXME: this assumes identically-located vertices
+                        // will always deform in the same way.
+                        // as assimp doesn't store a separate list of "positions",
+                        // there's not much that can be done about this
+                        // other than assuming that identical position means
+                        // identical vertex.
+                        continue;
+                    }
+                    subdef_indices.push_back(vi);
+                    subdef_weights.push_back(b->mWeights[wi].mWeight);
+                    last_index = vi;
+                }
+                // yes, "indexes"
+                sdnode.AddChild("Indexes", subdef_indices);
+                sdnode.AddChild("Weights", subdef_weights);
+            }
+
+            // transform is the transform of the mesh, but in bone space.
+            // if the skeleton is in the bind pose,
+            // we can take the inverse of the world-space bone transform
+            // and multiply by the world-space transform of the mesh.
+            aiMatrix4x4 bone_xform = get_world_transform(bone_node, mScene);
+            aiMatrix4x4 inverse_bone_xform = bone_xform;
+            inverse_bone_xform.Inverse();
+            aiMatrix4x4 tr = inverse_bone_xform * mesh_xform;
+
+            // this should be the same as the bone's mOffsetMatrix.
+            // if it's not the same, the skeleton isn't in the bind pose.
+            const float epsilon = 1e-4f; // some error is to be expected
+            bool bone_xform_okay = true;
+            if (b && ! tr.Equal(b->mOffsetMatrix, epsilon)) {
+                not_in_bind_pose.insert(b);
+                bone_xform_okay = false;
+            }
+
+            // if we have a bone we should use the mOffsetMatrix,
+            // otherwise try to just use the calculated transform.
+            if (b) {
+                sdnode.AddChild("Transform", b->mOffsetMatrix);
+            } else {
+                sdnode.AddChild("Transform", tr);
+            }
+            // note: it doesn't matter if we mix these,
+            // because if they disagree we'll throw an exception later.
+            // it could be that the skeleton is not in the bone pose
+            // but all bones are still defined,
+            // in which case this would use the mOffsetMatrix for everything
+            // and a correct skeleton would still be output.
+
+            // transformlink should be the position of the bone in world space.
+            // if the bone is in the bind pose (or nonexistent),
+            // we can just use the matrix we already calculated
+            if (bone_xform_okay) {
+                sdnode.AddChild("TransformLink", bone_xform);
+            // otherwise we can only work it out using the mesh position.
+            } else {
+                aiMatrix4x4 trl = b->mOffsetMatrix;
+                trl.Inverse();
+                trl *= mesh_xform;
+                sdnode.AddChild("TransformLink", trl);
+            }
+            // note: this means we ALWAYS rely on the mesh node transform
+            // being unchanged from the time the skeleton was bound.
+            // there's not really any way around this at the moment.
+
+            // done
+            sdnode.Dump(outstream, binary, indent);
+
+            // lastly, connect to the parent deformer
+            connections.emplace_back(
+                "C", "OO", subdeformer_uid, deformer_uid
+            );
+
+            // we also need to connect the limb node to the subdeformer.
+            connections.emplace_back(
+                "C", "OO", node_uids[bone_node], subdeformer_uid
+            );
+        }
+
+        // if we cannot create a valid FBX file, simply die.
+        // this will both prevent unnecessary bug reports,
+        // and tell the user what they can do to fix the situation
+        // (i.e. export their model in the bind pose).
+        if (no_offset_matrix.size() && not_in_bind_pose.size()) {
+            std::stringstream err;
+            err << "Not enough information to construct bind pose";
+            err << " for mesh " << mi << "!";
+            err << " Transform matrix for bone \"";
+            err << (*not_in_bind_pose.begin())->mName.C_Str() << "\"";
+            if (not_in_bind_pose.size() > 1) {
+                err << " (and " << not_in_bind_pose.size() - 1 << " more)";
+            }
+            err << " does not match mOffsetMatrix,";
+            err << " and node \"";
+            err << (*no_offset_matrix.begin())->mName.C_Str() << "\"";
+            if (no_offset_matrix.size() > 1) {
+                err << " (and " << no_offset_matrix.size() - 1 << " more)";
+            }
+            err << " has no offset matrix to rely on.";
+            err << " Please ensure bones are in the bind pose to export.";
+            throw DeadlyExportError(err.str());
+        }
+
+    }
+
+    // BindPose
+    //
+    // This is a legacy system, which should be unnecessary.
+    //
+    // Somehow including it slows file loading by the official FBX SDK,
+    // and as it can reconstruct it from the deformers anyway,
+    // this is not currently included.
+    //
+    // The code is kept here in case it's useful in the future,
+    // but it's pretty much a hack anyway,
+    // as assimp doesn't store bindpose information for full skeletons.
+    //
+    /*for (size_t mi = 0; mi < mScene->mNumMeshes; ++mi) {
+        aiMesh* mesh = mScene->mMeshes[mi];
+        if (! mesh->HasBones()) { continue; }
+        int64_t bindpose_uid = generate_uid();
+        FBX::Node bpnode("Pose");
+        bpnode.AddProperty(bindpose_uid);
+        // note: this uid is never linked or connected to anything.
+        bpnode.AddProperty(FBX::SEPARATOR + "Pose"); // blank name
+        bpnode.AddProperty("BindPose");
+
+        bpnode.AddChild("Type", "BindPose");
+        bpnode.AddChild("Version", int32_t(100));
+
+        aiNode* mesh_node = get_node_for_mesh(mi, mScene->mRootNode);
+
+        // next get the whole skeleton for this mesh.
+        // we need it all to define the bindpose section.
+        // the FBX SDK will complain if it's missing,
+        // and also if parents of used bones don't have a subdeformer.
+        // order shouldn't matter.
+        std::set<aiNode*> skeleton;
+        for (size_t bi = 0; bi < mesh->mNumBones; ++bi) {
+            // bone node should have already been indexed
+            const aiBone* b = mesh->mBones[bi];
+            const std::string bone_name(b->mName.C_Str());
+            aiNode* parent = node_by_bone[bone_name];
+            // insert all nodes down to the root or mesh node
+            while (
+                parent
+                && parent != mScene->mRootNode
+                && parent != mesh_node
+            ) {
+                skeleton.insert(parent);
+                parent = parent->mParent;
+            }
+        }
+
+        // number of pose nodes. includes one for the mesh itself.
+        bpnode.AddChild("NbPoseNodes", int32_t(1 + skeleton.size()));
+
+        // the first pose node is always the mesh itself
+        FBX::Node pose("PoseNode");
+        pose.AddChild("Node", mesh_uids[mi]);
+        aiMatrix4x4 mesh_node_xform = get_world_transform(mesh_node, mScene);
+        pose.AddChild("Matrix", mesh_node_xform);
+        bpnode.AddChild(pose);
+
+        for (aiNode* bonenode : skeleton) {
+            // does this node have a uid yet?
+            int64_t node_uid;
+            auto node_uid_iter = node_uids.find(bonenode);
+            if (node_uid_iter != node_uids.end()) {
+                node_uid = node_uid_iter->second;
+            } else {
+                node_uid = generate_uid();
+                node_uids[bonenode] = node_uid;
+            }
+
+            // make a pose thingy
+            pose = FBX::Node("PoseNode");
+            pose.AddChild("Node", node_uid);
+            aiMatrix4x4 node_xform = get_world_transform(bonenode, mScene);
+            pose.AddChild("Matrix", node_xform);
+            bpnode.AddChild(pose);
+        }
+
+        // now write it
+        bpnode.Dump(outstream, binary, indent);
+    }*/
+
+    // TODO: cameras, lights
+
+    // write nodes (i.e. model hierarchy)
+    // start at root node
+    WriteModelNodes(
+        outstream, mScene->mRootNode, 0, limbnodes
+    );
+
+    // animations
+    //
+    // in FBX there are:
+    // * AnimationStack - corresponds to an aiAnimation
+    // * AnimationLayer - a combinable animation component
+    // * AnimationCurveNode - links the property to be animated
+    // * AnimationCurve - defines animation data for a single property value
+    //
+    // the CurveNode also provides the default value for a property,
+    // such as the X, Y, Z coordinates for animatable translation.
+    //
+    // the Curve only specifies values for one component of the property,
+    // so there will be a separate AnimationCurve for X, Y, and Z.
+    //
+    // Assimp has:
+    // * aiAnimation - basically corresponds to an AnimationStack
+    // * aiNodeAnim - defines all animation for one aiNode
+    // * aiVectorKey/aiQuatKey - define the keyframe data for T/R/S
+    //
+    // assimp has no equivalent for AnimationLayer,
+    // and these are flattened on FBX import.
+    // we can assume there will be one per AnimationStack.
+    //
+    // the aiNodeAnim contains all animation data for a single aiNode,
+    // which will correspond to three AnimationCurveNode's:
+    // one each for translation, rotation and scale.
+    // The data for each of these will be put in 9 AnimationCurve's,
+    // T.X, T.Y, T.Z, R.X, R.Y, R.Z, etc.
+
+    // AnimationStack / aiAnimation
+    std::vector<int64_t> animation_stack_uids(mScene->mNumAnimations);
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        int64_t animstack_uid = generate_uid();
+        animation_stack_uids[ai] = animstack_uid;
+        const aiAnimation* anim = mScene->mAnimations[ai];
+
+        FBX::Node asnode("AnimationStack");
+        std::string name = anim->mName.C_Str() + FBX::SEPARATOR + "AnimStack";
+        asnode.AddProperties(animstack_uid, name, "");
+        FBX::Node p("Properties70");
+        p.AddP70time("LocalStart", 0); // assimp doesn't store this
+        p.AddP70time("LocalStop", to_ktime(anim->mDuration, anim));
+        p.AddP70time("ReferenceStart", 0);
+        p.AddP70time("ReferenceStop", to_ktime(anim->mDuration, anim));
+        asnode.AddChild(p);
+
+        // this node absurdly always pretends it has children
+        // (in this case it does, but just in case...)
+        asnode.force_has_children = true;
+        asnode.Dump(outstream, binary, indent);
+
+        // note: animation stacks are not connected to anything
+    }
+
+    // AnimationLayer - one per aiAnimation
+    std::vector<int64_t> animation_layer_uids(mScene->mNumAnimations);
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        int64_t animlayer_uid = generate_uid();
+        animation_layer_uids[ai] = animlayer_uid;
+        FBX::Node alnode("AnimationLayer");
+        alnode.AddProperties(animlayer_uid, FBX::SEPARATOR + "AnimLayer", "");
+
+        // this node absurdly always pretends it has children
+        alnode.force_has_children = true;
+        alnode.Dump(outstream, binary, indent);
+
+        // connect to the relevant animstack
+        connections.emplace_back(
+            "C", "OO", animlayer_uid, animation_stack_uids[ai]
+        );
+    }
+
+    // AnimCurveNode - three per aiNodeAnim
+    std::vector<std::vector<std::array<int64_t,3>>> curve_node_uids;
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        const aiAnimation* anim = mScene->mAnimations[ai];
+        const int64_t layer_uid = animation_layer_uids[ai];
+        std::vector<std::array<int64_t,3>> nodeanim_uids;
+        for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
+            const aiNodeAnim* na = anim->mChannels[nai];
+            // get the corresponding aiNode
+            const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
+            // and its transform
+            const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
+            aiVector3D T, R, S;
+            node_xfm.Decompose(S, R, T);
+
+            // AnimationCurveNode uids
+            std::array<int64_t,3> ids;
+            ids[0] = generate_uid(); // T
+            ids[1] = generate_uid(); // R
+            ids[2] = generate_uid(); // S
+
+            // translation
+            WriteAnimationCurveNode(outstream,
+                ids[0], "T", T, "Lcl Translation",
+                layer_uid, node_uids[node]
+            );
+
+            // rotation
+            WriteAnimationCurveNode(outstream,
+                ids[1], "R", R, "Lcl Rotation",
+                layer_uid, node_uids[node]
+            );
+
+            // scale
+            WriteAnimationCurveNode(outstream,
+                ids[2], "S", S, "Lcl Scale",
+                layer_uid, node_uids[node]
+            );
+
+            // store the uids for later use
+            nodeanim_uids.push_back(ids);
+        }
+        curve_node_uids.push_back(nodeanim_uids);
+    }
+
+    // AnimCurve - defines actual keyframe data.
+    // there's a separate curve for every component of every vector,
+    // for example a transform curvenode will have separate X/Y/Z AnimCurve's
+    for (size_t ai = 0; ai < mScene->mNumAnimations; ++ai) {
+        const aiAnimation* anim = mScene->mAnimations[ai];
+        for (size_t nai = 0; nai < anim->mNumChannels; ++nai) {
+            const aiNodeAnim* na = anim->mChannels[nai];
+            // get the corresponding aiNode
+            const aiNode* node = mScene->mRootNode->FindNode(na->mNodeName);
+            // and its transform
+            const aiMatrix4x4 node_xfm = get_world_transform(node, mScene);
+            aiVector3D T, R, S;
+            node_xfm.Decompose(S, R, T);
+            const std::array<int64_t,3>& ids = curve_node_uids[ai][nai];
+
+            std::vector<int64_t> times;
+            std::vector<float> xval, yval, zval;
+
+            // position/translation
+            for (size_t ki = 0; ki < na->mNumPositionKeys; ++ki) {
+                const aiVectorKey& k = na->mPositionKeys[ki];
+                times.push_back(to_ktime(k.mTime));
+                xval.push_back(k.mValue.x);
+                yval.push_back(k.mValue.y);
+                zval.push_back(k.mValue.z);
+            }
+            // one curve each for X, Y, Z
+            WriteAnimationCurve(outstream, T.x, times, xval, ids[0], "d|X");
+            WriteAnimationCurve(outstream, T.y, times, yval, ids[0], "d|Y");
+            WriteAnimationCurve(outstream, T.z, times, zval, ids[0], "d|Z");
+
+            // rotation
+            times.clear(); xval.clear(); yval.clear(); zval.clear();
+            for (size_t ki = 0; ki < na->mNumRotationKeys; ++ki) {
+                const aiQuatKey& k = na->mRotationKeys[ki];
+                times.push_back(to_ktime(k.mTime));
+                // TODO: aiQuaternion method to convert to Euler...
+                aiMatrix4x4 m(k.mValue.GetMatrix());
+                aiVector3D qs, qr, qt;
+                m.Decompose(qs, qr, qt);
+                qr *= DEG;
+                xval.push_back(qr.x);
+                yval.push_back(qr.y);
+                zval.push_back(qr.z);
+            }
+            WriteAnimationCurve(outstream, R.x, times, xval, ids[1], "d|X");
+            WriteAnimationCurve(outstream, R.y, times, yval, ids[1], "d|Y");
+            WriteAnimationCurve(outstream, R.z, times, zval, ids[1], "d|Z");
+
+            // scaling/scale
+            times.clear(); xval.clear(); yval.clear(); zval.clear();
+            for (size_t ki = 0; ki < na->mNumScalingKeys; ++ki) {
+                const aiVectorKey& k = na->mScalingKeys[ki];
+                times.push_back(to_ktime(k.mTime));
+                xval.push_back(k.mValue.x);
+                yval.push_back(k.mValue.y);
+                zval.push_back(k.mValue.z);
+            }
+            WriteAnimationCurve(outstream, S.x, times, xval, ids[2], "d|X");
+            WriteAnimationCurve(outstream, S.y, times, yval, ids[2], "d|Y");
+            WriteAnimationCurve(outstream, S.z, times, zval, ids[2], "d|Z");
+        }
+    }
+
+    indent = 0;
+    object_node.End(outstream, binary, indent, true);
+}
+
+// convenience map of magic node name strings to FBX properties,
+// including the expected type of transform.
+const std::map<std::string,std::pair<std::string,char>> transform_types = {
+    {"Translation", {"Lcl Translation", 't'}},
+    {"RotationOffset", {"RotationOffset", 't'}},
+    {"RotationPivot", {"RotationPivot", 't'}},
+    {"PreRotation", {"PreRotation", 'r'}},
+    {"Rotation", {"Lcl Rotation", 'r'}},
+    {"PostRotation", {"PostRotation", 'r'}},
+    {"RotationPivotInverse", {"RotationPivotInverse", 'i'}},
+    {"ScalingOffset", {"ScalingOffset", 't'}},
+    {"ScalingPivot", {"ScalingPivot", 't'}},
+    {"Scaling", {"Lcl Scaling", 's'}},
+    {"ScalingPivotInverse", {"ScalingPivotInverse", 'i'}},
+    {"GeometricScaling", {"GeometricScaling", 's'}},
+    {"GeometricRotation", {"GeometricRotation", 'r'}},
+    {"GeometricTranslation", {"GeometricTranslation", 't'}},
+    {"GeometricTranslationInverse", {"GeometricTranslationInverse", 'i'}},
+    {"GeometricRotationInverse", {"GeometricRotationInverse", 'i'}},
+    {"GeometricScalingInverse", {"GeometricScalingInverse", 'i'}}
+};
+
+// write a single model node to the stream
+void FBXExporter::WriteModelNode(
+    StreamWriterLE& outstream,
+    bool binary,
+    const aiNode* node,
+    int64_t node_uid,
+    const std::string& type,
+    const std::vector<std::pair<std::string,aiVector3D>>& transform_chain,
+    TransformInheritance inherit_type
+){
+    const aiVector3D zero = {0, 0, 0};
+    const aiVector3D one = {1, 1, 1};
+    FBX::Node m("Model");
+    std::string name = node->mName.C_Str() + FBX::SEPARATOR + "Model";
+    m.AddProperties(node_uid, name, type);
+    m.AddChild("Version", int32_t(232));
+    FBX::Node p("Properties70");
+    p.AddP70bool("RotationActive", 1);
+    p.AddP70int("DefaultAttributeIndex", 0);
+    p.AddP70enum("InheritType", inherit_type);
+    if (transform_chain.empty()) {
+        // decompose 4x4 transform matrix into TRS
+        aiVector3D t, r, s;
+        node->mTransformation.Decompose(s, r, t);
+        if (t != zero) {
+            p.AddP70(
+                "Lcl Translation", "Lcl Translation", "", "A",
+                double(t.x), double(t.y), double(t.z)
+            );
+        }
+        if (r != zero) {
+            p.AddP70(
+                "Lcl Rotation", "Lcl Rotation", "", "A",
+                double(DEG*r.x), double(DEG*r.y), double(DEG*r.z)
+            );
+        }
+        if (s != one) {
+            p.AddP70(
+                "Lcl Scaling", "Lcl Scaling", "", "A",
+                double(s.x), double(s.y), double(s.z)
+            );
+        }
+    } else {
+        // apply the transformation chain.
+        // these transformation elements are created when importing FBX,
+        // which has a complex transformation hierarchy for each node.
+        // as such we can bake the hierarchy back into the node on export.
+        for (auto &item : transform_chain) {
+            auto elem = transform_types.find(item.first);
+            if (elem == transform_types.end()) {
+                // then this is a bug
+                std::stringstream err;
+                err << "unrecognized FBX transformation type: ";
+                err << item.first;
+                throw DeadlyExportError(err.str());
+            }
+            const std::string &name = elem->second.first;
+            const aiVector3D &v = item.second;
+            if (name.compare(0, 4, "Lcl ") == 0) {
+                // special handling for animatable properties
+                p.AddP70(
+                    name, name, "", "A",
+                    double(v.x), double(v.y), double(v.z)
+                );
+            } else {
+                p.AddP70vector(name, v.x, v.y, v.z);
+            }
+        }
+    }
+    m.AddChild(p);
+
+    // not sure what these are for,
+    // but they seem to be omnipresent
+    m.AddChild("Shading", Property(true));
+    m.AddChild("Culling", Property("CullingOff"));
+
+    m.Dump(outstream, binary, 1);
+}
+
+// wrapper for WriteModelNodes to create and pass a blank transform chain
+void FBXExporter::WriteModelNodes(
+    StreamWriterLE& s,
+    const aiNode* node,
+    int64_t parent_uid,
+    const std::unordered_set<const aiNode*>& limbnodes
+) {
+    std::vector<std::pair<std::string,aiVector3D>> chain;
+    WriteModelNodes(s, node, parent_uid, limbnodes, chain);
+}
+
+void FBXExporter::WriteModelNodes(
+    StreamWriterLE& outstream,
+    const aiNode* node,
+    int64_t parent_uid,
+    const std::unordered_set<const aiNode*>& limbnodes,
+    std::vector<std::pair<std::string,aiVector3D>>& transform_chain
+) {
+    // first collapse any expanded transformation chains created by FBX import.
+    std::string node_name(node->mName.C_Str());
+    if (node_name.find(MAGIC_NODE_TAG) != std::string::npos) {
+        auto pos = node_name.find(MAGIC_NODE_TAG) + MAGIC_NODE_TAG.size() + 1;
+        std::string type_name = node_name.substr(pos);
+        auto elem = transform_types.find(type_name);
+        if (elem == transform_types.end()) {
+            // then this is a bug and should be fixed
+            std::stringstream err;
+            err << "unrecognized FBX transformation node";
+            err << " of type " << type_name << " in node " << node_name;
+            throw DeadlyExportError(err.str());
+        }
+        aiVector3D t, r, s;
+        node->mTransformation.Decompose(s, r, t);
+        switch (elem->second.second) {
+        case 'i': // inverse
+            // we don't need to worry about the inverse matrices
+            break;
+        case 't': // translation
+            transform_chain.emplace_back(elem->first, t);
+            break;
+        case 'r': // rotation
+            r *= float(DEG);
+            transform_chain.emplace_back(elem->first, r);
+            break;
+        case 's': // scale
+            transform_chain.emplace_back(elem->first, s);
+            break;
+        default:
+            // this should never happen
+            std::stringstream err;
+            err << "unrecognized FBX transformation type code: ";
+            err << elem->second.second;
+            throw DeadlyExportError(err.str());
+        }
+        // now continue on to any child nodes
+        for (unsigned i = 0; i < node->mNumChildren; ++i) {
+            WriteModelNodes(
+                outstream,
+                node->mChildren[i],
+                parent_uid,
+                limbnodes,
+                transform_chain
+            );
+        }
+        return;
+    }
+
+    int64_t node_uid = 0;
+    // generate uid and connect to parent, if not the root node,
+    if (node != mScene->mRootNode) {
+        auto elem = node_uids.find(node);
+        if (elem != node_uids.end()) {
+            node_uid = elem->second;
+        } else {
+            node_uid = generate_uid();
+            node_uids[node] = node_uid;
+        }
+        connections.emplace_back("C", "OO", node_uid, parent_uid);
+    }
+
+    // what type of node is this?
+    if (node == mScene->mRootNode) {
+        // handled later
+    } else if (node->mNumMeshes == 1) {
+        // connect to child mesh, which should have been written previously
+        connections.emplace_back(
+            "C", "OO", mesh_uids[node->mMeshes[0]], node_uid
+        );
+        // also connect to the material for the child mesh
+        connections.emplace_back(
+            "C", "OO",
+            material_uids[mScene->mMeshes[node->mMeshes[0]]->mMaterialIndex],
+            node_uid
+        );
+        // write model node
+        WriteModelNode(
+            outstream, binary, node, node_uid, "Mesh", transform_chain
+        );
+    } else if (limbnodes.count(node)) {
+        WriteModelNode(
+            outstream, binary, node, node_uid, "LimbNode", transform_chain
+        );
+        // we also need to write a nodeattribute to mark it as a skeleton
+        int64_t node_attribute_uid = generate_uid();
+        FBX::Node na("NodeAttribute");
+        na.AddProperties(
+            node_attribute_uid, FBX::SEPARATOR + "NodeAttribute", "LimbNode"
+        );
+        na.AddChild("TypeFlags", Property("Skeleton"));
+        na.Dump(outstream, binary, 1);
+        // and connect them
+        connections.emplace_back("C", "OO", node_attribute_uid, node_uid);
+    } else {
+        // generate a null node so we can add children to it
+        WriteModelNode(
+            outstream, binary, node, node_uid, "Null", transform_chain
+        );
+    }
+
+    // if more than one child mesh, make nodes for each mesh
+    if (node->mNumMeshes > 1 || node == mScene->mRootNode) {
+        for (size_t i = 0; i < node->mNumMeshes; ++i) {
+            // make a new model node
+            int64_t new_node_uid = generate_uid();
+            // connect to parent node
+            connections.emplace_back("C", "OO", new_node_uid, node_uid);
+            // connect to child mesh, which should have been written previously
+            connections.emplace_back(
+                "C", "OO", mesh_uids[node->mMeshes[i]], new_node_uid
+            );
+            // also connect to the material for the child mesh
+            connections.emplace_back(
+                "C", "OO",
+                material_uids[
+                    mScene->mMeshes[node->mMeshes[i]]->mMaterialIndex
+                ],
+                new_node_uid
+            );
+            // write model node
+            FBX::Node m("Model");
+            // take name from mesh name, if it exists
+            std::string name = mScene->mMeshes[node->mMeshes[i]]->mName.C_Str();
+            name += FBX::SEPARATOR + "Model";
+            m.AddProperties(new_node_uid, name, "Mesh");
+            m.AddChild("Version", int32_t(232));
+            FBX::Node p("Properties70");
+            p.AddP70enum("InheritType", 1);
+            m.AddChild(p);
+            m.Dump(outstream, binary, 1);
+        }
+    }
+
+    // now recurse into children
+    for (size_t i = 0; i < node->mNumChildren; ++i) {
+        WriteModelNodes(
+            outstream, node->mChildren[i], node_uid, limbnodes
+        );
+    }
+}
+
+
+void FBXExporter::WriteAnimationCurveNode(
+    StreamWriterLE& outstream,
+    int64_t uid,
+    std::string name, // "T", "R", or "S"
+    aiVector3D default_value,
+    std::string property_name, // "Lcl Translation" etc
+    int64_t layer_uid,
+    int64_t node_uid
+) {
+    FBX::Node n("AnimationCurveNode");
+    n.AddProperties(uid, name + FBX::SEPARATOR + "AnimCurveNode", "");
+    FBX::Node p("Properties70");
+    p.AddP70numberA("d|X", default_value.x);
+    p.AddP70numberA("d|Y", default_value.y);
+    p.AddP70numberA("d|Z", default_value.z);
+    n.AddChild(p);
+    n.Dump(outstream, binary, 1);
+    // connect to layer
+    this->connections.emplace_back("C", "OO", uid, layer_uid);
+    // connect to bone
+    this->connections.emplace_back("C", "OP", uid, node_uid, property_name);
+}
+
+
+void FBXExporter::WriteAnimationCurve(
+    StreamWriterLE& outstream,
+    double default_value,
+    const std::vector<int64_t>& times,
+    const std::vector<float>& values,
+    int64_t curvenode_uid,
+    const std::string& property_link // "d|X", "d|Y", etc
+) {
+    FBX::Node n("AnimationCurve");
+    int64_t curve_uid = generate_uid();
+    n.AddProperties(curve_uid, FBX::SEPARATOR + "AnimCurve", "");
+    n.AddChild("Default", default_value);
+    n.AddChild("KeyVer", int32_t(4009));
+    n.AddChild("KeyTime", times);
+    n.AddChild("KeyValueFloat", values);
+    // TODO: keyattr flags and data (STUB for now)
+    n.AddChild("KeyAttrFlags", std::vector<int32_t>{0});
+    n.AddChild("KeyAttrDataFloat", std::vector<float>{0,0,0,0});
+    n.AddChild(
+        "KeyAttrRefCount",
+        std::vector<int32_t>{static_cast<int32_t>(times.size())}
+    );
+    n.Dump(outstream, binary, 1);
+    this->connections.emplace_back(
+        "C", "OP", curve_uid, curvenode_uid, property_link
+    );
+}
+
+
+void FBXExporter::WriteConnections ()
+{
+    // we should have completed the connection graph already,
+    // so basically just dump it here
+    if (!binary) {
+        WriteAsciiSectionHeader("Object connections");
+    }
+    // TODO: comments with names in the ascii version
+    FBX::Node conn("Connections");
+    StreamWriterLE outstream(outfile);
+    conn.Begin(outstream, binary, 0);
+    conn.BeginChildren(outstream, binary, 0);
+    for (auto &n : connections) {
+        n.Dump(outstream, binary, 1);
+    }
+    conn.End(outstream, binary, 0, !connections.empty());
+    connections.clear();
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+#endif // ASSIMP_BUILD_NO_EXPORT

+ 178 - 0
thirdparty/assimp/code/FBXExporter.h

@@ -0,0 +1,178 @@
+/*
+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 FBXExporter.h
+* Declares the exporter class to write a scene to an fbx file
+*/
+#ifndef AI_FBXEXPORTER_H_INC
+#define AI_FBXEXPORTER_H_INC
+
+#ifndef ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#include "FBXExportNode.h" // FBX::Node
+#include "FBXCommon.h" // FBX::TransformInheritance
+
+#include <assimp/types.h>
+//#include <assimp/material.h>
+#include <assimp/StreamWriter.h> // StreamWriterLE
+#include <assimp/Exceptional.h> // DeadlyExportError
+
+#include <vector>
+#include <map>
+#include <unordered_set>
+#include <memory> // shared_ptr
+#include <sstream> // stringstream
+
+struct aiScene;
+struct aiNode;
+//struct aiMaterial;
+
+namespace Assimp
+{
+    class IOSystem;
+    class IOStream;
+    class ExportProperties;
+
+    // ---------------------------------------------------------------------
+    /** Helper class to export a given scene to an FBX file. */
+    // ---------------------------------------------------------------------
+    class FBXExporter
+    {
+    public:
+        /// Constructor for a specific scene to export
+        FBXExporter(const aiScene* pScene, const ExportProperties* pProperties);
+
+        // call one of these methods to export
+        void ExportBinary(const char* pFile, IOSystem* pIOSystem);
+        void ExportAscii(const char* pFile, IOSystem* pIOSystem);
+
+    private:
+        bool binary; // whether current export is in binary or ascii format
+        const aiScene* mScene; // the scene to export
+        const ExportProperties* mProperties; // currently unused
+        std::shared_ptr<IOStream> outfile; // file to write to
+
+        std::vector<FBX::Node> connections; // connection storage
+
+        std::vector<int64_t> mesh_uids;
+        std::vector<int64_t> material_uids;
+        std::map<const aiNode*,int64_t> node_uids;
+
+        // this crude unique-ID system is actually fine
+        int64_t last_uid = 999999;
+        int64_t generate_uid() { return ++last_uid; }
+
+        // binary files have a specific header and footer,
+        // in addition to the actual data
+        void WriteBinaryHeader();
+        void WriteBinaryFooter();
+
+        // ascii files have a comment at the top
+        void WriteAsciiHeader();
+
+        // WriteAllNodes does the actual export.
+        // It just calls all the Write<Section> methods below in order.
+        void WriteAllNodes();
+
+        // Methods to write individual sections.
+        // The order here matches the order inside an FBX file.
+        // Each method corresponds to a top-level FBX section,
+        // except WriteHeader which also includes some binary-only sections
+        // and WriteFooter which is binary data only.
+        void WriteHeaderExtension();
+        // WriteFileId(); // binary-only, included in WriteHeader
+        // WriteCreationTime(); // binary-only, included in WriteHeader
+        // WriteCreator(); // binary-only, included in WriteHeader
+        void WriteGlobalSettings();
+        void WriteDocuments();
+        void WriteReferences();
+        void WriteDefinitions();
+        void WriteObjects();
+        void WriteConnections();
+        // WriteTakes(); // deprecated since at least 2015 (fbx 7.4)
+
+        // helpers
+        void WriteAsciiSectionHeader(const std::string& title);
+        void WriteModelNodes(
+            Assimp::StreamWriterLE& s,
+            const aiNode* node,
+            int64_t parent_uid,
+            const std::unordered_set<const aiNode*>& limbnodes
+        );
+        void WriteModelNodes( // usually don't call this directly
+            StreamWriterLE& s,
+            const aiNode* node,
+            int64_t parent_uid,
+            const std::unordered_set<const aiNode*>& limbnodes,
+            std::vector<std::pair<std::string,aiVector3D>>& transform_chain
+        );
+        void WriteModelNode( // nor this
+            StreamWriterLE& s,
+            bool binary,
+            const aiNode* node,
+            int64_t node_uid,
+            const std::string& type,
+            const std::vector<std::pair<std::string,aiVector3D>>& xfm_chain,
+            FBX::TransformInheritance ti_type=FBX::TransformInheritance_RSrs
+        );
+        void WriteAnimationCurveNode(
+            StreamWriterLE& outstream,
+            int64_t uid,
+            std::string name, // "T", "R", or "S"
+            aiVector3D default_value,
+            std::string property_name, // "Lcl Translation" etc
+            int64_t animation_layer_uid,
+            int64_t node_uid
+        );
+        void WriteAnimationCurve(
+            StreamWriterLE& outstream,
+            double default_value,
+            const std::vector<int64_t>& times,
+            const std::vector<float>& values,
+            int64_t curvenode_id,
+            const std::string& property_link // "d|X", "d|Y", etc
+        );
+    };
+}
+
+#endif // ASSIMP_BUILD_NO_FBX_EXPORTER
+
+#endif // AI_FBXEXPORTER_H_INC

+ 153 - 0
thirdparty/assimp/code/FBXImportSettings.h

@@ -0,0 +1,153 @@
+/*
+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 INCLUDED_AI_FBX_IMPORTSETTINGS_H
+#define INCLUDED_AI_FBX_IMPORTSETTINGS_H
+
+namespace Assimp {
+namespace FBX {
+
+/** FBX import settings, parts of which are publicly accessible via their corresponding AI_CONFIG constants */
+struct ImportSettings
+{
+    ImportSettings()
+        : strictMode(true)
+        , readAllLayers(true)
+        , readAllMaterials(false)
+        , readMaterials(true)
+        , readTextures(true)
+        , readCameras(true)
+        , readLights(true)
+        , readAnimations(true)
+        , readWeights(true)
+        , preservePivots(true)
+        , optimizeEmptyAnimationCurves(true)
+        , useLegacyEmbeddedTextureNaming(false)
+    {}
+
+
+    /** 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;
+};
+
+
+} // !FBX
+} // !Assimp
+
+#endif
+

+ 197 - 0
thirdparty/assimp/code/FBXImporter.cpp

@@ -0,0 +1,197 @@
+/*
+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.
+r
+* 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  FBXImporter.cpp
+ *  @brief Implementation of the FBX importer.
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXImporter.h"
+
+#include "FBXTokenizer.h"
+#include "FBXParser.h"
+#include "FBXUtil.h"
+#include "FBXDocument.h"
+#include "FBXConverter.h"
+
+#include <assimp/StreamReader.h>
+#include <assimp/MemoryIOWrapper.h>
+#include <assimp/Importer.hpp>
+#include <assimp/importerdesc.h>
+
+namespace Assimp {
+
+template<>
+const char* LogFunctions<FBXImporter>::Prefix() {
+    static auto prefix = "FBX: ";
+    return prefix;
+}
+
+}
+
+using namespace Assimp;
+using namespace Assimp::Formatter;
+using namespace Assimp::FBX;
+
+namespace {
+
+static const aiImporterDesc desc = {
+    "Autodesk FBX Importer",
+    "",
+    "",
+    "",
+    aiImporterFlags_SupportTextFlavour,
+    0,
+    0,
+    0,
+    0,
+    "fbx"
+};
+}
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by #Importer
+FBXImporter::FBXImporter()
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+FBXImporter::~FBXImporter()
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the class can handle the format of the given file.
+bool FBXImporter::CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const
+{
+    const std::string& extension = GetExtension(pFile);
+    if (extension == std::string( desc.mFileExtensions ) ) {
+        return true;
+    }
+
+    else if ((!extension.length() || checkSig) && pIOHandler)   {
+        // at least ASCII-FBX files usually have a 'FBX' somewhere in their head
+        const char* tokens[] = {"fbx"};
+        return SearchFileHeaderForToken(pIOHandler,pFile,tokens,1);
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+// List all extensions handled by this loader
+const aiImporterDesc* FBXImporter::GetInfo () const
+{
+    return &desc;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration properties for the loader
+void FBXImporter::SetupProperties(const Importer* pImp)
+{
+    settings.readAllLayers = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_GEOMETRY_LAYERS, true);
+    settings.readAllMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ALL_MATERIALS, false);
+    settings.readMaterials = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_MATERIALS, true);
+    settings.readTextures = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_TEXTURES, true);
+    settings.readCameras = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_CAMERAS, true);
+    settings.readLights = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_LIGHTS, true);
+    settings.readAnimations = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_READ_ANIMATIONS, true);
+    settings.strictMode = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_STRICT_MODE, false);
+    settings.preservePivots = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_PRESERVE_PIVOTS, true);
+    settings.optimizeEmptyAnimationCurves = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_OPTIMIZE_EMPTY_ANIMATION_CURVES, true);
+    settings.useLegacyEmbeddedTextureNaming = pImp->GetPropertyBool(AI_CONFIG_IMPORT_FBX_EMBEDDED_TEXTURES_LEGACY_NAMING, false);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Imports the given file into the given scene structure.
+void FBXImporter::InternReadFile( const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler)
+{
+    std::unique_ptr<IOStream> stream(pIOHandler->Open(pFile,"rb"));
+    if (!stream) {
+        ThrowException("Could not open file for reading");
+    }
+
+    // read entire file into memory - no streaming for this, fbx
+    // files can grow large, but the assimp output data structure
+    // then becomes very large, too. Assimp doesn't support
+    // streaming for its output data structures so the net win with
+    // streaming input data would be very low.
+    std::vector<char> contents;
+    contents.resize(stream->FileSize()+1);
+    stream->Read( &*contents.begin(), 1, contents.size()-1 );
+    contents[ contents.size() - 1 ] = 0;
+    const char* const begin = &*contents.begin();
+
+    // broadphase tokenizing pass in which we identify the core
+    // syntax elements of FBX (brackets, commas, key:value mappings)
+    TokenList tokens;
+    try {
+
+        bool is_binary = false;
+        if (!strncmp(begin,"Kaydara FBX Binary",18)) {
+            is_binary = true;
+            TokenizeBinary(tokens,begin,static_cast<unsigned int>(contents.size()));
+        }
+        else {
+            Tokenize(tokens,begin);
+        }
+
+        // use this information to construct a very rudimentary
+        // parse-tree representing the FBX scope structure
+        Parser parser(tokens, is_binary);
+
+        // take the raw parse-tree and convert it to a FBX DOM
+        Document doc(parser,settings);
+
+        // convert the FBX DOM to aiScene
+        ConvertToAssimpScene(pScene,doc);
+
+        std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
+    }
+    catch(std::exception&) {
+        std::for_each(tokens.begin(),tokens.end(),Util::delete_fun<Token>());
+        throw;
+    }
+}
+
+#endif // !ASSIMP_BUILD_NO_FBX_IMPORTER

+ 100 - 0
thirdparty/assimp/code/FBXImporter.h

@@ -0,0 +1,100 @@
+/*
+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  FBXImporter.h
+ *  @brief Declaration of the FBX main importer class
+ */
+#ifndef INCLUDED_AI_FBX_IMPORTER_H
+#define INCLUDED_AI_FBX_IMPORTER_H
+
+#include <assimp/BaseImporter.h>
+#include <assimp/LogAux.h>
+
+#include "FBXImportSettings.h"
+
+namespace Assimp    {
+
+// TinyFormatter.h
+namespace Formatter {
+    template <typename T,typename TR, typename A> class basic_formatter;
+    typedef class basic_formatter< char, std::char_traits<char>, std::allocator<char> > format;
+}
+
+// -------------------------------------------------------------------------------------------
+/** Load the Autodesk FBX file format.
+
+ See http://en.wikipedia.org/wiki/FBX
+*/
+// -------------------------------------------------------------------------------------------
+class FBXImporter : public BaseImporter, public LogFunctions<FBXImporter>
+{
+public:
+    FBXImporter();
+    virtual ~FBXImporter();
+
+    // --------------------
+    bool CanRead( const std::string& pFile,
+        IOSystem* pIOHandler,
+        bool checkSig
+    ) const;
+
+protected:
+
+    // --------------------
+    const aiImporterDesc* GetInfo () const;
+
+    // --------------------
+    void SetupProperties(const Importer* pImp);
+
+    // --------------------
+    void InternReadFile( const std::string& pFile,
+        aiScene* pScene,
+        IOSystem* pIOHandler
+    );
+
+private:
+    FBX::ImportSettings settings;
+}; // !class FBXImporter
+
+} // end of namespace Assimp
+#endif // !INCLUDED_AI_FBX_IMPORTER_H
+

+ 351 - 0
thirdparty/assimp/code/FBXMaterial.cpp

@@ -0,0 +1,351 @@
+/*
+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  FBXMaterial.cpp
+ *  @brief Assimp::FBX::Material and Assimp::FBX::Texture implementation
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXParser.h"
+#include "FBXDocument.h"
+#include "FBXImporter.h"
+#include "FBXImportSettings.h"
+#include "FBXDocumentUtil.h"
+#include "FBXProperties.h"
+#include <assimp/ByteSwapper.h>
+
+#include <algorithm> // std::transform
+
+namespace Assimp {
+namespace FBX {
+
+    using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Material::Material(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: Object(id,element,name)
+{
+    const Scope& sc = GetRequiredScope(element);
+
+    const Element* const ShadingModel = sc["ShadingModel"];
+    const Element* const MultiLayer = sc["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;
+
+    // lower-case shading because Blender (for example) writes "Phong"
+    std::transform(shading.begin(), shading.end(), shading.begin(), ::tolower);
+    if(shading == "phong") {
+        templateName = "Material.FbxSurfacePhong";
+    }
+    else if(shading == "lambert") {
+        templateName = "Material.FbxSurfaceLambert";
+    }
+    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;
+        }
+
+        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);
+        if(!tex) {
+            const LayeredTexture* const layeredTexture = dynamic_cast<const 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*)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()
+{
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Texture::Texture(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: Object(id,element,name)
+, uvScaling(1.0f,1.0f)
+, media(0)
+{
+    const Scope& sc = GetRequiredScope(element);
+
+    const Element* const Type = sc["Type"];
+    const Element* const FileName = sc["FileName"];
+    const Element* const RelativeFilename = sc["RelativeFilename"];
+    const Element* const ModelUVTranslation = sc["ModelUVTranslation"];
+    const Element* const ModelUVScaling = sc["ModelUVScaling"];
+    const Element* const Texture_Alpha_Source = sc["Texture_Alpha_Source"];
+    const Element* const Cropping = sc["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 = aiVector2D(ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,0)),
+            ParseTokenAsFloat(GetRequiredToken(*ModelUVTranslation,1))
+        );
+    }
+
+    if(ModelUVScaling) {
+        uvScaling = aiVector2D(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);
+
+    // 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()
+{
+
+}
+
+LayeredTexture::LayeredTexture(uint64_t id, const Element& element, const Document& /*doc*/, const std::string& name)
+: Object(id,element,name)
+,blendMode(BlendMode_Modulate)
+,alpha(1)
+{
+    const Scope& sc = GetRequiredScope(element);
+
+    const Element* const BlendModes = sc["BlendModes"];
+    const Element* const Alphas = sc["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 Element& element, const Document& doc, const std::string& name)
+: Object(id,element,name)
+, contentLength(0)
+, content(0)
+{
+    const Scope& sc = GetRequiredScope(element);
+
+    const Element* const Type = sc["Type"];
+    const Element* const FileName = sc.FindElementCaseInsensitive("FileName");  //some files retain the information as "Filename", others "FileName", who knows
+    const Element* const RelativeFilename = sc["RelativeFilename"];
+    const Element* const Content = sc["Content"];
+
+    if(Type) {
+        type = ParseTokenAsString(GetRequiredToken(*Type,0));
+    }
+
+    if(FileName) {
+        fileName = ParseTokenAsString(GetRequiredToken(*FileName,0));
+    }
+
+    if(RelativeFilename) {
+        relativeFileName = ParseTokenAsString(GetRequiredToken(*RelativeFilename,0));
+    }
+
+    if(Content) {
+        //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()) {
+                DOMWarning("video content is not binary data, ignoring", &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 (const runtime_error& runtimeError)
+        {
+            //we don't need the content data for contents that has already been loaded
+            ASSIMP_LOG_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;
+    }
+}
+
+} //!FBX
+} //!Assimp
+
+#endif

+ 711 - 0
thirdparty/assimp/code/FBXMeshGeometry.cpp

@@ -0,0 +1,711 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include <functional>
+
+#include "FBXMeshGeometry.h"
+#include "FBXDocument.h"
+#include "FBXImporter.h"
+#include "FBXImportSettings.h"
+#include "FBXDocumentUtil.h"
+
+
+namespace Assimp {
+namespace FBX {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Geometry::Geometry(uint64_t id, const Element& 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* const sk = ProcessSimpleConnection<Skin>(*con, false, "Skin -> Geometry", element);
+        if(sk) {
+            skin = sk;
+        }
+        const BlendShape* const bsp = ProcessSimpleConnection<BlendShape>(*con, false, "BlendShape -> Geometry", element);
+        if (bsp) {
+            blendShapes.push_back(bsp);
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+Geometry::~Geometry()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<const BlendShape*>& Geometry::GetBlendShapes() const {
+    return blendShapes;
+}
+
+// ------------------------------------------------------------------------------------------------
+const Skin* Geometry::DeformerSkin() const {
+    return skin;
+}
+
+// ------------------------------------------------------------------------------------------------
+MeshGeometry::MeshGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
+: Geometry(id, element,name, doc)
+{
+    const Scope* sc = element.Compound();
+    if (!sc) {
+        DOMError("failed to read Geometry object (class: Mesh), no data scope found");
+    }
+
+    // must have Mesh elements:
+    const Element& Vertices = GetRequiredElement(*sc,"Vertices",&element);
+    const Element& PolygonVertexIndex = GetRequiredElement(*sc,"PolygonVertexIndex",&element);
+
+    // optional Mesh elements:
+    const ElementCollection& Layer = sc->GetCollection("Layer");
+
+    std::vector<aiVector3D> tempVerts;
+    ParseVectorDataArray(tempVerts,Vertices);
+
+    if(tempVerts.empty()) {
+        FBXImporter::LogWarn("encountered mesh with no vertices");
+        return;
+    }
+
+    std::vector<int> tempFaces;
+    ParseVectorDataArray(tempFaces,PolygonVertexIndex);
+
+    if(tempFaces.empty()) {
+        FBXImporter::LogWarn("encountered mesh with no faces");
+        return;
+    }
+
+    m_vertices.reserve(tempFaces.size());
+    m_faces.reserve(tempFaces.size() / 3);
+
+    m_mapping_offsets.resize(tempVerts.size());
+    m_mapping_counts.resize(tempVerts.size(),0);
+    m_mappings.resize(tempFaces.size());
+
+    const size_t vertex_count = tempVerts.size();
+
+    // generate output vertices, computing an adjacency table to
+    // preserve the mapping from fbx indices to *this* indexing.
+    unsigned int count = 0;
+    for(int index : tempFaces) {
+        const int absi = index < 0 ? (-index - 1) : index;
+        if(static_cast<size_t>(absi) >= vertex_count) {
+            DOMError("polygon vertex index out of range",&PolygonVertexIndex);
+        }
+
+        m_vertices.push_back(tempVerts[absi]);
+        ++count;
+
+        ++m_mapping_counts[absi];
+
+        if (index < 0) {
+            m_faces.push_back(count);
+            count = 0;
+        }
+    }
+
+    unsigned int cursor = 0;
+    for (size_t i = 0, e = tempVerts.size(); i < e; ++i) {
+        m_mapping_offsets[i] = cursor;
+        cursor += m_mapping_counts[i];
+
+        m_mapping_counts[i] = 0;
+    }
+
+    cursor = 0;
+    for(int index : tempFaces) {
+        const int absi = index < 0 ? (-index - 1) : index;
+        m_mappings[m_mapping_offsets[absi] + m_mapping_counts[absi]++] = cursor++;
+    }
+
+    // if settings.readAllLayers is true:
+    //  * read all layers, try to load as many vertex channels as possible
+    // if settings.readAllLayers is false:
+    //  * read only the layer with index 0, but warn about any further layers
+    for (ElementMap::const_iterator it = Layer.first; it != Layer.second; ++it) {
+        const TokenList& tokens = (*it).second->Tokens();
+
+        const char* err;
+        const int index = ParseTokenAsInt(*tokens[0], err);
+        if(err) {
+            DOMError(err,&element);
+        }
+
+        if(doc.Settings().readAllLayers || index == 0) {
+            const Scope& layer = GetRequiredScope(*(*it).second);
+            ReadLayer(layer);
+        }
+        else {
+            FBXImporter::LogWarn("ignoring additional geometry layers");
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+MeshGeometry::~MeshGeometry() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& MeshGeometry::GetVertices() const {
+    return m_vertices;
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& MeshGeometry::GetNormals() const {
+    return m_normals;
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& MeshGeometry::GetTangents() const {
+    return m_tangents;
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& MeshGeometry::GetBinormals() const {
+    return m_binormals;
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<unsigned int>& MeshGeometry::GetFaceIndexCounts() const {
+    return m_faces;
+}
+
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector2D>& MeshGeometry::GetTextureCoords( unsigned int index ) const {
+    static const std::vector<aiVector2D> empty;
+    return index >= AI_MAX_NUMBER_OF_TEXTURECOORDS ? empty : m_uvs[ index ];
+}
+
+std::string MeshGeometry::GetTextureCoordChannelName( unsigned int index ) const {
+    return index >= AI_MAX_NUMBER_OF_TEXTURECOORDS ? "" : m_uvNames[ index ];
+}
+
+const std::vector<aiColor4D>& MeshGeometry::GetVertexColors( unsigned int index ) const {
+    static const std::vector<aiColor4D> empty;
+    return index >= AI_MAX_NUMBER_OF_COLOR_SETS ? empty : m_colors[ index ];
+}
+
+const MatIndexArray& MeshGeometry::GetMaterialIndices() const {
+    return m_materials;
+}
+// ------------------------------------------------------------------------------------------------
+const unsigned int* MeshGeometry::ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const {
+    if ( in_index >= m_mapping_counts.size() ) {
+        return NULL;
+    }
+
+    ai_assert( m_mapping_counts.size() == m_mapping_offsets.size() );
+    count = m_mapping_counts[ in_index ];
+
+    ai_assert( m_mapping_offsets[ in_index ] + count <= m_mappings.size() );
+
+    return &m_mappings[ m_mapping_offsets[ in_index ] ];
+}
+
+// ------------------------------------------------------------------------------------------------
+unsigned int MeshGeometry::FaceForVertexIndex( unsigned int in_index ) const {
+    ai_assert( in_index < m_vertices.size() );
+
+    // in the current conversion pattern this will only be needed if
+    // weights are present, so no need to always pre-compute this table
+    if ( m_facesVertexStartIndices.empty() ) {
+        m_facesVertexStartIndices.resize( m_faces.size() + 1, 0 );
+
+        std::partial_sum( m_faces.begin(), m_faces.end(), m_facesVertexStartIndices.begin() + 1 );
+        m_facesVertexStartIndices.pop_back();
+    }
+
+    ai_assert( m_facesVertexStartIndices.size() == m_faces.size() );
+    const std::vector<unsigned int>::iterator it = std::upper_bound(
+        m_facesVertexStartIndices.begin(),
+        m_facesVertexStartIndices.end(),
+        in_index
+        );
+
+    return static_cast< unsigned int >( std::distance( m_facesVertexStartIndices.begin(), it - 1 ) );
+}
+
+// ------------------------------------------------------------------------------------------------
+void MeshGeometry::ReadLayer(const Scope& layer)
+{
+    const ElementCollection& LayerElement = layer.GetCollection("LayerElement");
+    for (ElementMap::const_iterator eit = LayerElement.first; eit != LayerElement.second; ++eit) {
+        const Scope& elayer = GetRequiredScope(*(*eit).second);
+
+        ReadLayerElement(elayer);
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+void MeshGeometry::ReadLayerElement(const Scope& layerElement)
+{
+    const Element& Type = GetRequiredElement(layerElement,"Type");
+    const Element& TypedIndex = GetRequiredElement(layerElement,"TypedIndex");
+
+    const std::string& type = ParseTokenAsString(GetRequiredToken(Type,0));
+    const int typedIndex = ParseTokenAsInt(GetRequiredToken(TypedIndex,0));
+
+    const Scope& top = GetRequiredScope(element);
+    const ElementCollection candidates = top.GetCollection(type);
+
+    for (ElementMap::const_iterator it = candidates.first; it != candidates.second; ++it) {
+        const int index = ParseTokenAsInt(GetRequiredToken(*(*it).second,0));
+        if(index == typedIndex) {
+            ReadVertexData(type,typedIndex,GetRequiredScope(*(*it).second));
+            return;
+        }
+    }
+
+    FBXImporter::LogError(Formatter::format("failed to resolve vertex layer element: ")
+        << type << ", index: " << typedIndex);
+}
+
+// ------------------------------------------------------------------------------------------------
+void MeshGeometry::ReadVertexData(const std::string& type, int index, const Scope& source)
+{
+    const std::string& MappingInformationType = ParseTokenAsString(GetRequiredToken(
+        GetRequiredElement(source,"MappingInformationType"),0)
+    );
+
+    const std::string& ReferenceInformationType = ParseTokenAsString(GetRequiredToken(
+        GetRequiredElement(source,"ReferenceInformationType"),0)
+    );
+
+    if (type == "LayerElementUV") {
+        if(index >= AI_MAX_NUMBER_OF_TEXTURECOORDS) {
+            FBXImporter::LogError(Formatter::format("ignoring UV layer, maximum number of UV channels exceeded: ")
+                << index << " (limit is " << AI_MAX_NUMBER_OF_TEXTURECOORDS << ")" );
+            return;
+        }
+
+        const Element* Name = source["Name"];
+        m_uvNames[index] = "";
+        if(Name) {
+            m_uvNames[index] = ParseTokenAsString(GetRequiredToken(*Name,0));
+        }
+
+        ReadVertexDataUV(m_uvs[index],source,
+            MappingInformationType,
+            ReferenceInformationType
+        );
+    }
+    else if (type == "LayerElementMaterial") {
+        if (m_materials.size() > 0) {
+            FBXImporter::LogError("ignoring additional material layer");
+            return;
+        }
+
+        std::vector<int> temp_materials;
+
+        ReadVertexDataMaterials(temp_materials,source,
+            MappingInformationType,
+            ReferenceInformationType
+        );
+
+        // sometimes, there will be only negative entries. Drop the material
+        // layer in such a case (I guess it means a default material should
+        // be used). This is what the converter would do anyway, and it
+        // avoids losing the material if there are more material layers
+        // coming of which at least one contains actual data (did observe
+        // that with one test file).
+        const size_t count_neg = std::count_if(temp_materials.begin(),temp_materials.end(),[](int n) { return n < 0; });
+        if(count_neg == temp_materials.size()) {
+            FBXImporter::LogWarn("ignoring dummy material layer (all entries -1)");
+            return;
+        }
+
+        std::swap(temp_materials, m_materials);
+    }
+    else if (type == "LayerElementNormal") {
+        if (m_normals.size() > 0) {
+            FBXImporter::LogError("ignoring additional normal layer");
+            return;
+        }
+
+        ReadVertexDataNormals(m_normals,source,
+            MappingInformationType,
+            ReferenceInformationType
+        );
+    }
+    else if (type == "LayerElementTangent") {
+        if (m_tangents.size() > 0) {
+            FBXImporter::LogError("ignoring additional tangent layer");
+            return;
+        }
+
+        ReadVertexDataTangents(m_tangents,source,
+            MappingInformationType,
+            ReferenceInformationType
+        );
+    }
+    else if (type == "LayerElementBinormal") {
+        if (m_binormals.size() > 0) {
+            FBXImporter::LogError("ignoring additional binormal layer");
+            return;
+        }
+
+        ReadVertexDataBinormals(m_binormals,source,
+            MappingInformationType,
+            ReferenceInformationType
+        );
+    }
+    else if (type == "LayerElementColor") {
+        if(index >= AI_MAX_NUMBER_OF_COLOR_SETS) {
+            FBXImporter::LogError(Formatter::format("ignoring vertex color layer, maximum number of color sets exceeded: ")
+                << index << " (limit is " << AI_MAX_NUMBER_OF_COLOR_SETS << ")" );
+            return;
+        }
+
+        ReadVertexDataColors(m_colors[index],source,
+            MappingInformationType,
+            ReferenceInformationType
+        );
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Lengthy utility function to read and resolve a FBX vertex data array - that is, the
+// output is in polygon vertex order. This logic is used for reading normals, UVs, colors,
+// tangents ..
+template <typename T>
+void ResolveVertexDataArray(std::vector<T>& data_out, const Scope& source,
+    const std::string& MappingInformationType,
+    const std::string& ReferenceInformationType,
+    const char* dataElementName,
+    const char* indexDataElementName,
+    size_t vertex_count,
+    const std::vector<unsigned int>& mapping_counts,
+    const std::vector<unsigned int>& mapping_offsets,
+    const std::vector<unsigned int>& mappings)
+{
+    bool isDirect = ReferenceInformationType == "Direct";
+    bool isIndexToDirect = ReferenceInformationType == "IndexToDirect";
+
+    // fall-back to direct data if there is no index data element
+    if ( isIndexToDirect && !HasElement( source, indexDataElementName ) ) {
+        isDirect = true;
+        isIndexToDirect = false;
+    }
+
+    // handle permutations of Mapping and Reference type - it would be nice to
+    // deal with this more elegantly and with less redundancy, but right
+    // now it seems unavoidable.
+    if (MappingInformationType == "ByVertice" && isDirect) {
+        if (!HasElement(source, dataElementName)) {
+            return;
+        }
+        std::vector<T> tempData;
+		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
+
+        data_out.resize(vertex_count);
+		for (size_t i = 0, e = tempData.size(); i < e; ++i) {
+
+            const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i];
+            for (unsigned int j = istart; j < iend; ++j) {
+				data_out[mappings[j]] = tempData[i];
+            }
+        }
+    }
+    else if (MappingInformationType == "ByVertice" && isIndexToDirect) {
+		std::vector<T> tempData;
+		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
+
+        data_out.resize(vertex_count);
+
+        std::vector<int> uvIndices;
+        ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName));
+        for (size_t i = 0, e = uvIndices.size(); i < e; ++i) {
+
+            const unsigned int istart = mapping_offsets[i], iend = istart + mapping_counts[i];
+            for (unsigned int j = istart; j < iend; ++j) {
+				if (static_cast<size_t>(uvIndices[i]) >= tempData.size()) {
+                    DOMError("index out of range",&GetRequiredElement(source,indexDataElementName));
+                }
+				data_out[mappings[j]] = tempData[uvIndices[i]];
+            }
+        }
+    }
+    else if (MappingInformationType == "ByPolygonVertex" && isDirect) {
+		std::vector<T> tempData;
+		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
+
+		if (tempData.size() != vertex_count) {
+            FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygon mapping: ")
+				<< tempData.size() << ", expected " << vertex_count
+            );
+            return;
+        }
+
+		data_out.swap(tempData);
+    }
+    else if (MappingInformationType == "ByPolygonVertex" && isIndexToDirect) {
+		std::vector<T> tempData;
+		ParseVectorDataArray(tempData, GetRequiredElement(source, dataElementName));
+
+        data_out.resize(vertex_count);
+
+        std::vector<int> uvIndices;
+        ParseVectorDataArray(uvIndices,GetRequiredElement(source,indexDataElementName));
+
+        if (uvIndices.size() != vertex_count) {
+            FBXImporter::LogError("length of input data unexpected for ByPolygonVertex mapping");
+            return;
+        }
+
+        const T empty;
+        unsigned int next = 0;
+        for(int i : uvIndices) {
+            if ( -1 == i ) {
+                data_out[ next++ ] = empty;
+                continue;
+            }
+            if (static_cast<size_t>(i) >= tempData.size()) {
+                DOMError("index out of range",&GetRequiredElement(source,indexDataElementName));
+            }
+
+			data_out[next++] = tempData[i];
+        }
+    }
+    else {
+        FBXImporter::LogError(Formatter::format("ignoring vertex data channel, access type not implemented: ")
+            << MappingInformationType << "," << ReferenceInformationType);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void MeshGeometry::ReadVertexDataNormals(std::vector<aiVector3D>& normals_out, const Scope& source,
+    const std::string& MappingInformationType,
+    const std::string& ReferenceInformationType)
+{
+    ResolveVertexDataArray(normals_out,source,MappingInformationType,ReferenceInformationType,
+        "Normals",
+        "NormalsIndex",
+        m_vertices.size(),
+        m_mapping_counts,
+        m_mapping_offsets,
+        m_mappings);
+}
+
+// ------------------------------------------------------------------------------------------------
+void MeshGeometry::ReadVertexDataUV(std::vector<aiVector2D>& uv_out, const Scope& source,
+    const std::string& MappingInformationType,
+    const std::string& ReferenceInformationType)
+{
+    ResolveVertexDataArray(uv_out,source,MappingInformationType,ReferenceInformationType,
+        "UV",
+        "UVIndex",
+        m_vertices.size(),
+        m_mapping_counts,
+        m_mapping_offsets,
+        m_mappings);
+}
+
+// ------------------------------------------------------------------------------------------------
+void MeshGeometry::ReadVertexDataColors(std::vector<aiColor4D>& colors_out, const Scope& source,
+    const std::string& MappingInformationType,
+    const std::string& ReferenceInformationType)
+{
+    ResolveVertexDataArray(colors_out,source,MappingInformationType,ReferenceInformationType,
+        "Colors",
+        "ColorIndex",
+        m_vertices.size(),
+        m_mapping_counts,
+        m_mapping_offsets,
+        m_mappings);
+}
+
+// ------------------------------------------------------------------------------------------------
+static const std::string TangentIndexToken = "TangentIndex";
+static const std::string TangentsIndexToken = "TangentsIndex";
+
+void MeshGeometry::ReadVertexDataTangents(std::vector<aiVector3D>& tangents_out, const Scope& source,
+    const std::string& MappingInformationType,
+    const std::string& ReferenceInformationType)
+{
+    const char * str = source.Elements().count( "Tangents" ) > 0 ? "Tangents" : "Tangent";
+    const char * strIdx = source.Elements().count( "Tangents" ) > 0 ? TangentsIndexToken.c_str() : TangentIndexToken.c_str();
+    ResolveVertexDataArray(tangents_out,source,MappingInformationType,ReferenceInformationType,
+        str,
+        strIdx,
+        m_vertices.size(),
+        m_mapping_counts,
+        m_mapping_offsets,
+        m_mappings);
+}
+
+// ------------------------------------------------------------------------------------------------
+static const std::string BinormalIndexToken = "BinormalIndex";
+static const std::string BinormalsIndexToken = "BinormalsIndex";
+
+void MeshGeometry::ReadVertexDataBinormals(std::vector<aiVector3D>& binormals_out, const Scope& source,
+    const std::string& MappingInformationType,
+    const std::string& ReferenceInformationType)
+{
+    const char * str = source.Elements().count( "Binormals" ) > 0 ? "Binormals" : "Binormal";
+    const char * strIdx = source.Elements().count( "Binormals" ) > 0 ? BinormalsIndexToken.c_str() : BinormalIndexToken.c_str();
+    ResolveVertexDataArray(binormals_out,source,MappingInformationType,ReferenceInformationType,
+        str,
+        strIdx,
+        m_vertices.size(),
+        m_mapping_counts,
+        m_mapping_offsets,
+        m_mappings);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+void MeshGeometry::ReadVertexDataMaterials(std::vector<int>& materials_out, const Scope& source,
+    const std::string& MappingInformationType,
+    const std::string& ReferenceInformationType)
+{
+    const size_t face_count = m_faces.size();
+    ai_assert(face_count);
+
+    // materials are handled separately. First of all, they are assigned per-face
+    // and not per polyvert. Secondly, ReferenceInformationType=IndexToDirect
+    // has a slightly different meaning for materials.
+    ParseVectorDataArray(materials_out,GetRequiredElement(source,"Materials"));
+
+    if (MappingInformationType == "AllSame") {
+        // easy - same material for all faces
+        if (materials_out.empty()) {
+            FBXImporter::LogError(Formatter::format("expected material index, ignoring"));
+            return;
+        }
+        else if (materials_out.size() > 1) {
+            FBXImporter::LogWarn(Formatter::format("expected only a single material index, ignoring all except the first one"));
+            materials_out.clear();
+        }
+
+        m_materials.assign(m_vertices.size(),materials_out[0]);
+    }
+    else if (MappingInformationType == "ByPolygon" && ReferenceInformationType == "IndexToDirect") {
+        m_materials.resize(face_count);
+
+        if(materials_out.size() != face_count) {
+            FBXImporter::LogError(Formatter::format("length of input data unexpected for ByPolygon mapping: ")
+                << materials_out.size() << ", expected " << face_count
+            );
+            return;
+        }
+    }
+    else {
+        FBXImporter::LogError(Formatter::format("ignoring material assignments, access type not implemented: ")
+            << MappingInformationType << "," << ReferenceInformationType);
+    }
+}
+// ------------------------------------------------------------------------------------------------
+ShapeGeometry::ShapeGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
+    : Geometry(id, element, name, doc)
+{
+    const Scope* sc = element.Compound();
+    if (!sc) {
+        DOMError("failed to read Geometry object (class: Shape), no data scope found");
+    }
+    const Element& Indexes = GetRequiredElement(*sc, "Indexes", &element);
+    const Element& Normals = GetRequiredElement(*sc, "Normals", &element);
+    const Element& Vertices = GetRequiredElement(*sc, "Vertices", &element);
+    ParseVectorDataArray(m_indices, Indexes);
+    ParseVectorDataArray(m_vertices, Vertices);
+    ParseVectorDataArray(m_normals, Normals);
+}
+
+// ------------------------------------------------------------------------------------------------
+ShapeGeometry::~ShapeGeometry() {
+    // empty
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& ShapeGeometry::GetVertices() const {
+    return m_vertices;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& ShapeGeometry::GetNormals() const {
+    return m_normals;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<unsigned int>& ShapeGeometry::GetIndices() const {
+    return m_indices;
+}
+// ------------------------------------------------------------------------------------------------
+LineGeometry::LineGeometry(uint64_t id, const Element& element, const std::string& name, const Document& doc)
+    : Geometry(id, element, name, doc)
+{
+    const Scope* sc = element.Compound();
+    if (!sc) {
+        DOMError("failed to read Geometry object (class: Line), no data scope found");
+    }
+    const Element& Points = GetRequiredElement(*sc, "Points", &element);
+    const Element& PointsIndex = GetRequiredElement(*sc, "PointsIndex", &element);
+    ParseVectorDataArray(m_vertices, Points);
+    ParseVectorDataArray(m_indices, PointsIndex);
+}
+
+// ------------------------------------------------------------------------------------------------
+LineGeometry::~LineGeometry() {
+    // empty
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<aiVector3D>& LineGeometry::GetVertices() const {
+    return m_vertices;
+}
+// ------------------------------------------------------------------------------------------------
+const std::vector<int>& LineGeometry::GetIndices() const {
+    return m_indices;
+}
+} // !FBX
+} // !Assimp
+#endif
+

+ 235 - 0
thirdparty/assimp/code/FBXMeshGeometry.h

@@ -0,0 +1,235 @@
+/*
+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  FBXImporter.h
+*  @brief Declaration of the FBX main importer class
+*/
+#ifndef INCLUDED_AI_FBX_MESHGEOMETRY_H
+#define INCLUDED_AI_FBX_MESHGEOMETRY_H
+
+#include "FBXParser.h"
+#include "FBXDocument.h"
+
+namespace Assimp {
+namespace FBX {
+
+/** 
+ *  DOM base class for all kinds of FBX geometry 
+ */
+class Geometry : public Object
+{
+public:
+    Geometry( uint64_t id, const Element& element, const std::string& name, const Document& doc );
+    virtual ~Geometry();
+
+    /** Get the Skin attached to this geometry or NULL */
+    const Skin* DeformerSkin() const;
+
+    /** Get the BlendShape attached to this geometry or NULL */
+    const std::vector<const BlendShape*>& GetBlendShapes() const;
+
+private:
+    const Skin* skin;
+    std::vector<const BlendShape*> blendShapes;
+
+};
+
+typedef std::vector<int> MatIndexArray;
+
+
+/** 
+ *  DOM class for FBX geometry of type "Mesh"
+ */
+class MeshGeometry : public Geometry
+{
+public:
+    /** The class constructor */
+    MeshGeometry( uint64_t id, const Element& element, const std::string& name, const Document& doc );
+    
+    /** The class destructor */
+    virtual ~MeshGeometry();
+
+    /** Get a list of all vertex points, non-unique*/
+    const std::vector<aiVector3D>& GetVertices() const;
+
+    /** Get a list of all vertex normals or an empty array if
+    *  no normals are specified. */
+    const std::vector<aiVector3D>& GetNormals() const;
+
+    /** Get a list of all vertex tangents or an empty array
+    *  if no tangents are specified */
+    const std::vector<aiVector3D>& GetTangents() const;
+
+    /** Get a list of all vertex bi-normals or an empty array
+    *  if no bi-normals are specified */
+    const std::vector<aiVector3D>& GetBinormals() const;
+
+    /** Return list of faces - each entry denotes a face and specifies
+    *  how many vertices it has. Vertices are taken from the
+    *  vertex data arrays in sequential order. */
+    const std::vector<unsigned int>& GetFaceIndexCounts() const;
+
+    /** Get a UV coordinate slot, returns an empty array if
+    *  the requested slot does not exist. */
+    const std::vector<aiVector2D>& GetTextureCoords( unsigned int index ) const;
+
+    /** Get a UV coordinate slot, returns an empty array if
+    *  the requested slot does not exist. */
+    std::string GetTextureCoordChannelName( unsigned int index ) const;
+
+    /** Get a vertex color coordinate slot, returns an empty array if
+    *  the requested slot does not exist. */
+    const std::vector<aiColor4D>& GetVertexColors( unsigned int index ) const;
+
+    /** Get per-face-vertex material assignments */
+    const MatIndexArray& GetMaterialIndices() const;
+
+    /** Convert from a fbx file vertex index (for example from a #Cluster weight) or NULL
+    * if the vertex index is not valid. */
+    const unsigned int* ToOutputVertexIndex( unsigned int in_index, unsigned int& count ) const;
+
+    /** Determine the face to which a particular output vertex index belongs.
+    *  This mapping is always unique. */
+    unsigned int FaceForVertexIndex( unsigned int in_index ) const;
+private:
+    void ReadLayer( const Scope& layer );
+    void ReadLayerElement( const Scope& layerElement );
+    void ReadVertexData( const std::string& type, int index, const Scope& source );
+
+    void ReadVertexDataUV( std::vector<aiVector2D>& uv_out, const Scope& source,
+        const std::string& MappingInformationType,
+        const std::string& ReferenceInformationType );
+
+    void ReadVertexDataNormals( std::vector<aiVector3D>& normals_out, const Scope& source,
+        const std::string& MappingInformationType,
+        const std::string& ReferenceInformationType );
+
+    void ReadVertexDataColors( std::vector<aiColor4D>& colors_out, const Scope& source,
+        const std::string& MappingInformationType,
+        const std::string& ReferenceInformationType );
+
+    void ReadVertexDataTangents( std::vector<aiVector3D>& tangents_out, const Scope& source,
+        const std::string& MappingInformationType,
+        const std::string& ReferenceInformationType );
+
+    void ReadVertexDataBinormals( std::vector<aiVector3D>& binormals_out, const Scope& source,
+        const std::string& MappingInformationType,
+        const std::string& ReferenceInformationType );
+
+    void ReadVertexDataMaterials( MatIndexArray& materials_out, const Scope& source,
+        const std::string& MappingInformationType,
+        const std::string& ReferenceInformationType );
+
+private:
+    // cached data arrays
+    MatIndexArray m_materials;
+    std::vector<aiVector3D> m_vertices;
+    std::vector<unsigned int> m_faces;
+    mutable std::vector<unsigned int> m_facesVertexStartIndices;
+    std::vector<aiVector3D> m_tangents;
+    std::vector<aiVector3D> m_binormals;
+    std::vector<aiVector3D> m_normals;
+
+    std::string m_uvNames[ AI_MAX_NUMBER_OF_TEXTURECOORDS ];
+    std::vector<aiVector2D> m_uvs[ AI_MAX_NUMBER_OF_TEXTURECOORDS ];
+    std::vector<aiColor4D> m_colors[ AI_MAX_NUMBER_OF_COLOR_SETS ];
+
+    std::vector<unsigned int> m_mapping_counts;
+    std::vector<unsigned int> m_mapping_offsets;
+    std::vector<unsigned int> m_mappings;
+};
+
+/**
+*  DOM class for FBX geometry of type "Shape"
+*/
+class ShapeGeometry : public Geometry
+{
+public:
+    /** The class constructor */
+    ShapeGeometry(uint64_t id, const Element& 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<aiVector3D>& GetVertices() const;
+
+    /** Get a list of all vertex normals or an empty array if
+    *  no normals are specified. */
+    const std::vector<aiVector3D>& GetNormals() const;
+
+    /** Return list of vertex indices. */
+    const std::vector<unsigned int>& GetIndices() const;
+
+private:
+    std::vector<aiVector3D> m_vertices;
+    std::vector<aiVector3D> 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 Element& 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<aiVector3D>& GetVertices() const;
+
+    /** Return list of vertex indices. */
+    const std::vector<int>& GetIndices() const;
+
+private:
+    std::vector<aiVector3D> m_vertices;
+    std::vector<int> m_indices;
+};
+
+}
+}
+
+#endif // INCLUDED_AI_FBX_MESHGEOMETRY_H
+

+ 153 - 0
thirdparty/assimp/code/FBXModel.cpp

@@ -0,0 +1,153 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXParser.h"
+#include "FBXMeshGeometry.h"
+#include "FBXDocument.h"
+#include "FBXImporter.h"
+#include "FBXDocumentUtil.h"
+
+namespace Assimp {
+namespace FBX {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+Model::Model(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+    : Object(id,element,name)
+    , shading("Y")
+{
+    const Scope& sc = GetRequiredScope(element);
+    const Element* const Shading = sc["Shading"];
+    const Element* const Culling = sc["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()
+{
+
+}
+
+// ------------------------------------------------------------------------------------------------
+void Model::ResolveLinks(const Element& 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;
+}
+
+
+} //!FBX
+} //!Assimp
+
+#endif

+ 170 - 0
thirdparty/assimp/code/FBXNodeAttribute.cpp

@@ -0,0 +1,170 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXParser.h"
+#include "FBXDocument.h"
+#include "FBXImporter.h"
+#include "FBXDocumentUtil.h"
+
+namespace Assimp {
+namespace FBX {
+
+using namespace Util;
+
+// ------------------------------------------------------------------------------------------------
+NodeAttribute::NodeAttribute(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: Object(id,element,name)
+, props()
+{
+    const Scope& 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 Element& element, const Document& doc, const std::string& name)
+    : NodeAttribute(id,element,doc,name)
+{
+    const Scope& sc = GetRequiredScope(element);
+    const Element* const CameraId = sc["CameraId"];
+    const Element* const CameraName = sc["CameraName"];
+    const Element* const CameraIndexName = sc["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 Element& element, const Document& doc, const std::string& name)
+: NodeAttribute(id,element,doc,name)
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Camera::~Camera()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+Light::Light(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: NodeAttribute(id,element,doc,name)
+{
+    // empty
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Light::~Light()
+{
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Null::Null(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: NodeAttribute(id,element,doc,name)
+{
+
+}
+
+
+// ------------------------------------------------------------------------------------------------
+Null::~Null()
+{
+
+}
+
+
+// ------------------------------------------------------------------------------------------------
+LimbNode::LimbNode(uint64_t id, const Element& element, const Document& doc, const std::string& name)
+: NodeAttribute(id,element,doc,name)
+{
+
+}
+
+
+// ------------------------------------------------------------------------------------------------
+LimbNode::~LimbNode()
+{
+
+}
+
+}
+}
+
+#endif

+ 1313 - 0
thirdparty/assimp/code/FBXParser.cpp

@@ -0,0 +1,1313 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#ifdef ASSIMP_BUILD_NO_OWN_ZLIB
+#   include <zlib.h>
+#else
+#   include "../contrib/zlib/zlib.h"
+#endif
+
+#include "FBXTokenizer.h"
+#include "FBXParser.h"
+#include "FBXUtil.h"
+
+#include <assimp/ParsingUtils.h>
+#include <assimp/fast_atof.h>
+#include <assimp/ByteSwapper.h>
+
+#include <iostream>
+
+using namespace Assimp;
+using namespace Assimp::FBX;
+
+namespace {
+
+    // ------------------------------------------------------------------------------------------------
+    // signal parse error, this is always unrecoverable. Throws DeadlyImportError.
+    AI_WONT_RETURN void ParseError(const std::string& message, const Token& token) AI_WONT_RETURN_SUFFIX;
+    AI_WONT_RETURN void ParseError(const std::string& message, const Token& token)
+    {
+        throw DeadlyImportError(Util::AddTokenText("FBX-Parser",message,&token));
+    }
+
+    // ------------------------------------------------------------------------------------------------
+    AI_WONT_RETURN void ParseError(const std::string& message, const Element* element = NULL) AI_WONT_RETURN_SUFFIX;
+    AI_WONT_RETURN void ParseError(const std::string& message, const Element* element)
+    {
+        if(element) {
+            ParseError(message,element->KeyToken());
+        }
+        throw DeadlyImportError("FBX-Parser " + message);
+    }
+
+
+    // ------------------------------------------------------------------------------------------------
+    void ParseError(const std::string& message, TokenPtr token)
+    {
+        if(token) {
+            ParseError(message, *token);
+        }
+        ParseError(message);
+    }
+
+    // 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 Assimp {
+namespace FBX {
+
+// ------------------------------------------------------------------------------------------------
+Element::Element(const Token& key_token, Parser& parser)
+: key_token(key_token)
+{
+    TokenPtr n = NULL;
+    do {
+        n = parser.AdvanceToNextToken();
+        if(!n) {
+            ParseError("unexpected end of file, expected closing bracket",parser.LastToken());
+        }
+
+        if (n->Type() == TokenType_DATA) {
+            tokens.push_back(n);
+			TokenPtr prev = n;
+            n = parser.AdvanceToNextToken();
+            if(!n) {
+                ParseError("unexpected end of file, expected bracket, comma or key",parser.LastToken());
+            }
+
+			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) {
+                ParseError("unexpected token; expected bracket, comma or key",n);
+            }
+        }
+
+        if (n->Type() == TokenType_OPEN_BRACKET) {
+            compound.reset(new Scope(parser));
+
+            // current token should be a TOK_CLOSE_BRACKET
+            n = parser.CurrentToken();
+            ai_assert(n);
+
+            if (n->Type() != TokenType_CLOSE_BRACKET) {
+                ParseError("expected closing bracket",n);
+            }
+
+            parser.AdvanceToNextToken();
+            return;
+        }
+    }
+    while(n->Type() != TokenType_KEY && n->Type() != TokenType_CLOSE_BRACKET);
+}
+
+// ------------------------------------------------------------------------------------------------
+Element::~Element()
+{
+     // no need to delete tokens, they are owned by the parser
+}
+
+// ------------------------------------------------------------------------------------------------
+Scope::Scope(Parser& parser,bool topLevel)
+{
+    if(!topLevel) {
+        TokenPtr t = parser.CurrentToken();
+        if (t->Type() != TokenType_OPEN_BRACKET) {
+            ParseError("expected open bracket",t);
+        }
+    }
+
+    TokenPtr n = parser.AdvanceToNextToken();
+    if(n == NULL) {
+        ParseError("unexpected end of file");
+    }
+
+    // note: empty scopes are allowed
+    while(n->Type() != TokenType_CLOSE_BRACKET) {
+        if (n->Type() != TokenType_KEY) {
+            ParseError("unexpected token, expected TOK_KEY",n);
+        }
+
+        const std::string& str = n->StringContents();
+        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 == NULL) {
+            if (topLevel) {
+                return;
+            }
+            ParseError("unexpected end of file",parser.LastToken());
+        }
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+Scope::~Scope()
+{
+    for(ElementMap::value_type& v : elements) {
+        delete v.second;
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+Parser::Parser (const TokenList& tokens, bool is_binary)
+: tokens(tokens)
+, last()
+, current()
+, cursor(tokens.begin())
+, is_binary(is_binary)
+{
+    root.reset(new Scope(*this,true));
+}
+
+// ------------------------------------------------------------------------------------------------
+Parser::~Parser()
+{
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+TokenPtr Parser::AdvanceToNextToken()
+{
+    last = current;
+    if (cursor == tokens.end()) {
+        current = NULL;
+    } else {
+        current = *cursor++;
+    }
+    return current;
+}
+
+// ------------------------------------------------------------------------------------------------
+TokenPtr Parser::CurrentToken() const
+{
+    return current;
+}
+
+// ------------------------------------------------------------------------------------------------
+TokenPtr Parser::LastToken() const
+{
+    return last;
+}
+
+// ------------------------------------------------------------------------------------------------
+uint64_t ParseTokenAsID(const Token& t, const char*& err_out)
+{
+    err_out = NULL;
+
+    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;
+        }
+
+        BE_NCONST uint64_t id = SafeParse<uint64_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);
+
+    const char* out = nullptr;
+    const uint64_t id = strtoul10_64(t.begin(),&out,&length);
+    if (out > t.end()) {
+        err_out = "failed to parse ID (text)";
+        return 0L;
+    }
+
+    return id;
+}
+
+// ------------------------------------------------------------------------------------------------
+size_t ParseTokenAsDim(const Token& t, const char*& err_out)
+{
+    // same as ID parsing, except there is a trailing asterisk
+    err_out = NULL;
+
+    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;
+        }
+
+        BE_NCONST 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;
+    const size_t id = static_cast<size_t>(strtoul10_64(t.begin() + 1,&out,&length));
+    if (out > t.end()) {
+        err_out = "failed to parse ID";
+        return 0;
+    }
+
+    return id;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+float ParseTokenAsFloat(const Token& t, const char*& err_out)
+{
+    err_out = NULL;
+
+    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 fast_atof(temp);
+}
+
+
+// ------------------------------------------------------------------------------------------------
+int ParseTokenAsInt(const Token& t, const char*& err_out)
+{
+    err_out = NULL;
+
+    if (t.Type() != TokenType_DATA) {
+        err_out = "expected TOK_DATA token";
+        return 0;
+    }
+
+    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;
+        }
+
+        BE_NCONST int32_t ival = SafeParse<int32_t>(data+1, t.end());
+        AI_SWAP4(ival);
+        return static_cast<int>(ival);
+    }
+
+    ai_assert(static_cast<size_t>(t.end() - t.begin()) > 0);
+
+    const char* out;
+    const int intval = strtol10(t.begin(),&out);
+    if (out != t.end()) {
+        err_out = "failed to parse ID";
+        return 0;
+    }
+
+    return intval;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+int64_t ParseTokenAsInt64(const Token& t, const char*& err_out)
+{
+    err_out = NULL;
+
+    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;
+        }
+
+        BE_NCONST 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);
+
+    const char* out = nullptr;
+    const int64_t id = strtol10_64(t.begin(), &out, &length);
+    if (out > t.end()) {
+        err_out = "failed to parse Int64 (text)";
+        return 0L;
+    }
+
+    return id;
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string ParseTokenAsString(const Token& t, const char*& err_out)
+{
+    err_out = NULL;
+
+    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 S(tring), unexpected data type (binary)";
+            return "";
+        }
+
+        // read string length
+        BE_NCONST 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 Element& el)
+{
+    if (static_cast<size_t>(end-data) < 5) {
+        ParseError("binary data array is too short, need five (5) bytes for type signature and element count",&el);
+    }
+
+    // data type
+    type = *data;
+
+    // read number of elements
+    BE_NCONST 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 Element& /*el*/)
+{
+    BE_NCONST uint32_t encmode = SafeParse<uint32_t>(data, end);
+    AI_SWAP4(encmode);
+    data += 4;
+
+    // next comes the compressed length
+    BE_NCONST 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;
+
+        default:
+            ai_assert(false);
+    };
+
+    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)) {
+            ParseError("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) {
+            ParseError("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);
+}
+
+} // !anon
+
+
+// ------------------------------------------------------------------------------------------------
+// read an array of float3 tuples
+void ParseVectorDataArray(std::vector<aiVector3D>& out, const Element& el)
+{
+    out.resize( 0 );
+
+    const TokenList& tok = el.Tokens();
+    if(tok.empty()) {
+        ParseError("unexpected empty element",&el);
+    }
+
+    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) {
+            ParseError("number of floats is not a multiple of three (3) (binary)",&el);
+        }
+
+        if(!count) {
+            return;
+        }
+
+        if (type != 'd' && type != 'f') {
+            ParseError("expected float or double array (binary)",&el);
+        }
+
+        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(aiVector3D(static_cast<float>(d[0]),
+                    static_cast<float>(d[1]),
+                    static_cast<float>(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(aiVector3D(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 Scope& scope = GetRequiredScope(el);
+    const Element& a = GetRequiredElement(scope,"a",&el);
+
+    if (a.Tokens().size() % 3 != 0) {
+        ParseError("number of floats is not a multiple of three (3)",&el);
+    }
+    for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) {
+        aiVector3D 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<aiColor4D>& out, const Element& el)
+{
+    out.resize( 0 );
+    const TokenList& tok = el.Tokens();
+    if(tok.empty()) {
+        ParseError("unexpected empty element",&el);
+    }
+
+    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) {
+            ParseError("number of floats is not a multiple of four (4) (binary)",&el);
+        }
+
+        if(!count) {
+            return;
+        }
+
+        if (type != 'd' && type != 'f') {
+            ParseError("expected float or double array (binary)",&el);
+        }
+
+        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(aiColor4D(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(aiColor4D(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 Scope& scope = GetRequiredScope(el);
+    const Element& a = GetRequiredElement(scope,"a",&el);
+
+    if (a.Tokens().size() % 4 != 0) {
+        ParseError("number of floats is not a multiple of four (4)",&el);
+    }
+    for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) {
+        aiColor4D 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<aiVector2D>& out, const Element& el)
+{
+    out.resize( 0 );
+    const TokenList& tok = el.Tokens();
+    if(tok.empty()) {
+        ParseError("unexpected empty element",&el);
+    }
+
+    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) {
+            ParseError("number of floats is not a multiple of two (2) (binary)",&el);
+        }
+
+        if(!count) {
+            return;
+        }
+
+        if (type != 'd' && type != 'f') {
+            ParseError("expected float or double array (binary)",&el);
+        }
+
+        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(aiVector2D(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(aiVector2D(f[0],f[1]));
+            }
+        }
+
+        return;
+    }
+
+    const size_t dim = ParseTokenAsDim(*tok[0]);
+
+    // see notes in ParseVectorDataArray() above
+    out.reserve(dim);
+
+    const Scope& scope = GetRequiredScope(el);
+    const Element& a = GetRequiredElement(scope,"a",&el);
+
+    if (a.Tokens().size() % 2 != 0) {
+        ParseError("number of floats is not a multiple of two (2)",&el);
+    }
+    for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end; ) {
+        aiVector2D 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 Element& el)
+{
+    out.resize( 0 );
+    const TokenList& tok = el.Tokens();
+    if(tok.empty()) {
+        ParseError("unexpected empty element",&el);
+    }
+
+    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') {
+            ParseError("expected int array (binary)",&el);
+        }
+
+        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) {
+            BE_NCONST 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 Scope& scope = GetRequiredScope(el);
+    const Element& 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 Element& el)
+{
+    out.resize( 0 );
+    const TokenList& tok = el.Tokens();
+    if(tok.empty()) {
+        ParseError("unexpected empty element",&el);
+    }
+
+    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') {
+            ParseError("expected float or double array (binary)",&el);
+        }
+
+        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 Scope& scope = GetRequiredScope(el);
+    const Element& 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 Element& el)
+{
+    out.resize( 0 );
+    const TokenList& tok = el.Tokens();
+    if(tok.empty()) {
+        ParseError("unexpected empty element",&el);
+    }
+
+    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') {
+            ParseError("expected (u)int array (binary)",&el);
+        }
+
+        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) {
+            BE_NCONST int32_t val = *ip;
+            if(val < 0) {
+                ParseError("encountered negative integer index (binary)");
+            }
+
+            AI_SWAP4(val);
+            out.push_back(val);
+        }
+
+        return;
+    }
+
+    const size_t dim = ParseTokenAsDim(*tok[0]);
+
+    // see notes in ParseVectorDataArray()
+    out.reserve(dim);
+
+    const Scope& scope = GetRequiredScope(el);
+    const Element& 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) {
+            ParseError("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 Element& el)
+{
+    out.resize( 0 );
+    const TokenList& tok = el.Tokens();
+    if(tok.empty()) {
+        ParseError("unexpected empty element",&el);
+    }
+
+    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') {
+            ParseError("expected long array (binary)",&el);
+        }
+
+        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) {
+            BE_NCONST 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 Scope& scope = GetRequiredScope(el);
+    const Element& 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 Element& el)
+{
+    out.resize( 0 );
+    const TokenList& tok = el.Tokens();
+    if (tok.empty()) {
+        ParseError("unexpected empty element", &el);
+    }
+
+    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') {
+            ParseError("expected long array (binary)", &el);
+        }
+
+        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) {
+            BE_NCONST 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 Scope& scope = GetRequiredScope(el);
+    const Element& a = GetRequiredElement(scope, "a", &el);
+
+    for (TokenList::const_iterator it = a.Tokens().begin(), end = a.Tokens().end(); it != end;) {
+        const int64_t ival = ParseTokenAsInt64(**it++);
+
+        out.push_back(ival);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+aiMatrix4x4 ReadMatrix(const Element& element)
+{
+    std::vector<float> values;
+    ParseVectorDataArray(values,element);
+
+    if(values.size() != 16) {
+        ParseError("expected 16 matrix elements");
+    }
+
+    aiMatrix4x4 result;
+
+
+    result.a1 = values[0];
+    result.a2 = values[1];
+    result.a3 = values[2];
+    result.a4 = values[3];
+
+    result.b1 = values[4];
+    result.b2 = values[5];
+    result.b3 = values[6];
+    result.b4 = values[7];
+
+    result.c1 = values[8];
+    result.c2 = values[9];
+    result.c3 = values[10];
+    result.c4 = values[11];
+
+    result.d1 = values[12];
+    result.d2 = values[13];
+    result.d3 = values[14];
+    result.d4 = values[15];
+
+    result.Transpose();
+    return result;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsString() with ParseError handling
+std::string ParseTokenAsString(const Token& t)
+{
+    const char* err;
+    const std::string& i = ParseTokenAsString(t,err);
+    if(err) {
+        ParseError(err,t);
+    }
+    return i;
+}
+
+bool HasElement( const Scope& sc, const std::string& index ) {
+    const Element* el = sc[ index ];
+    if ( nullptr == el ) {
+        return false;
+    }
+
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// extract a required element from a scope, abort if the element cannot be found
+const Element& GetRequiredElement(const Scope& sc, const std::string& index, const Element* element /*= NULL*/)
+{
+    const Element* el = sc[index];
+    if(!el) {
+        ParseError("did not find required element \"" + index + "\"",element);
+    }
+    return *el;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// extract required compound scope
+const Scope& GetRequiredScope(const Element& el)
+{
+    const Scope* const s = el.Compound();
+    if(!s) {
+        ParseError("expected compound scope",&el);
+    }
+
+    return *s;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// get token at a particular index
+const Token& GetRequiredToken(const Element& el, unsigned int index)
+{
+    const TokenList& t = el.Tokens();
+    if(index >= t.size()) {
+        ParseError(Formatter::format( "missing token at index " ) << index,&el);
+    }
+
+    return *t[index];
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsID() with ParseError handling
+uint64_t ParseTokenAsID(const Token& t)
+{
+    const char* err;
+    const uint64_t i = ParseTokenAsID(t,err);
+    if(err) {
+        ParseError(err,t);
+    }
+    return i;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsDim() with ParseError handling
+size_t ParseTokenAsDim(const Token& t)
+{
+    const char* err;
+    const size_t i = ParseTokenAsDim(t,err);
+    if(err) {
+        ParseError(err,t);
+    }
+    return i;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsFloat() with ParseError handling
+float ParseTokenAsFloat(const Token& t)
+{
+    const char* err;
+    const float i = ParseTokenAsFloat(t,err);
+    if(err) {
+        ParseError(err,t);
+    }
+    return i;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsInt() with ParseError handling
+int ParseTokenAsInt(const Token& t)
+{
+    const char* err;
+    const int i = ParseTokenAsInt(t,err);
+    if(err) {
+        ParseError(err,t);
+    }
+    return i;
+}
+
+
+
+// ------------------------------------------------------------------------------------------------
+// wrapper around ParseTokenAsInt64() with ParseError handling
+int64_t ParseTokenAsInt64(const Token& t)
+{
+    const char* err;
+    const int64_t i = ParseTokenAsInt64(t, err);
+    if (err) {
+        ParseError(err, t);
+    }
+    return i;
+}
+
+} // !FBX
+} // !Assimp
+
+#endif

+ 235 - 0
thirdparty/assimp/code/FBXParser.h

@@ -0,0 +1,235 @@
+/*
+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 INCLUDED_AI_FBX_PARSER_H
+#define INCLUDED_AI_FBX_PARSER_H
+
+#include <stdint.h>
+#include <map>
+#include <memory>
+#include <assimp/LogAux.h>
+#include <assimp/fast_atof.h>
+
+#include "FBXCompileConfig.h"
+#include "FBXTokenizer.h"
+
+namespace Assimp {
+namespace FBX {
+
+class Scope;
+class Parser;
+class Element;
+
+// XXX should use C++11's unique_ptr - but assimp's need to keep working with 03
+typedef std::vector< Scope* > ScopeList;
+typedef std::fbx_unordered_multimap< std::string, Element* > 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(const Token& key_token, Parser& parser);
+    ~Element();
+
+    const Scope* Compound() const {
+        return compound.get();
+    }
+
+    const Token& KeyToken() const {
+        return key_token;
+    }
+
+    const TokenList& Tokens() const {
+        return tokens;
+    }
+
+private:
+    const Token& key_token;
+    TokenList tokens;
+    std::unique_ptr<Scope> compound;
+};
+
+/** 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();
+
+    const Element* operator[] (const std::string& index) const {
+        ElementMap::const_iterator it = elements.find(index);
+        return it == elements.end() ? NULL : (*it).second;
+    }
+
+	const Element* FindElementCaseInsensitive(const std::string& elementName) const {
+		const char* elementNameCStr = elementName.c_str();
+		for (auto element = elements.begin(); element != elements.end(); ++element)
+		{
+			if (!ASSIMP_strincmp(element->first.c_str(), elementNameCStr, MAXLEN)) {
+				return element->second;
+			}
+		}
+		return NULL;
+	}
+
+    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();
+
+    const Scope& GetRootScope() const {
+        return *root.get();
+    }
+
+    bool IsBinary() const {
+        return is_binary;
+    }
+
+private:
+    friend class Scope;
+    friend class Element;
+
+    TokenPtr AdvanceToNextToken();
+    TokenPtr LastToken() const;
+    TokenPtr CurrentToken() const;
+
+private:
+    const TokenList& tokens;
+
+    TokenPtr last, current;
+    TokenList::const_iterator cursor;
+    std::unique_ptr<Scope> root;
+
+    const bool is_binary;
+};
+
+
+/* token parsing - this happens when building the DOM out of the parse-tree*/
+uint64_t ParseTokenAsID(const Token& t, const char*& err_out);
+size_t ParseTokenAsDim(const Token& t, const char*& err_out);
+
+float ParseTokenAsFloat(const Token& t, const char*& err_out);
+int ParseTokenAsInt(const Token& t, const char*& err_out);
+int64_t ParseTokenAsInt64(const Token& t, const char*& err_out);
+std::string ParseTokenAsString(const Token& t, const char*& err_out);
+
+/* wrapper around ParseTokenAsXXX() with DOMError handling */
+uint64_t ParseTokenAsID(const Token& t);
+size_t ParseTokenAsDim(const Token& t);
+float ParseTokenAsFloat(const Token& t);
+int ParseTokenAsInt(const Token& t);
+int64_t ParseTokenAsInt64(const Token& t);
+std::string ParseTokenAsString(const Token& t);
+
+/* read data arrays */
+void ParseVectorDataArray(std::vector<aiVector3D>& out, const Element& el);
+void ParseVectorDataArray(std::vector<aiColor4D>& out, const Element& el);
+void ParseVectorDataArray(std::vector<aiVector2D>& out, const Element& el);
+void ParseVectorDataArray(std::vector<int>& out, const Element& el);
+void ParseVectorDataArray(std::vector<float>& out, const Element& el);
+void ParseVectorDataArray(std::vector<unsigned int>& out, const Element& el);
+void ParseVectorDataArray(std::vector<uint64_t>& out, const Element& e);
+void ParseVectorDataArray(std::vector<int64_t>& out, const Element& el);
+
+bool HasElement( const Scope& sc, const std::string& index );
+
+// extract a required element from a scope, abort if the element cannot be found
+const Element& GetRequiredElement(const Scope& sc, const std::string& index, const Element* element = NULL);
+
+// extract required compound scope
+const Scope& GetRequiredScope(const Element& el);
+// get token at a particular index
+const Token& GetRequiredToken(const Element& el, unsigned int index);
+
+// read a 4x4 matrix from an array of 16 floats
+aiMatrix4x4 ReadMatrix(const Element& element);
+
+} // ! FBX
+} // ! Assimp
+
+#endif // ! INCLUDED_AI_FBX_PARSER_H

+ 235 - 0
thirdparty/assimp/code/FBXProperties.cpp

@@ -0,0 +1,235 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+#include "FBXTokenizer.h"
+#include "FBXParser.h"
+#include "FBXDocument.h"
+#include "FBXDocumentUtil.h"
+#include "FBXProperties.h"
+
+namespace Assimp {
+namespace FBX {
+
+    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.
+Property* ReadTypedProperty(const Element& 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<aiVector3D>(aiVector3D(
+            ParseTokenAsFloat(*tok[4]),
+            ParseTokenAsFloat(*tok[5]),
+            ParseTokenAsFloat(*tok[6]))
+        );
+    }
+    else if (!strcmp(cs,"double") || !strcmp(cs,"Number") || !strcmp(cs,"Float") || !strcmp(cs,"FieldOfView") || !strcmp( cs, "UnitScaleFactor" ) ) {
+        return new TypedProperty<float>(ParseTokenAsFloat(*tok[4]));
+    }
+    return NULL;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// 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]);
+}
+
+} //! anon
+
+
+// ------------------------------------------------------------------------------------------------
+PropertyTable::PropertyTable()
+: templateProps()
+, element()
+{
+}
+
+// ------------------------------------------------------------------------------------------------
+PropertyTable::PropertyTable(const Element& element, std::shared_ptr<const PropertyTable> templateProps)
+: templateProps(templateProps)
+, element(&element)
+{
+    const Scope& scope = GetRequiredScope(element);
+    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;
+        }
+
+        lazyProps[name] = v.second;
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+PropertyTable::~PropertyTable()
+{
+    for(PropertyMap::value_type& v : props) {
+        delete v.second;
+    }
+}
+
+
+// ------------------------------------------------------------------------------------------------
+const Property* 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 NULL;
+        }
+    }
+
+    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.
+        std::shared_ptr<Property> prop = std::shared_ptr<Property>(ReadTypedProperty(*element.second));
+
+        // Element could not be read. Skip it.
+        if (!prop) continue;
+
+        // Add to result
+        result[element.first] = prop;
+    }
+
+    return result;
+}
+
+} //! FBX
+} //! Assimp
+
+#endif

+ 185 - 0
thirdparty/assimp/code/FBXProperties.h

@@ -0,0 +1,185 @@
+/*
+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 INCLUDED_AI_FBX_PROPERTIES_H
+#define INCLUDED_AI_FBX_PROPERTIES_H
+
+#include "FBXCompileConfig.h"
+#include <memory>
+#include <string>
+
+namespace Assimp {
+namespace FBX {
+
+// 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;
+};
+
+
+typedef std::fbx_unordered_map<std::string,std::shared_ptr<Property> > DirectPropertyMap;
+typedef std::fbx_unordered_map<std::string,const Property*>            PropertyMap;
+typedef std::fbx_unordered_map<std::string,const Element*>             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 Element& element, std::shared_ptr<const PropertyTable> templateProps);
+    ~PropertyTable();
+
+    const Property* Get(const std::string& name) const;
+
+    // PropertyTable's need not be coupled with FBX elements so this can be NULL
+    const Element* GetElement() const {
+        return element;
+    }
+
+    const PropertyTable* TemplateProps() const {
+        return templateProps.get();
+    }
+
+    DirectPropertyMap GetUnparsedProperties() const;
+
+private:
+    LazyPropertyMap lazyProps;
+    mutable PropertyMap props;
+    const std::shared_ptr<const PropertyTable> templateProps;
+    const Element* const element;
+};
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+inline 
+T PropertyGet(const PropertyTable& in, const std::string& name, const T& defaultValue) {
+    const Property* const 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 ) {
+    const Property* 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();
+}
+
+} //! FBX
+} //! Assimp
+
+#endif // INCLUDED_AI_FBX_PROPERTIES_H

+ 248 - 0
thirdparty/assimp/code/FBXTokenizer.cpp

@@ -0,0 +1,248 @@
+/*
+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
+ */
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+// tab width for logging columns
+#define ASSIMP_FBX_TAB_WIDTH 4
+
+#include <assimp/ParsingUtils.h>
+
+#include "FBXTokenizer.h"
+#include "FBXUtil.h"
+#include <assimp/Exceptional.h>
+
+namespace Assimp {
+namespace FBX {
+
+// ------------------------------------------------------------------------------------------------
+Token::Token(const char* sbegin, const char* send, TokenType type, unsigned int line, unsigned int column)
+    :
+#ifdef DEBUG
+    contents(sbegin, static_cast<size_t>(send-sbegin)),
+#endif
+    sbegin(sbegin)
+    , send(send)
+    , type(type)
+    , line(line)
+    , column(column)
+{
+    ai_assert(sbegin);
+    ai_assert(send);
+
+    // tokens must be of non-zero length
+    ai_assert(static_cast<size_t>(send-sbegin) > 0);
+}
+
+// ------------------------------------------------------------------------------------------------
+Token::~Token()
+{
+}
+
+namespace {
+
+// ------------------------------------------------------------------------------------------------
+// signal tokenization error, this is always unrecoverable. Throws DeadlyImportError.
+AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int line, unsigned int column) AI_WONT_RETURN_SUFFIX;
+AI_WONT_RETURN void TokenizeError(const std::string& message, unsigned int line, unsigned int column)
+{
+    throw DeadlyImportError(Util::AddLineAndColumn("FBX-Tokenize",message,line,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 = NULL;
+}
+
+}
+
+// ------------------------------------------------------------------------------------------------
+void Tokenize(TokenList& output_tokens, const char* input)
+{
+    ai_assert(input);
+
+    // 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 = NULL, *token_end = NULL;
+    for (const char* cur = input;*cur;column += (*cur == '\t' ? ASSIMP_FBX_TAB_WIDTH : 1), ++cur) {
+        const char c = *cur;
+
+        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;
+        }
+    }
+}
+
+} // !FBX
+} // !Assimp
+
+#endif

+ 187 - 0
thirdparty/assimp/code/FBXTokenizer.h

@@ -0,0 +1,187 @@
+/*
+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 INCLUDED_AI_FBX_TOKENIZER_H
+#define INCLUDED_AI_FBX_TOKENIZER_H
+
+#include "FBXCompileConfig.h"
+#include <assimp/ai_assert.h>
+#include <vector>
+#include <string>
+
+namespace Assimp {
+namespace FBX {
+
+/** 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* sbegin, const char* send, TokenType type, unsigned int line, unsigned int column);
+
+    /** construct a binary token */
+    Token(const char* sbegin, const char* send, TokenType type, unsigned int 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;
+    }
+
+    unsigned int Offset() const {
+        ai_assert(IsBinary());
+        return offset;
+    }
+
+    unsigned int Line() const {
+        ai_assert(!IsBinary());
+        return line;
+    }
+
+    unsigned int Column() const {
+        ai_assert(!IsBinary());
+        return column;
+    }
+
+private:
+
+#ifdef DEBUG
+    // full string copy for the sole purpose that it nicely appears
+    // in msvc's debugger window.
+    const std::string contents;
+#endif
+
+
+    const char* const sbegin;
+    const char* const send;
+    const TokenType type;
+
+    union {
+        const unsigned int line;
+        unsigned int offset;
+    };
+    const unsigned int column;
+};
+
+// XXX should use C++11's unique_ptr - but assimp's need to keep working with 03
+typedef const 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.
+ * @throw DeadlyImportError if something goes wrong */
+void Tokenize(TokenList& output_tokens, const char* input);
+
+
+/** 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.
+ * @throw DeadlyImportError if something goes wrong */
+void TokenizeBinary(TokenList& output_tokens, const char* input, unsigned int length);
+
+
+} // ! FBX
+} // ! Assimp
+
+#endif // ! INCLUDED_AI_FBX_PARSER_H

+ 120 - 0
thirdparty/assimp/code/FBXUtil.cpp

@@ -0,0 +1,120 @@
+/*
+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 <assimp/TinyFormatter.h>
+#include <string>
+
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+
+namespace Assimp {
+namespace FBX {
+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 "";
+}
+
+
+// ------------------------------------------------------------------------------------------------
+std::string AddOffset(const std::string& prefix, const std::string& text, unsigned int offset)
+{
+    return static_cast<std::string>( (Formatter::format() << prefix << " (offset 0x" << std::hex << offset << ") " << text) );
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string AddLineAndColumn(const std::string& prefix, const std::string& text, unsigned int line, unsigned int column)
+{
+    return static_cast<std::string>( (Formatter::format() << prefix << " (line " << line << " <<  col " << column << ") " << text) );
+}
+
+// ------------------------------------------------------------------------------------------------
+std::string AddTokenText(const std::string& prefix, const std::string& text, const Token* tok)
+{
+    if(tok->IsBinary()) {
+        return static_cast<std::string>( (Formatter::format() << prefix <<
+            " (" << TokenTypeString(tok->Type()) <<
+            ", offset 0x" << std::hex << tok->Offset() << ") " <<
+            text) );
+    }
+
+    return static_cast<std::string>( (Formatter::format() << prefix <<
+        " (" << TokenTypeString(tok->Type()) <<
+        ", line " << tok->Line() <<
+        ", col " << tok->Column() << ") " <<
+        text) );
+}
+
+} // !Util
+} // !FBX
+} // !Assimp
+
+#endif

+ 105 - 0
thirdparty/assimp/code/FBXUtil.h

@@ -0,0 +1,105 @@
+/*
+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 INCLUDED_AI_FBX_UTIL_H
+#define INCLUDED_AI_FBX_UTIL_H
+
+#include "FBXCompileConfig.h"
+#include "FBXTokenizer.h"
+
+namespace Assimp {
+namespace FBX {
+
+
+namespace Util {
+
+
+/** helper for std::for_each to delete all heap-allocated items in a container */
+template<typename T>
+struct delete_fun
+{
+    void operator()(const volatile T* del) {
+        delete del;
+    }
+};
+
+/** Get a string representation for a #TokenType. */
+const char* TokenTypeString(TokenType t);
+
+
+
+/** Format log/error messages using a given offset in the source binary file
+ *
+ *  @param prefix Message prefix to be preprended to the location info.
+ *  @param text Message text
+ *  @param line Line index, 1-based
+ *  @param column Column index, 1-based
+ *  @return A string of the following format: {prefix} (offset 0x{offset}) {text}*/
+std::string AddOffset(const std::string& prefix, const std::string& text, unsigned int offset);
+
+
+/** Format log/error messages using a given line location in the source file.
+ *
+ *  @param prefix Message prefix to be preprended to the location info.
+ *  @param text Message text
+ *  @param line Line index, 1-based
+ *  @param column Column index, 1-based
+ *  @return A string of the following format: {prefix} (line {line}, col {column}) {text}*/
+std::string AddLineAndColumn(const std::string& prefix, const std::string& text, unsigned int line, unsigned int column);
+
+
+/** Format log/error messages using a given cursor token.
+ *
+ *  @param prefix Message prefix to be preprended to the location info.
+ *  @param text Message text
+ *  @param tok Token where parsing/processing stopped
+ *  @return A string of the following format: {prefix} ({token-type}, line {line}, col {column}) {text}*/
+std::string AddTokenText(const std::string& prefix, const std::string& text, const Token* tok);
+
+}
+}
+}
+
+#endif // ! INCLUDED_AI_FBX_UTIL_H

+ 1834 - 0
thirdparty/assimp/code/FIReader.cpp

@@ -0,0 +1,1834 @@
+/*
+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   FIReader.cpp
+/// \brief  Reader for Fast Infoset encoded binary XML files.
+/// \date   2017
+/// \author Patrick Daehne
+
+#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
+
+#include "FIReader.hpp"
+#include <assimp/StringUtils.h>
+
+// Workaround for issue #1361
+// https://github.com/assimp/assimp/issues/1361
+#ifdef __ANDROID__
+#  define _GLIBCXX_USE_C99 1
+#endif
+
+#include <assimp/Exceptional.h>
+#include <assimp/IOStream.hpp>
+#include <assimp/types.h>
+#include <assimp/MemoryIOWrapper.h>
+#include <assimp/irrXMLWrapper.h>
+#include "../contrib/utf8cpp/source/utf8.h"
+#include <assimp/fast_atof.h>
+#include <stack>
+#include <map>
+#include <iostream>
+#include <sstream>
+#include <iomanip>
+
+namespace Assimp {
+
+static const std::string parseErrorMessage = "Fast Infoset parse error";
+
+static const char *xmlDeclarations[] = {
+    "<?xml encoding='finf'?>",
+    "<?xml encoding='finf' standalone='yes'?>",
+    "<?xml encoding='finf' standalone='no'?>",
+    "<?xml version='1.0' encoding='finf'?>",
+    "<?xml version='1.0' encoding='finf' standalone='yes'?>",
+    "<?xml version='1.0' encoding='finf' standalone='no'?>",
+    "<?xml version='1.1' encoding='finf'?>",
+    "<?xml version='1.1' encoding='finf' standalone='yes'?>",
+    "<?xml version='1.1' encoding='finf' standalone='no'?>"
+};
+
+static size_t parseMagic(const uint8_t *data, const uint8_t *dataEnd) {
+    if (dataEnd - data < 4) {
+        return 0;
+    }
+    uint32_t magic = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+    switch (magic) {
+    case 0xe0000001:
+        return 4;
+    case 0x3c3f786d: // "<?xm"
+        {
+            size_t xmlDeclarationsLength = sizeof(xmlDeclarations) / sizeof(xmlDeclarations[0]);
+            for (size_t i = 0; i < xmlDeclarationsLength; ++i) {
+                auto xmlDeclaration = xmlDeclarations[i];
+                ptrdiff_t xmlDeclarationLength = strlen(xmlDeclaration);
+                if ((dataEnd - data >= xmlDeclarationLength) && (memcmp(xmlDeclaration, data, xmlDeclarationLength) == 0)) {
+                    data += xmlDeclarationLength;
+                    if (dataEnd - data < 4) {
+                        return 0;
+                    }
+                    magic = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+                    return magic == 0xe0000001 ? xmlDeclarationLength + 4 : 0;
+                }
+            }
+            return 0;
+        }
+    default:
+        return 0;
+    }
+}
+
+static std::string parseUTF8String(const uint8_t *data, size_t len) {
+    return std::string((char*)data, len);
+}
+
+static std::string parseUTF16String(const uint8_t *data, size_t len) {
+    if (len & 1) {
+        throw DeadlyImportError(parseErrorMessage);
+    }
+    size_t numShorts = len / 2;
+    std::vector<short> utf16;
+    utf16.reserve(numShorts);
+    for (size_t i = 0; i < numShorts; ++i) {
+        short v = (data[0] << 8) | data[1];
+        utf16.push_back(v);
+        data += 2;
+    }
+    std::string result;
+    utf8::utf16to8(utf16.begin(), utf16.end(), back_inserter(result));
+    return result;
+}
+
+struct FIStringValueImpl: public FIStringValue {
+    inline FIStringValueImpl(std::string &&value_) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ { return value; }
+};
+
+std::shared_ptr<FIStringValue> FIStringValue::create(std::string &&value) {
+    return std::make_shared<FIStringValueImpl>(std::move(value));
+}
+
+struct FIHexValueImpl: public FIHexValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIHexValueImpl(std::vector<uint8_t> &&value_):  strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            os << std::hex << std::uppercase << std::setfill('0');
+            std::for_each(value.begin(), value.end(), [&](uint8_t c) { os << std::setw(2) << static_cast<int>(c); });
+            strValue = os.str();
+        }
+        return strValue;
+    };
+};
+
+std::shared_ptr<FIHexValue> FIHexValue::create(std::vector<uint8_t> &&value) {
+    return std::make_shared<FIHexValueImpl>(std::move(value));
+}
+
+struct FIBase64ValueImpl: public FIBase64Value {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIBase64ValueImpl(std::vector<uint8_t> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            uint8_t c1 = 0, c2;
+            int imod3 = 0;
+            std::vector<uint8_t>::size_type valueSize = value.size();
+            for (std::vector<uint8_t>::size_type i = 0; i < valueSize; ++i) {
+                c2 = value[i];
+                switch (imod3) {
+                case 0:
+                    os << basis_64[c2 >> 2];
+                    imod3 = 1;
+                    break;
+                case 1:
+                    os << basis_64[((c1 & 0x03) << 4) | ((c2 & 0xf0) >> 4)];
+                    imod3 = 2;
+                    break;
+                case 2:
+                    os << basis_64[((c1 & 0x0f) << 2) | ((c2 & 0xc0) >> 6)] << basis_64[c2 & 0x3f];
+                    imod3 = 0;
+                    break;
+                }
+                c1 = c2;
+            }
+            switch (imod3) {
+            case 1:
+                os << basis_64[(c1 & 0x03) << 4] << "==";
+                break;
+            case 2:
+                os << basis_64[(c1 & 0x0f) << 2] << '=';
+                break;
+            }
+            strValue = os.str();
+        }
+        return strValue;
+    };
+    static const char basis_64[];
+};
+
+const char FIBase64ValueImpl::basis_64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
+
+std::shared_ptr<FIBase64Value> FIBase64Value::create(std::vector<uint8_t> &&value) {
+    return std::make_shared<FIBase64ValueImpl>(std::move(value));
+}
+
+struct FIShortValueImpl: public FIShortValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIShortValueImpl(std::vector<int16_t> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            int n = 0;
+            std::for_each(value.begin(), value.end(), [&](int16_t s) { if (++n > 1) os << ' '; os << s; });
+            strValue = os.str();
+        }
+        return strValue;
+    }
+};
+
+std::shared_ptr<FIShortValue> FIShortValue::create(std::vector<int16_t> &&value) {
+    return std::make_shared<FIShortValueImpl>(std::move(value));
+}
+
+struct FIIntValueImpl: public FIIntValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIIntValueImpl(std::vector<int32_t> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            int n = 0;
+            std::for_each(value.begin(), value.end(), [&](int32_t i) { if (++n > 1) os << ' '; os << i; });
+            strValue = os.str();
+        }
+        return strValue;
+    };
+};
+
+std::shared_ptr<FIIntValue> FIIntValue::create(std::vector<int32_t> &&value) {
+    return std::make_shared<FIIntValueImpl>(std::move(value));
+}
+
+struct FILongValueImpl: public FILongValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FILongValueImpl(std::vector<int64_t> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            int n = 0;
+            std::for_each(value.begin(), value.end(), [&](int64_t l) { if (++n > 1) os << ' '; os << l; });
+            strValue = os.str();
+        }
+        return strValue;
+    };
+};
+
+std::shared_ptr<FILongValue> FILongValue::create(std::vector<int64_t> &&value) {
+    return std::make_shared<FILongValueImpl>(std::move(value));
+}
+
+struct FIBoolValueImpl: public FIBoolValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIBoolValueImpl(std::vector<bool> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            os << std::boolalpha;
+            int n = 0;
+            std::for_each(value.begin(), value.end(), [&](bool b) { if (++n > 1) os << ' '; os << b; });
+            strValue = os.str();
+        }
+        return strValue;
+    };
+};
+
+std::shared_ptr<FIBoolValue> FIBoolValue::create(std::vector<bool> &&value) {
+    return std::make_shared<FIBoolValueImpl>(std::move(value));
+}
+
+struct FIFloatValueImpl: public FIFloatValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIFloatValueImpl(std::vector<float> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            int n = 0;
+            std::for_each(value.begin(), value.end(), [&](float f) { if (++n > 1) os << ' '; os << f; });
+            strValue = os.str();
+        }
+        return strValue;
+    }
+};
+
+std::shared_ptr<FIFloatValue> FIFloatValue::create(std::vector<float> &&value) {
+    return std::make_shared<FIFloatValueImpl>(std::move(value));
+}
+
+struct FIDoubleValueImpl: public FIDoubleValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIDoubleValueImpl(std::vector<double> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            int n = 0;
+            std::for_each(value.begin(), value.end(), [&](double d) { if (++n > 1) os << ' '; os << d; });
+            strValue = os.str();
+        }
+        return strValue;
+    }
+};
+
+std::shared_ptr<FIDoubleValue> FIDoubleValue::create(std::vector<double> &&value) {
+    return std::make_shared<FIDoubleValueImpl>(std::move(value));
+}
+
+struct FIUUIDValueImpl: public FIUUIDValue {
+    mutable std::string strValue;
+    mutable bool strValueValid;
+    inline FIUUIDValueImpl(std::vector<uint8_t> &&value_): strValueValid(false) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ {
+        if (!strValueValid) {
+            strValueValid = true;
+            std::ostringstream os;
+            os << std::hex << std::uppercase << std::setfill('0');
+            std::vector<uint8_t>::size_type valueSize = value.size();
+            for (std::vector<uint8_t>::size_type i = 0; i < valueSize; ++i) {
+                switch (i & 15) {
+                case 0:
+                    if (i > 0) {
+                        os << ' ';
+                    }
+                    os << std::setw(2) << static_cast<int>(value[i]);
+                    break;
+                case 4:
+                case 6:
+                case 8:
+                case 10:
+                    os << '-';
+                    // intentionally fall through!
+                case 1:
+                case 2:
+                case 3:
+                case 5:
+                case 7:
+                case 9:
+                case 11:
+                case 12:
+                case 13:
+                case 14:
+                case 15:
+                    os << std::setw(2) << static_cast<int>(value[i]);
+                    break;
+                }
+            }
+            strValue = os.str();
+        }
+        return strValue;
+    };
+};
+
+std::shared_ptr<FIUUIDValue> FIUUIDValue::create(std::vector<uint8_t> &&value) {
+    return std::make_shared<FIUUIDValueImpl>(std::move(value));
+}
+
+struct FICDATAValueImpl: public FICDATAValue {
+    inline FICDATAValueImpl(std::string &&value_) { value = std::move(value_); }
+    virtual const std::string &toString() const /*override*/ { return value; }
+};
+
+std::shared_ptr<FICDATAValue> FICDATAValue::create(std::string &&value) {
+    return std::make_shared<FICDATAValueImpl>(std::move(value));
+}
+
+struct FIHexDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        return FIHexValue::create(std::vector<uint8_t>(data, data + len));
+    }
+};
+
+struct FIBase64Decoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        return FIBase64Value::create(std::vector<uint8_t>(data, data + len));
+    }
+};
+
+struct FIShortDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        if (len & 1) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        std::vector<int16_t> value;
+        size_t numShorts = len / 2;
+        value.reserve(numShorts);
+        for (size_t i = 0; i < numShorts; ++i) {
+            int16_t v = (data[0] << 8) | data[1];
+            value.push_back(v);
+            data += 2;
+        }
+        return FIShortValue::create(std::move(value));
+    }
+};
+
+struct FIIntDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        if (len & 3) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        std::vector<int32_t> value;
+        size_t numInts = len / 4;
+        value.reserve(numInts);
+        for (size_t i = 0; i < numInts; ++i) {
+            int32_t v = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+            value.push_back(v);
+            data += 4;
+        }
+        return FIIntValue::create(std::move(value));
+    }
+};
+
+struct FILongDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        if (len & 7) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        std::vector<int64_t> value;
+        size_t numLongs = len / 8;
+        value.reserve(numLongs);
+        for (size_t i = 0; i < numLongs; ++i) {
+            int64_t b0 = data[0], b1 = data[1], b2 = data[2], b3 = data[3], b4 = data[4], b5 = data[5], b6 = data[6], b7 = data[7];
+            int64_t v = (b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7;
+            value.push_back(v);
+            data += 8;
+        }
+        return FILongValue::create(std::move(value));
+    }
+};
+
+struct FIBoolDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        if (len < 1) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        std::vector<bool> value;
+        uint8_t b = *data++;
+        size_t unusedBits = b >> 4;
+        size_t numBools = (len * 8) - 4 - unusedBits;
+        value.reserve(numBools);
+        uint8_t mask = 1 << 3;
+        for (size_t i = 0; i < numBools; ++i) {
+            if (!mask) {
+                mask = 1 << 7;
+                b = *data++;
+            }
+            value.push_back((b & mask) != 0);
+        }
+        return FIBoolValue::create(std::move(value));
+    }
+};
+
+struct FIFloatDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        if (len & 3) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        std::vector<float> value;
+        size_t numFloats = len / 4;
+        value.reserve(numFloats);
+        for (size_t i = 0; i < numFloats; ++i) {
+            int v = (data[0] << 24) | (data[1] << 16) | (data[2] << 8) | data[3];
+            float f;
+            memcpy(&f, &v, 4);
+            value.push_back(f);
+            data += 4;
+        }
+        return FIFloatValue::create(std::move(value));
+    }
+};
+
+struct FIDoubleDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        if (len & 7) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        std::vector<double> value;
+        size_t numDoubles = len / 8;
+        value.reserve(numDoubles);
+        for (size_t i = 0; i < numDoubles; ++i) {
+            long long b0 = data[0], b1 = data[1], b2 = data[2], b3 = data[3], b4 = data[4], b5 = data[5], b6 = data[6], b7 = data[7];
+            long long v = (b0 << 56) | (b1 << 48) | (b2 << 40) | (b3 << 32) | (b4 << 24) | (b5 << 16) | (b6 << 8) | b7;
+            double f;
+            memcpy(&f, &v, 8);
+            value.push_back(f);
+            data += 8;
+        }
+        return FIDoubleValue::create(std::move(value));
+    }
+};
+
+struct FIUUIDDecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        if (len & 15) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        return FIUUIDValue::create(std::vector<uint8_t>(data, data + len));
+    }
+};
+
+struct FICDATADecoder: public FIDecoder {
+    virtual std::shared_ptr<const FIValue> decode(const uint8_t *data, size_t len) /*override*/ {
+        return FICDATAValue::create(parseUTF8String(data, len));
+    }
+};
+
+class CFIReaderImpl: public FIReader {
+public:
+
+    CFIReaderImpl(std::unique_ptr<uint8_t[]> data_, size_t size):
+    data(std::move(data_)), dataP(data.get()), dataEnd(data.get() + size), currentNodeType(irr::io::EXN_NONE),
+    emptyElement(false), headerPending(true), terminatorPending(false)
+    {}
+
+    virtual ~CFIReaderImpl() {}
+
+    virtual bool read() /*override*/ {
+        if (headerPending) {
+            headerPending = false;
+            parseHeader();
+        }
+        if (terminatorPending) {
+            terminatorPending = false;
+            if (elementStack.empty()) {
+                return false;
+            }
+            else {
+                nodeName = elementStack.top();
+                elementStack.pop();
+                currentNodeType = nodeName.empty() ? irr::io::EXN_UNKNOWN : irr::io::EXN_ELEMENT_END;
+                return true;
+            }
+        }
+        if (dataP >= dataEnd) {
+            return false;
+        }
+        uint8_t b = *dataP;
+        if (b < 0x80) { // Element (C.2.11.2, C.3.7.2)
+            // C.3
+            parseElement();
+            return true;
+        }
+        else if (b < 0xc0) { // Characters (C.3.7.5)
+            // C.7
+            auto chars = parseNonIdentifyingStringOrIndex3(vocabulary.charactersTable);
+            nodeName = chars->toString();
+            currentNodeType = irr::io::EXN_TEXT;
+            return true;
+        }
+        else if (b < 0xe0) {
+            if ((b & 0xfc) == 0xc4) { // DTD (C.2.11.5)
+                // C.9
+                ++dataP;
+                if (b & 0x02) {
+                    /*const std::string &systemID =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                }
+                if (b & 0x01) {
+                    /*const std::string &publicID =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                }
+                elementStack.push(EmptyString);
+                currentNodeType = irr::io::EXN_UNKNOWN;
+                return true;
+            }
+            else if ((b & 0xfc) == 0xc8) { // Unexpanded entity reference (C.3.7.4)
+                // C.6
+                ++dataP;
+                /*const std::string &name =*/ parseIdentifyingStringOrIndex(vocabulary.otherNCNameTable);
+                if (b & 0x02) {
+                    /*const std::string &systemID =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                }
+                if (b & 0x01) {
+                    /*const std::string &publicID =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                }
+                currentNodeType = irr::io::EXN_UNKNOWN;
+                return true;
+            }
+        }
+        else if (b < 0xf0) {
+            if (b == 0xe1) { // Processing instruction (C.2.11.3, C.3.7.3)
+                // C.5
+                ++dataP;
+                /*const std::string &target =*/ parseIdentifyingStringOrIndex(vocabulary.otherNCNameTable);
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                /*std::shared_ptr<const FIValue> data =*/ parseNonIdentifyingStringOrIndex1(vocabulary.otherStringTable);
+                currentNodeType = irr::io::EXN_UNKNOWN;
+                return true;
+            }
+            else if (b == 0xe2) { // Comment (C.2.11.4, C.3.7.6)
+                // C.8
+                ++dataP;
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                std::shared_ptr<const FIValue> comment = parseNonIdentifyingStringOrIndex1(vocabulary.otherStringTable);
+                nodeName = comment->toString();
+                currentNodeType = irr::io::EXN_COMMENT;
+                return true;
+            }
+        }
+        else { // Terminator (C.2.12, C.3.8)
+            ++dataP;
+            if (b == 0xff) {
+                terminatorPending = true;
+            }
+            if (elementStack.empty()) {
+                return false;
+            }
+            else {
+                nodeName = elementStack.top();
+                elementStack.pop();
+                currentNodeType = nodeName.empty() ? irr::io::EXN_UNKNOWN : irr::io::EXN_ELEMENT_END;
+                return true;
+            }
+        }
+        throw DeadlyImportError(parseErrorMessage);
+    }
+
+    virtual irr::io::EXML_NODE getNodeType() const /*override*/ {
+        return currentNodeType;
+    }
+
+    virtual int getAttributeCount() const /*override*/ {
+        return static_cast<int>(attributes.size());
+    }
+
+    virtual const char* getAttributeName(int idx) const /*override*/ {
+        if (idx < 0 || idx >= (int)attributes.size()) {
+            return nullptr;
+        }
+        return attributes[idx].name.c_str();
+    }
+
+    virtual const char* getAttributeValue(int idx) const /*override*/ {
+        if (idx < 0 || idx >= (int)attributes.size()) {
+            return nullptr;
+        }
+        return attributes[idx].value->toString().c_str();
+    }
+
+    virtual const char* getAttributeValue(const char* name) const /*override*/ {
+        const Attribute* attr = getAttributeByName(name);
+        if (!attr) {
+            return nullptr;
+        }
+        return attr->value->toString().c_str();
+    }
+
+    virtual const char* getAttributeValueSafe(const char* name) const /*override*/ {
+        const Attribute* attr = getAttributeByName(name);
+        if (!attr) {
+            return EmptyString.c_str();
+        }
+        return attr->value->toString().c_str();
+    }
+
+    virtual int getAttributeValueAsInt(const char* name) const /*override*/ {
+        const Attribute* attr = getAttributeByName(name);
+        if (!attr) {
+            return 0;
+        }
+        std::shared_ptr<const FIIntValue> intValue = std::dynamic_pointer_cast<const FIIntValue>(attr->value);
+        if (intValue) {
+            return intValue->value.size() == 1 ? intValue->value.front() : 0;
+        }
+        return atoi(attr->value->toString().c_str());
+    }
+
+    virtual int getAttributeValueAsInt(int idx) const /*override*/ {
+        if (idx < 0 || idx >= (int)attributes.size()) {
+            return 0;
+        }
+        std::shared_ptr<const FIIntValue> intValue = std::dynamic_pointer_cast<const FIIntValue>(attributes[idx].value);
+        if (intValue) {
+            return intValue->value.size() == 1 ? intValue->value.front() : 0;
+        }
+        return atoi(attributes[idx].value->toString().c_str());
+    }
+
+    virtual float getAttributeValueAsFloat(const char* name) const /*override*/ {
+        const Attribute* attr = getAttributeByName(name);
+        if (!attr) {
+            return 0;
+        }
+        std::shared_ptr<const FIFloatValue> floatValue = std::dynamic_pointer_cast<const FIFloatValue>(attr->value);
+        if (floatValue) {
+            return floatValue->value.size() == 1 ? floatValue->value.front() : 0;
+        }
+
+        return fast_atof(attr->value->toString().c_str());
+    }
+
+    virtual float getAttributeValueAsFloat(int idx) const /*override*/ {
+        if (idx < 0 || idx >= (int)attributes.size()) {
+            return 0;
+        }
+        std::shared_ptr<const FIFloatValue> floatValue = std::dynamic_pointer_cast<const FIFloatValue>(attributes[idx].value);
+        if (floatValue) {
+            return floatValue->value.size() == 1 ? floatValue->value.front() : 0;
+        }
+        return fast_atof(attributes[idx].value->toString().c_str());
+    }
+
+    virtual const char* getNodeName() const /*override*/ {
+        return nodeName.c_str();
+    }
+
+    virtual const char* getNodeData() const /*override*/ {
+        return nodeName.c_str();
+    }
+
+    virtual bool isEmptyElement() const /*override*/ {
+        return emptyElement;
+    }
+
+    virtual irr::io::ETEXT_FORMAT getSourceFormat() const /*override*/ {
+        return irr::io::ETF_UTF8;
+    }
+
+    virtual irr::io::ETEXT_FORMAT getParserFormat() const /*override*/ {
+        return irr::io::ETF_UTF8;
+    }
+
+    virtual std::shared_ptr<const FIValue> getAttributeEncodedValue(int idx) const /*override*/ {
+        if (idx < 0 || idx >= (int)attributes.size()) {
+            return nullptr;
+        }
+        return attributes[idx].value;
+    }
+
+    virtual std::shared_ptr<const FIValue> getAttributeEncodedValue(const char* name) const /*override*/ {
+        const Attribute* attr = getAttributeByName(name);
+        if (!attr) {
+            return nullptr;
+        }
+        return attr->value;
+    }
+
+    virtual void registerDecoder(const std::string &algorithmUri, std::unique_ptr<FIDecoder> decoder) /*override*/ {
+        decoderMap[algorithmUri] = std::move(decoder);
+    }
+
+    virtual void registerVocabulary(const std::string &vocabularyUri, const FIVocabulary *vocabulary) /*override*/ {
+        vocabularyMap[vocabularyUri] = vocabulary;
+    }
+
+private:
+
+    struct QName {
+        std::string prefix;
+        std::string uri;
+        std::string name;
+        inline QName() {}
+        inline QName(const FIQName &qname): prefix(qname.prefix ? qname.prefix : ""), uri(qname.uri ? qname.uri : ""), name(qname.name) {}
+    };
+
+    struct Attribute {
+        QName qname;
+        std::string name;
+        std::shared_ptr<const FIValue> value;
+    };
+
+    struct Vocabulary {
+        std::vector<std::string> restrictedAlphabetTable;
+        std::vector<std::string> encodingAlgorithmTable;
+        std::vector<std::string> prefixTable;
+        std::vector<std::string> namespaceNameTable;
+        std::vector<std::string> localNameTable;
+        std::vector<std::string> otherNCNameTable;
+        std::vector<std::string> otherURITable;
+        std::vector<std::shared_ptr<const FIValue>> attributeValueTable;
+        std::vector<std::shared_ptr<const FIValue>> charactersTable;
+        std::vector<std::shared_ptr<const FIValue>> otherStringTable;
+        std::vector<QName> elementNameTable;
+        std::vector<QName> attributeNameTable;
+        Vocabulary() {
+            prefixTable.push_back("xml");
+            namespaceNameTable.push_back("http://www.w3.org/XML/1998/namespace");
+        }
+    };
+
+    const Attribute* getAttributeByName(const char* name) const {
+        if (!name) {
+            return 0;
+        }
+        std::string n = name;
+        for (int i=0; i<(int)attributes.size(); ++i) {
+            if (attributes[i].name == n) {
+                return &attributes[i];
+            }
+        }
+        return 0;
+    }
+
+    size_t parseInt2() { // C.25
+        uint8_t b = *dataP++;
+        if (!(b & 0x40)) { // x0...... (C.25.2)
+            return b & 0x3f;
+        }
+        else if ((b & 0x60) == 0x40) { // x10..... ........ (C.25.3)
+            if (dataEnd - dataP > 0) {
+                return (((b & 0x1f) << 8) | *dataP++) + 0x40;
+            }
+        }
+        else if ((b & 0x70) == 0x60) { // x110.... ........ ........ (C.25.4)
+            if (dataEnd - dataP > 1) {
+                size_t result = (((b & 0x0f) << 16) | (dataP[0] << 8) | dataP[1]) + 0x2040;
+                dataP += 2;
+                return result;
+            }
+        }
+        throw DeadlyImportError(parseErrorMessage);
+    }
+
+    size_t parseInt3() { // C.27
+        uint8_t b = *dataP++;
+        if (!(b & 0x20)) { // xx0..... (C.27.2)
+            return b & 0x1f;
+        }
+        else if ((b & 0x38) == 0x20) { // xx100... ........ (C.27.3)
+            if (dataEnd - dataP > 0) {
+                return (((b & 0x07) << 8) | *dataP++) + 0x20;
+            }
+        }
+        else if ((b & 0x38) == 0x28) { // xx101... ........ ........ (C.27.4)
+            if (dataEnd - dataP > 1) {
+                size_t result = (((b & 0x07) << 16) | (dataP[0] << 8) | dataP[1]) + 0x820;
+                dataP += 2;
+                return result;
+            }
+        }
+        else if ((b & 0x3f) == 0x30) { // xx110000 0000.... ........ ........ (C.27.5)
+            if ((dataEnd - dataP > 2) && !(dataP[0] & 0xf0)) {
+                size_t result = (((dataP[0] & 0x0f) << 16) | (dataP[1] << 8) | dataP[2]) + 0x80820;
+                dataP += 3;
+                return result;
+            }
+        }
+        throw DeadlyImportError(parseErrorMessage);
+    }
+
+    size_t parseInt4() { // C.28
+        uint8_t b = *dataP++;
+        if (!(b & 0x10)) { // xxx0.... (C.28.2)
+            return b & 0x0f;
+        }
+        else if ((b & 0x1c) == 0x10) { // xxx100.. ........ (C.28.3)
+            if (dataEnd - dataP > 0) {
+                return (((b & 0x03) << 8) | *dataP++) + 0x10;
+            }
+        }
+        else if ((b & 0x1c) == 0x14) { // xxx101.. ........ ........ (C.28.4)
+            if (dataEnd - dataP > 1) {
+                size_t result = (((b & 0x03) << 16) | (dataP[0] << 8) | dataP[1]) + 0x410;
+                dataP += 2;
+                return result;
+            }
+        }
+        else if ((b & 0x1f) == 0x18) { // xxx11000 0000.... ........ ........ (C.28.5)
+            if ((dataEnd - dataP > 2) && !(dataP[0] & 0xf0)) {
+                size_t result = (((dataP[0] & 0x0f) << 16) | (dataP[1] << 8) | dataP[2]) + 0x40410;
+                dataP += 3;
+                return result;
+            }
+        }
+        throw DeadlyImportError(parseErrorMessage);
+    }
+
+    size_t parseSequenceLen() { // C.21
+        if (dataEnd - dataP > 0) {
+            uint8_t b = *dataP++;
+            if (b < 0x80) { // 0....... (C.21.2)
+                return b;
+            }
+            else if ((b & 0xf0) == 0x80) { // 1000.... ........ ........ (C.21.3)
+                if (dataEnd - dataP > 1) {
+                    size_t result = (((b & 0x0f) << 16) | (dataP[0] << 8) | dataP[1]) + 0x80;
+                    dataP += 2;
+                    return result;
+                }
+            }
+        }
+        throw DeadlyImportError(parseErrorMessage);
+    }
+
+    std::string parseNonEmptyOctetString2() { // C.22
+        // Parse the length of the string
+        uint8_t b = *dataP++ & 0x7f;
+        size_t len;
+        if (!(b & 0x40)) { // x0...... (C.22.3.1)
+            len = b + 1;
+        }
+        else if (b == 0x40) { // x1000000 ........ (C.22.3.2)
+            if (dataEnd - dataP < 1) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            len = *dataP++ + 0x41;
+        }
+        else if (b == 0x60) { // x1100000 ........ ........ ........ ........ (C.22.3.3)
+            if (dataEnd - dataP < 4) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            len = ((dataP[0] << 24) | (dataP[1] << 16) | (dataP[2] << 8) | dataP[3]) + 0x141;
+            dataP += 4;
+        }
+        else {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+
+        // Parse the string (C.22.4)
+        if (dataEnd - dataP < static_cast<ptrdiff_t>(len)) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        std::string s = parseUTF8String(dataP, len);
+        dataP += len;
+
+        return s;
+    }
+
+    size_t parseNonEmptyOctetString5Length() { // C.23
+        // Parse the length of the string
+        size_t b = *dataP++ & 0x0f;
+        if (!(b & 0x08)) { // xxxx0... (C.23.3.1)
+            return b + 1;
+        }
+        else if (b == 0x08) { // xxxx1000 ........ (C.23.3.2)
+            if (dataEnd - dataP > 0) {
+                return *dataP++ + 0x09;
+            }
+        }
+        else if (b == 0x0c) { // xxxx1100 ........ ........ ........ ........ (C.23.3.3)
+            if (dataEnd - dataP > 3) {
+                size_t result = ((dataP[0] << 24) | (dataP[1] << 16) | (dataP[2] << 8) | dataP[3]) + 0x109;
+                dataP += 4;
+                return result;
+            }
+        }
+        throw DeadlyImportError(parseErrorMessage);
+    }
+
+    size_t parseNonEmptyOctetString7Length() { // C.24
+        // Parse the length of the string
+        size_t b = *dataP++ & 0x03;
+        if (!(b & 0x02)) { // xxxxxx0. (C.24.3.1)
+            return b + 1;
+        }
+        else if (b == 0x02) { // xxxxxx10 ........ (C.24.3.2)
+            if (dataEnd - dataP > 0) {
+                return *dataP++ + 0x3;
+            }
+        }
+        else if (b == 0x03) { // xxxxxx11 ........ ........ ........ ........ (C.24.3.3)
+            if (dataEnd - dataP > 3) {
+                size_t result = ((dataP[0] << 24) | (dataP[1] << 16) | (dataP[2] << 8) | dataP[3]) + 0x103;
+                dataP += 4;
+                return result;
+            }
+        }
+        throw DeadlyImportError(parseErrorMessage);
+    }
+
+    std::shared_ptr<const FIValue> parseEncodedData(size_t index, size_t len) {
+        if (index < 32) {
+            FIDecoder *decoder = defaultDecoder[index];
+            if (!decoder) {
+                throw DeadlyImportError("Invalid encoding algorithm index " + to_string(index));
+            }
+            return decoder->decode(dataP, len);
+        }
+        else {
+            if (index - 32 >= vocabulary.encodingAlgorithmTable.size()) {
+                throw DeadlyImportError("Invalid encoding algorithm index " + to_string(index));
+            }
+            std::string uri = vocabulary.encodingAlgorithmTable[index - 32];
+            auto it = decoderMap.find(uri);
+            if (it == decoderMap.end()) {
+                throw DeadlyImportError("Unsupported encoding algorithm " + uri);
+            }
+            else {
+                return it->second->decode(dataP, len);
+            }
+        }
+    }
+
+    std::shared_ptr<const FIValue> parseRestrictedAlphabet(size_t index, size_t len) {
+        std::string alphabet;
+        if (index < 16) {
+            switch (index) {
+            case 0: // numeric
+                alphabet = "0123456789-+.e ";
+                break;
+            case 1: // date and time
+                alphabet = "0123456789-:TZ ";
+                break;
+            default:
+                throw DeadlyImportError("Invalid restricted alphabet index " + to_string(index));
+            }
+        }
+        else {
+            if (index - 16 >= vocabulary.restrictedAlphabetTable.size()) {
+                throw DeadlyImportError("Invalid restricted alphabet index " + to_string(index));
+            }
+            alphabet = vocabulary.restrictedAlphabetTable[index - 16];
+        }
+        std::vector<uint32_t> alphabetUTF32;
+        utf8::utf8to32(alphabet.begin(), alphabet.end(), back_inserter(alphabetUTF32));
+        std::string::size_type alphabetLength = alphabetUTF32.size();
+        if (alphabetLength < 2) {
+            throw DeadlyImportError("Invalid restricted alphabet length " + to_string(alphabetLength));
+        }
+        std::string::size_type bitsPerCharacter = 1;
+        while ((1ull << bitsPerCharacter) <= alphabetLength) {
+            ++bitsPerCharacter;
+        }
+        size_t bitsAvail = 0;
+        uint8_t mask = (1 << bitsPerCharacter) - 1;
+        uint32_t bits = 0;
+        std::string s;
+        for (size_t i = 0; i < len; ++i) {
+            bits = (bits << 8) | dataP[i];
+            bitsAvail += 8;
+            while (bitsAvail >= bitsPerCharacter) {
+                bitsAvail -= bitsPerCharacter;
+                size_t charIndex = (bits >> bitsAvail) & mask;
+                if (charIndex < alphabetLength) {
+                    s.push_back(alphabetUTF32[charIndex]);
+                }
+                else if (charIndex != mask) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+            }
+        }
+        return FIStringValue::create(std::move(s));
+    }
+
+    std::shared_ptr<const FIValue> parseEncodedCharacterString3() { // C.19
+        std::shared_ptr<const FIValue> result;
+        size_t len;
+        uint8_t b = *dataP;
+        if (b & 0x20) {
+            ++dataP;
+            if (dataEnd - dataP < 1) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            size_t index = ((b & 0x0f) << 4) | ((*dataP & 0xf0) >> 4); // C.29
+            len = parseNonEmptyOctetString5Length();
+            if (dataEnd - dataP < static_cast<ptrdiff_t>(len)) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            if (b & 0x10) {
+                // encoding algorithm (C.19.3.4)
+                result = parseEncodedData(index, len);
+            }
+            else {
+                // Restricted alphabet (C.19.3.3)
+                result = parseRestrictedAlphabet(index, len);
+            }
+        }
+        else {
+            len = parseNonEmptyOctetString5Length();
+            if (dataEnd - dataP < static_cast<ptrdiff_t>(len)) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            if (b & 0x10) {
+                // UTF-16 (C.19.3.2)
+                if (len & 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                result = FIStringValue::create(parseUTF16String(dataP, len));
+            }
+            else {
+                // UTF-8 (C.19.3.1)
+                result = FIStringValue::create(parseUTF8String(dataP, len));
+            }
+        }
+        dataP += len;
+        return result;
+    }
+
+    std::shared_ptr<const FIValue> parseEncodedCharacterString5() { // C.20
+        std::shared_ptr<const FIValue> result;
+        size_t len;
+        uint8_t b = *dataP;
+        if (b & 0x08) {
+            ++dataP;
+            if (dataEnd - dataP < 1) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            size_t index = ((b & 0x03) << 6) | ((*dataP & 0xfc) >> 2); /* C.29 */
+            len = parseNonEmptyOctetString7Length();
+            if (dataEnd - dataP < static_cast<ptrdiff_t>(len)) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            if (b & 0x04) {
+                // encoding algorithm (C.20.3.4)
+                result = parseEncodedData(index, len);
+            }
+            else {
+                // Restricted alphabet (C.20.3.3)
+                result = parseRestrictedAlphabet(index, len);
+            }
+        }
+        else {
+            len = parseNonEmptyOctetString7Length();
+            if (dataEnd - dataP < static_cast<ptrdiff_t>(len)) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            if (b & 0x04) {
+                // UTF-16 (C.20.3.2)
+                if (len & 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                result = FIStringValue::create(parseUTF16String(dataP, len));
+            }
+            else {
+                // UTF-8 (C.20.3.1)
+                result = FIStringValue::create(parseUTF8String(dataP, len));
+            }
+        }
+        dataP += len;
+        return result;
+    }
+
+    const std::string &parseIdentifyingStringOrIndex(std::vector<std::string> &stringTable) { // C.13
+        if (dataEnd - dataP < 1) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        uint8_t b = *dataP;
+        if (b & 0x80) {
+            // We have an index (C.13.4)
+            size_t index = parseInt2();
+            if (index >= stringTable.size()) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            return stringTable[index];
+        }
+        else {
+            // We have a string (C.13.3)
+            stringTable.push_back(parseNonEmptyOctetString2());
+            return stringTable.back();
+        }
+    }
+
+    QName parseNameSurrogate() { // C.16
+        if (dataEnd - dataP < 1) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        uint8_t b = *dataP++;
+        if (b & 0xfc) { // Padding '000000' C.2.5.5
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        QName result;
+        size_t index;
+        if (b & 0x02) { // prefix (C.16.3)
+            if ((dataEnd - dataP < 1) || (*dataP & 0x80)) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            index = parseInt2();
+            if (index >= vocabulary.prefixTable.size()) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            result.prefix = vocabulary.prefixTable[index];
+        }
+        if (b & 0x01) { // namespace-name (C.16.4)
+            if ((dataEnd - dataP < 1) || (*dataP & 0x80)) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            index = parseInt2();
+            if (index >= vocabulary.namespaceNameTable.size()) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            result.uri = vocabulary.namespaceNameTable[index];
+        }
+        // local-name
+        if ((dataEnd - dataP < 1) || (*dataP & 0x80)) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        index = parseInt2();
+        if (index >= vocabulary.localNameTable.size()) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        result.name = vocabulary.localNameTable[index];
+        return result;
+    }
+
+    const QName &parseQualifiedNameOrIndex2(std::vector<QName> &qNameTable) { // C.17
+        uint8_t b = *dataP;
+        if ((b & 0x7c) == 0x78) { // x11110..
+            // We have a literal (C.17.3)
+            ++dataP;
+            QName result;
+            // prefix (C.17.3.1)
+            result.prefix = b & 0x02 ? parseIdentifyingStringOrIndex(vocabulary.prefixTable) : std::string();
+            // namespace-name (C.17.3.1)
+            result.uri = b & 0x01 ? parseIdentifyingStringOrIndex(vocabulary.namespaceNameTable) : std::string();
+            // local-name
+            result.name = parseIdentifyingStringOrIndex(vocabulary.localNameTable);
+            qNameTable.push_back(result);
+            return qNameTable.back();
+        }
+        else {
+            // We have an index (C.17.4)
+            size_t index = parseInt2();
+            if (index >= qNameTable.size()) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            return qNameTable[index];
+        }
+    }
+
+    const QName &parseQualifiedNameOrIndex3(std::vector<QName> &qNameTable) { // C.18
+        uint8_t b = *dataP;
+        if ((b & 0x3c) == 0x3c) { // xx1111..
+            // We have a literal (C.18.3)
+            ++dataP;
+            QName result;
+            // prefix (C.18.3.1)
+            result.prefix = b & 0x02 ? parseIdentifyingStringOrIndex(vocabulary.prefixTable) : std::string();
+            // namespace-name (C.18.3.1)
+            result.uri = b & 0x01 ? parseIdentifyingStringOrIndex(vocabulary.namespaceNameTable) : std::string();
+            // local-name
+            result.name = parseIdentifyingStringOrIndex(vocabulary.localNameTable);
+            qNameTable.push_back(result);
+            return qNameTable.back();
+        }
+        else {
+            // We have an index (C.18.4)
+            size_t index = parseInt3();
+            if (index >= qNameTable.size()) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            return qNameTable[index];
+        }
+    }
+
+    std::shared_ptr<const FIValue> parseNonIdentifyingStringOrIndex1(std::vector<std::shared_ptr<const FIValue>> &valueTable) { // C.14
+        uint8_t b = *dataP;
+        if (b == 0xff) { // C.26.2
+            // empty string
+            ++dataP;
+            return EmptyFIString;
+        }
+        else if (b & 0x80) { // C.14.4
+            // We have an index
+            size_t index = parseInt2();
+            if (index >= valueTable.size()) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            return valueTable[index];
+        }
+        else { // C.14.3
+            // We have a literal
+            std::shared_ptr<const FIValue> result = parseEncodedCharacterString3();
+            if (b & 0x40) { // C.14.3.1
+                valueTable.push_back(result);
+            }
+            return result;
+        }
+    }
+
+    std::shared_ptr<const FIValue> parseNonIdentifyingStringOrIndex3(std::vector<std::shared_ptr<const FIValue>> &valueTable) { // C.15
+        uint8_t b = *dataP;
+        if (b & 0x20) { // C.15.4
+            // We have an index
+            size_t index = parseInt4();
+            if (index >= valueTable.size()) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            return valueTable[index];
+        }
+        else { // C.15.3
+            // We have a literal
+            std::shared_ptr<const FIValue> result = parseEncodedCharacterString5();
+            if (b & 0x10) { // C.15.3.1
+                valueTable.push_back(result);
+            }
+            return result;
+        }
+    }
+
+    void parseElement() {
+        // C.3
+
+        attributes.clear();
+
+        uint8_t b = *dataP;
+        bool hasAttributes = (b & 0x40) != 0; // C.3.3
+        if ((b & 0x3f) == 0x38) { // C.3.4.1
+            // Parse namespaces
+            ++dataP;
+            for (;;) {
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                b = *dataP++;
+                if (b == 0xf0) { // C.3.4.3
+                    break;
+                }
+                if ((b & 0xfc) != 0xcc) { // C.3.4.2
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                // C.12
+                Attribute attr;
+                attr.qname.prefix = "xmlns";
+                attr.qname.name = b & 0x02 ? parseIdentifyingStringOrIndex(vocabulary.prefixTable) : std::string();
+                attr.qname.uri = b & 0x01 ? parseIdentifyingStringOrIndex(vocabulary.namespaceNameTable) : std::string();
+                attr.name = attr.qname.name.empty() ? "xmlns" : "xmlns:" + attr.qname.name;
+                attr.value = FIStringValue::create(std::string(attr.qname.uri));
+                attributes.push_back(attr);
+            }
+            if ((dataEnd - dataP < 1) || (*dataP & 0xc0)) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+        }
+
+        // Parse Element name (C.3.5)
+        const QName &elemName = parseQualifiedNameOrIndex3(vocabulary.elementNameTable);
+        nodeName = elemName.prefix.empty() ? elemName.name : elemName.prefix + ':' + elemName.name;
+
+        if (hasAttributes) {
+            for (;;) {
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                b = *dataP;
+                if (b < 0x80) { // C.3.6.1
+                    // C.4
+                    Attribute attr;
+                    attr.qname = parseQualifiedNameOrIndex2(vocabulary.attributeNameTable);
+                    attr.name = attr.qname.prefix.empty() ? attr.qname.name : attr.qname.prefix + ':' + attr.qname.name;
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    attr.value = parseNonIdentifyingStringOrIndex1(vocabulary.attributeValueTable);
+                    attributes.push_back(attr);
+                }
+                else {
+                    if ((b & 0xf0) != 0xf0) { // C.3.6.2
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    emptyElement = b == 0xff; // C.3.6.2, C.3.8
+                    ++dataP;
+                    break;
+                }
+            }
+        }
+        else {
+            if (dataEnd - dataP < 1) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            b = *dataP;
+            switch (b) {
+            case 0xff:
+                terminatorPending = true;
+                // Intentionally fall through
+            case 0xf0:
+                emptyElement = true;
+                ++dataP;
+                break;
+            default:
+                emptyElement = false;
+            }
+        }
+        if (!emptyElement) {
+            elementStack.push(nodeName);
+        }
+
+        currentNodeType = irr::io::EXN_ELEMENT;
+    }
+
+    void parseHeader() {
+        // Parse header (C.1.3)
+        size_t magicSize = parseMagic(dataP, dataEnd);
+        if (!magicSize) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        dataP += magicSize;
+        // C.2.3
+        if (dataEnd - dataP < 1) {
+            throw DeadlyImportError(parseErrorMessage);
+        }
+        uint8_t b = *dataP++;
+        if (b & 0x40) {
+            // Parse additional data (C.2.4)
+            size_t len = parseSequenceLen();
+            for (size_t i = 0; i < len; ++i) {
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                /*std::string id =*/ parseNonEmptyOctetString2();
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                /*std::string data =*/ parseNonEmptyOctetString2();
+            }
+        }
+        if (b & 0x20) {
+            // Parse initial vocabulary (C.2.5)
+            if (dataEnd - dataP < 2) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            uint16_t b1 = (dataP[0] << 8) | dataP[1];
+            dataP += 2;
+            if (b1 & 0x1000) {
+                // External vocabulary (C.2.5.2)
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                std::string uri = parseNonEmptyOctetString2();
+                auto it = vocabularyMap.find(uri);
+                if (it == vocabularyMap.end()) {
+                    throw DeadlyImportError("Unknown vocabulary " + uri);
+                }
+                const FIVocabulary *externalVocabulary = it->second;
+                if (externalVocabulary->restrictedAlphabetTable) {
+                    std::copy(externalVocabulary->restrictedAlphabetTable, externalVocabulary->restrictedAlphabetTable + externalVocabulary->restrictedAlphabetTableSize, std::back_inserter(vocabulary.restrictedAlphabetTable));
+                }
+                if (externalVocabulary->encodingAlgorithmTable) {
+                    std::copy(externalVocabulary->encodingAlgorithmTable, externalVocabulary->encodingAlgorithmTable + externalVocabulary->encodingAlgorithmTableSize, std::back_inserter(vocabulary.encodingAlgorithmTable));
+                }
+                if (externalVocabulary->prefixTable) {
+                    std::copy(externalVocabulary->prefixTable, externalVocabulary->prefixTable + externalVocabulary->prefixTableSize, std::back_inserter(vocabulary.prefixTable));
+                }
+                if (externalVocabulary->namespaceNameTable) {
+                    std::copy(externalVocabulary->namespaceNameTable, externalVocabulary->namespaceNameTable + externalVocabulary->namespaceNameTableSize, std::back_inserter(vocabulary.namespaceNameTable));
+                }
+                if (externalVocabulary->localNameTable) {
+                    std::copy(externalVocabulary->localNameTable, externalVocabulary->localNameTable + externalVocabulary->localNameTableSize, std::back_inserter(vocabulary.localNameTable));
+                }
+                if (externalVocabulary->otherNCNameTable) {
+                    std::copy(externalVocabulary->otherNCNameTable, externalVocabulary->otherNCNameTable + externalVocabulary->otherNCNameTableSize, std::back_inserter(vocabulary.otherNCNameTable));
+                }
+                if (externalVocabulary->otherURITable) {
+                    std::copy(externalVocabulary->otherURITable, externalVocabulary->otherURITable + externalVocabulary->otherURITableSize, std::back_inserter(vocabulary.otherURITable));
+                }
+                if (externalVocabulary->attributeValueTable) {
+                    std::copy(externalVocabulary->attributeValueTable, externalVocabulary->attributeValueTable + externalVocabulary->attributeValueTableSize, std::back_inserter(vocabulary.attributeValueTable));
+                }
+                if (externalVocabulary->charactersTable) {
+                    std::copy(externalVocabulary->charactersTable, externalVocabulary->charactersTable + externalVocabulary->charactersTableSize, std::back_inserter(vocabulary.charactersTable));
+                }
+                if (externalVocabulary->otherStringTable) {
+                    std::copy(externalVocabulary->otherStringTable, externalVocabulary->otherStringTable + externalVocabulary->otherStringTableSize, std::back_inserter(vocabulary.otherStringTable));
+                }
+                if (externalVocabulary->elementNameTable) {
+                    std::copy(externalVocabulary->elementNameTable, externalVocabulary->elementNameTable + externalVocabulary->elementNameTableSize, std::back_inserter(vocabulary.elementNameTable));
+                }
+                if (externalVocabulary->attributeNameTable) {
+                    std::copy(externalVocabulary->attributeNameTable, externalVocabulary->attributeNameTable + externalVocabulary->attributeNameTableSize, std::back_inserter(vocabulary.attributeNameTable));
+                }
+            }
+            if (b1 & 0x0800) {
+                // Parse restricted alphabets (C.2.5.3)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.restrictedAlphabetTable.push_back(parseNonEmptyOctetString2());
+                }
+            }
+            if (b1 & 0x0400) {
+                // Parse encoding algorithms (C.2.5.3)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.encodingAlgorithmTable.push_back(parseNonEmptyOctetString2());
+                }
+            }
+            if (b1 & 0x0200) {
+                // Parse prefixes (C.2.5.3)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.prefixTable.push_back(parseNonEmptyOctetString2());
+                }
+            }
+            if (b1 & 0x0100) {
+                // Parse namespace names (C.2.5.3)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.namespaceNameTable.push_back(parseNonEmptyOctetString2());
+                }
+            }
+            if (b1 & 0x0080) {
+                // Parse local names (C.2.5.3)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.localNameTable.push_back(parseNonEmptyOctetString2());
+                }
+            }
+            if (b1 & 0x0040) {
+                // Parse other ncnames (C.2.5.3)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.otherNCNameTable.push_back(parseNonEmptyOctetString2());
+                }
+            }
+            if (b1 & 0x0020) {
+                // Parse other uris (C.2.5.3)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.otherURITable.push_back(parseNonEmptyOctetString2());
+                }
+            }
+            if (b1 & 0x0010) {
+                // Parse attribute values (C.2.5.4)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.attributeValueTable.push_back(parseEncodedCharacterString3());
+                }
+            }
+            if (b1 & 0x0008) {
+                // Parse content character chunks (C.2.5.4)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.charactersTable.push_back(parseEncodedCharacterString3());
+                }
+            }
+            if (b1 & 0x0004) {
+                // Parse other strings (C.2.5.4)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    if (dataEnd - dataP < 1) {
+                        throw DeadlyImportError(parseErrorMessage);
+                    }
+                    vocabulary.otherStringTable.push_back(parseEncodedCharacterString3());
+                }
+            }
+            if (b1 & 0x0002) {
+                // Parse element name surrogates (C.2.5.5)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    vocabulary.elementNameTable.push_back(parseNameSurrogate());
+                }
+            }
+            if (b1 & 0x0001) {
+                // Parse attribute name surrogates (C.2.5.5)
+                for (size_t len = parseSequenceLen(); len > 0; --len) {
+                    vocabulary.attributeNameTable.push_back(parseNameSurrogate());
+                }
+            }
+        }
+        if (b & 0x10) {
+            // Parse notations (C.2.6)
+            for (;;) {
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                uint8_t b1 = *dataP++;
+                if (b1 == 0xf0) {
+                    break;
+                }
+                if ((b1 & 0xfc) != 0xc0) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                /* C.11 */
+                /*const std::string &name =*/ parseIdentifyingStringOrIndex(vocabulary.otherNCNameTable);
+                if (b1 & 0x02) {
+                    /*const std::string &systemId =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                }
+                if (b1 & 0x01) {
+                    /*const std::string &publicId =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                }
+            }
+        }
+        if (b & 0x08) {
+            // Parse unparsed entities (C.2.7)
+            for (;;) {
+                if (dataEnd - dataP < 1) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                uint8_t b1 = *dataP++;
+                if (b1 == 0xf0) {
+                    break;
+                }
+                if ((b1 & 0xfe) != 0xd0) {
+                    throw DeadlyImportError(parseErrorMessage);
+                }
+                /* C.10 */
+                /*const std::string &name =*/ parseIdentifyingStringOrIndex(vocabulary.otherNCNameTable);
+                /*const std::string &systemId =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                if (b1 & 0x01) {
+                    /*const std::string &publicId =*/ parseIdentifyingStringOrIndex(vocabulary.otherURITable);
+                }
+                /*const std::string &notationName =*/ parseIdentifyingStringOrIndex(vocabulary.otherNCNameTable);
+            }
+        }
+        if (b & 0x04) {
+            // Parse character encoding scheme (C.2.8)
+            if (dataEnd - dataP < 1) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            /*std::string characterEncodingScheme =*/ parseNonEmptyOctetString2();
+        }
+        if (b & 0x02) {
+            // Parse standalone flag (C.2.9)
+            if (dataEnd - dataP < 1) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            uint8_t b1 = *dataP++;
+            if (b1 & 0xfe) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            //bool standalone = b1 & 0x01;
+        }
+        if (b & 0x01) {
+            // Parse version (C.2.10)
+            if (dataEnd - dataP < 1) {
+                throw DeadlyImportError(parseErrorMessage);
+            }
+            /*std::shared_ptr<const FIValue> version =*/ parseNonIdentifyingStringOrIndex1(vocabulary.otherStringTable);
+        }
+    }
+
+    std::unique_ptr<uint8_t[]> data;
+    uint8_t *dataP, *dataEnd;
+    irr::io::EXML_NODE currentNodeType;
+    bool emptyElement;
+    bool headerPending;
+    bool terminatorPending;
+    Vocabulary vocabulary;
+    std::vector<Attribute> attributes;
+    std::stack<std::string> elementStack;
+    std::string nodeName;
+    std::map<std::string, std::unique_ptr<FIDecoder>> decoderMap;
+    std::map<std::string, const FIVocabulary*> vocabularyMap;
+
+    static const std::string EmptyString;
+    static std::shared_ptr<const FIValue> EmptyFIString;
+
+    static FIHexDecoder hexDecoder;
+    static FIBase64Decoder base64Decoder;
+    static FIShortDecoder shortDecoder;
+    static FIIntDecoder intDecoder;
+    static FILongDecoder longDecoder;
+    static FIBoolDecoder boolDecoder;
+    static FIFloatDecoder floatDecoder;
+    static FIDoubleDecoder doubleDecoder;
+    static FIUUIDDecoder uuidDecoder;
+    static FICDATADecoder cdataDecoder;
+    static FIDecoder *defaultDecoder[32];
+};
+
+const std::string CFIReaderImpl::EmptyString;
+std::shared_ptr<const FIValue> CFIReaderImpl::EmptyFIString = FIStringValue::create(std::string());
+
+FIHexDecoder CFIReaderImpl::hexDecoder;
+FIBase64Decoder CFIReaderImpl::base64Decoder;
+FIShortDecoder CFIReaderImpl::shortDecoder;
+FIIntDecoder CFIReaderImpl::intDecoder;
+FILongDecoder CFIReaderImpl::longDecoder;
+FIBoolDecoder CFIReaderImpl::boolDecoder;
+FIFloatDecoder CFIReaderImpl::floatDecoder;
+FIDoubleDecoder CFIReaderImpl::doubleDecoder;
+FIUUIDDecoder CFIReaderImpl::uuidDecoder;
+FICDATADecoder CFIReaderImpl::cdataDecoder;
+
+FIDecoder *CFIReaderImpl::defaultDecoder[32] = {
+    &hexDecoder,
+    &base64Decoder,
+    &shortDecoder,
+    &intDecoder,
+    &longDecoder,
+    &boolDecoder,
+    &floatDecoder,
+    &doubleDecoder,
+    &uuidDecoder,
+    &cdataDecoder
+};
+
+class CXMLReaderImpl : public FIReader
+{
+public:
+
+    //! Constructor
+    CXMLReaderImpl(std::unique_ptr<irr::io::IIrrXMLReader<char, irr::io::IXMLBase>> reader_)
+    : reader(std::move(reader_))
+    {}
+
+    virtual ~CXMLReaderImpl() {}
+
+    virtual bool read() /*override*/ {
+        return reader->read();
+    }
+
+    virtual irr::io::EXML_NODE getNodeType() const /*override*/ {
+        return reader->getNodeType();
+    }
+
+    virtual int getAttributeCount() const /*override*/ {
+        return reader->getAttributeCount();
+    }
+
+    virtual const char* getAttributeName(int idx) const /*override*/ {
+        return reader->getAttributeName(idx);
+    }
+
+    virtual const char* getAttributeValue(int idx) const /*override*/ {
+        return reader->getAttributeValue(idx);
+    }
+
+    virtual const char* getAttributeValue(const char* name) const /*override*/ {
+        return reader->getAttributeValue(name);
+    }
+
+    virtual const char* getAttributeValueSafe(const char* name) const /*override*/ {
+        return reader->getAttributeValueSafe(name);
+    }
+
+    virtual int getAttributeValueAsInt(const char* name) const /*override*/ {
+        return reader->getAttributeValueAsInt(name);
+    }
+
+    virtual int getAttributeValueAsInt(int idx) const /*override*/ {
+        return reader->getAttributeValueAsInt(idx);
+    }
+
+    virtual float getAttributeValueAsFloat(const char* name) const /*override*/ {
+        return reader->getAttributeValueAsFloat(name);
+    }
+
+    virtual float getAttributeValueAsFloat(int idx) const /*override*/ {
+        return reader->getAttributeValueAsFloat(idx);
+    }
+
+    virtual const char* getNodeName() const /*override*/ {
+        return reader->getNodeName();
+    }
+
+    virtual const char* getNodeData() const /*override*/ {
+        return reader->getNodeData();
+    }
+
+    virtual bool isEmptyElement() const /*override*/ {
+        return reader->isEmptyElement();
+    }
+
+    virtual irr::io::ETEXT_FORMAT getSourceFormat() const /*override*/ {
+        return reader->getSourceFormat();
+    }
+
+    virtual irr::io::ETEXT_FORMAT getParserFormat() const /*override*/ {
+        return reader->getParserFormat();
+    }
+
+    virtual std::shared_ptr<const FIValue> getAttributeEncodedValue(int /*idx*/) const /*override*/ {
+        return nullptr;
+    }
+
+    virtual std::shared_ptr<const FIValue> getAttributeEncodedValue(const char* /*name*/) const /*override*/ {
+        return nullptr;
+    }
+
+    virtual void registerDecoder(const std::string & /*algorithmUri*/, std::unique_ptr<FIDecoder> /*decoder*/) /*override*/ {}
+
+
+    virtual void registerVocabulary(const std::string &/*vocabularyUri*/, const FIVocabulary * /*vocabulary*/) /*override*/ {}
+
+private:
+
+    std::unique_ptr<irr::io::IIrrXMLReader<char, irr::io::IXMLBase>> reader;
+};
+
+static std::unique_ptr<uint8_t[]> readFile(IOStream *stream, size_t &size, bool &isFI) {
+    size = stream->FileSize();
+    std::unique_ptr<uint8_t[]> data = std::unique_ptr<uint8_t[]>(new uint8_t[size]);
+    if (stream->Read(data.get(), size, 1) != 1) {
+        size = 0;
+        data.reset();
+    }
+    isFI = parseMagic(data.get(), data.get() + size) > 0;
+    return data;
+}
+
+std::unique_ptr<FIReader> FIReader::create(IOStream *stream)
+{
+    size_t size;
+    bool isFI;
+    auto data = readFile(stream, size, isFI);
+    if (isFI) {
+        return std::unique_ptr<FIReader>(new CFIReaderImpl(std::move(data), size));
+    }
+    else {
+        auto memios = std::unique_ptr<MemoryIOStream>(new MemoryIOStream(data.release(), size, true));
+        auto callback = std::unique_ptr<CIrrXML_IOStreamReader>(new CIrrXML_IOStreamReader(memios.get()));
+        return std::unique_ptr<FIReader>(new CXMLReaderImpl(std::unique_ptr<irr::io::IIrrXMLReader<char, irr::io::IXMLBase>>(createIrrXMLReader(callback.get()))));
+    }
+}
+
+}// namespace Assimp
+
+#endif // !ASSIMP_BUILD_NO_X3D_IMPORTER

+ 107 - 0
thirdparty/assimp/code/FileLogStream.h

@@ -0,0 +1,107 @@
+/*
+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 FileLofStream.h
+*/
+#ifndef ASSIMP_FILELOGSTREAM_H_INC
+#define ASSIMP_FILELOGSTREAM_H_INC
+
+#include <assimp/LogStream.hpp>
+#include <assimp/IOStream.hpp>
+#include <assimp/DefaultIOSystem.h>
+
+namespace Assimp    {
+
+// ----------------------------------------------------------------------------------
+/** @class  FileLogStream
+ *  @brief  Logstream to write into a file.
+ */
+class FileLogStream :
+    public LogStream
+{
+public:
+    FileLogStream( const char* file, IOSystem* io = NULL );
+    ~FileLogStream();
+    void write( const char* message );
+
+private:
+    IOStream *m_pStream;
+};
+
+// ----------------------------------------------------------------------------------
+//  Constructor
+inline FileLogStream::FileLogStream( const char* file, IOSystem* io ) :
+    m_pStream(NULL)
+{
+    if ( !file || 0 == *file )
+        return;
+
+    // If no IOSystem is specified: take a default one
+    if (!io)
+    {
+        DefaultIOSystem FileSystem;
+        m_pStream = FileSystem.Open( file, "wt");
+    }
+    else m_pStream = io->Open( file, "wt" );
+}
+
+// ----------------------------------------------------------------------------------
+//  Destructor
+inline FileLogStream::~FileLogStream()
+{
+    // The virtual d'tor should destroy the underlying file
+    delete m_pStream;
+}
+
+// ----------------------------------------------------------------------------------
+//  Write method
+inline void FileLogStream::write( const char* message )
+{
+    if (m_pStream != NULL)
+    {
+        m_pStream->Write(message, sizeof(char), ::strlen(message));
+        m_pStream->Flush();
+    }
+}
+
+// ----------------------------------------------------------------------------------
+} // !Namespace Assimp
+
+#endif // !! ASSIMP_FILELOGSTREAM_H_INC

+ 345 - 0
thirdparty/assimp/code/FileSystemFilter.h

@@ -0,0 +1,345 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2008, 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 FileSystemFilter.h
+ *  Implements a filter system to filter calls to Exists() and Open()
+ *  in order to improve the success rate of file opening ...
+ */
+#pragma once
+#ifndef AI_FILESYSTEMFILTER_H_INC
+#define AI_FILESYSTEMFILTER_H_INC
+
+#include <assimp/IOSystem.hpp>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/fast_atof.h>
+#include <assimp/ParsingUtils.h>
+
+namespace Assimp    {
+
+inline bool IsHex(char s) {
+    return (s>='0' && s<='9') || (s>='a' && s<='f') || (s>='A' && s<='F');
+}
+
+// ---------------------------------------------------------------------------
+/** File system filter
+ */
+class FileSystemFilter : public IOSystem
+{
+public:
+    /** Constructor. */
+    FileSystemFilter(const std::string& file, IOSystem* old)
+    : mWrapped  (old)
+    , mSrc_file(file)
+    , mSep(mWrapped->getOsSeparator()) {
+        ai_assert(nullptr != mWrapped);
+
+        // Determine base directory
+        mBase = mSrc_file;
+        std::string::size_type ss2;
+        if (std::string::npos != (ss2 = mBase.find_last_of("\\/")))  {
+            mBase.erase(ss2,mBase.length()-ss2);
+        } else {
+            mBase = "";
+        }
+
+        // make sure the directory is terminated properly
+        char s;
+
+        if ( mBase.empty() ) {
+            mBase = ".";
+            mBase += getOsSeparator();
+        } else if ((s = *(mBase.end()-1)) != '\\' && s != '/') {
+            mBase += getOsSeparator();
+        }
+
+        DefaultLogger::get()->info("Import root directory is \'" + mBase + "\'");
+    }
+
+    /** Destructor. */
+    ~FileSystemFilter() {
+        // empty
+    }
+
+    // -------------------------------------------------------------------
+    /** Tests for the existence of a file at the given path. */
+    bool Exists( const char* pFile) const {
+        ai_assert( nullptr != mWrapped );
+        
+        std::string tmp = pFile;
+
+        // Currently this IOSystem is also used to open THE ONE FILE.
+        if (tmp != mSrc_file)    {
+            BuildPath(tmp);
+            Cleanup(tmp);
+        }
+
+        return mWrapped->Exists(tmp);
+    }
+
+    // -------------------------------------------------------------------
+    /** Returns the directory separator. */
+    char getOsSeparator() const {
+        return mSep;
+    }
+
+    // -------------------------------------------------------------------
+    /** Open a new file with a given path. */
+    IOStream* Open( const char* pFile, const char* pMode = "rb") {
+        ai_assert( nullptr != mWrapped );
+        if ( nullptr == pFile || nullptr == pMode ) {
+            return nullptr;
+        }
+        
+        ai_assert( nullptr != pFile );
+        ai_assert( nullptr != pMode );
+
+        // First try the unchanged path
+        IOStream* s = mWrapped->Open(pFile,pMode);
+
+        if (nullptr == s) {
+            std::string tmp = pFile;
+
+            // Try to convert between absolute and relative paths
+            BuildPath(tmp);
+            s = mWrapped->Open(tmp,pMode);
+
+            if (nullptr == s) {
+                // Finally, look for typical issues with paths
+                // and try to correct them. This is our last
+                // resort.
+                tmp = pFile;
+                Cleanup(tmp);
+                BuildPath(tmp);
+                s = mWrapped->Open(tmp,pMode);
+            }
+        }
+
+        return s;
+    }
+
+    // -------------------------------------------------------------------
+    /** Closes the given file and releases all resources associated with it. */
+    void Close( IOStream* pFile) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->Close(pFile);
+    }
+
+    // -------------------------------------------------------------------
+    /** Compare two paths */
+    bool ComparePaths (const char* one, const char* second) const {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->ComparePaths (one,second);
+    }
+
+    // -------------------------------------------------------------------
+    /** Pushes a new directory onto the directory stack. */
+    bool PushDirectory(const std::string &path ) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->PushDirectory(path);
+    }
+
+    // -------------------------------------------------------------------
+    /** Returns the top directory from the stack. */
+    const std::string &CurrentDirectory() const {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->CurrentDirectory();
+    }
+
+    // -------------------------------------------------------------------
+    /** Returns the number of directories stored on the stack. */
+    size_t StackSize() const {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->StackSize();
+    }
+
+    // -------------------------------------------------------------------
+    /** Pops the top directory from the stack. */
+    bool PopDirectory() {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->PopDirectory();
+    }
+
+    // -------------------------------------------------------------------
+    /** Creates an new directory at the given path. */
+    bool CreateDirectory(const std::string &path) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->CreateDirectory(path);
+    }
+
+    // -------------------------------------------------------------------
+    /** Will change the current directory to the given path. */
+    bool ChangeDirectory(const std::string &path) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->ChangeDirectory(path);
+    }
+
+    // -------------------------------------------------------------------
+    /** Delete file. */
+    bool DeleteFile(const std::string &file) {
+        ai_assert( nullptr != mWrapped );
+        return mWrapped->DeleteFile(file);
+    }
+
+private:
+    // -------------------------------------------------------------------
+    /** Build a valid path from a given relative or absolute path.
+     */
+    void BuildPath (std::string& in) const {
+        ai_assert( nullptr != mWrapped );
+        // if we can already access the file, great.
+        if (in.length() < 3 || mWrapped->Exists(in)) {
+            return;
+        }
+
+        // Determine whether this is a relative path (Windows-specific - most assets are packaged on Windows).
+        if (in[1] != ':') {
+
+            // append base path and try
+            const std::string tmp = mBase + in;
+            if (mWrapped->Exists(tmp)) {
+                in = tmp;
+                return;
+            }
+        }
+
+        // Chop of the file name and look in the model directory, if
+        // this fails try all sub paths of the given path, i.e.
+        // if the given path is foo/bar/something.lwo, try
+        // <base>/something.lwo
+        // <base>/bar/something.lwo
+        // <base>/foo/bar/something.lwo
+        std::string::size_type pos = in.rfind('/');
+        if (std::string::npos == pos) {
+            pos = in.rfind('\\');
+        }
+
+        if (std::string::npos != pos)   {
+            std::string tmp;
+            std::string::size_type last_dirsep = std::string::npos;
+
+            while(true) {
+                tmp = mBase;
+                tmp += mSep;
+
+                std::string::size_type dirsep = in.rfind('/', last_dirsep);
+                if (std::string::npos == dirsep) {
+                    dirsep = in.rfind('\\', last_dirsep);
+                }
+
+                if (std::string::npos == dirsep || dirsep == 0) {
+                    // we did try this already.
+                    break;
+                }
+
+                last_dirsep = dirsep-1;
+
+                tmp += in.substr(dirsep+1, in.length()-pos);
+                if (mWrapped->Exists(tmp)) {
+                    in = tmp;
+                    return;
+                }
+            }
+        }
+
+        // hopefully the underlying file system has another few tricks to access this file ...
+    }
+
+    // -------------------------------------------------------------------
+    /** Cleanup the given path
+     */
+    void Cleanup (std::string& in) const {
+        if(in.empty()) {
+            return;
+        }
+
+        // Remove a very common issue when we're parsing file names: spaces at the
+        // beginning of the path.
+        char last = 0;
+        std::string::iterator it = in.begin();
+        while (IsSpaceOrNewLine( *it ))++it;
+        if (it != in.begin()) {
+            in.erase(in.begin(),it+1);
+        }
+
+        const char separator = getOsSeparator();
+        for (it = in.begin(); it != in.end(); ++it) {
+            // Exclude :// and \\, which remain untouched.
+            // https://sourceforge.net/tracker/?func=detail&aid=3031725&group_id=226462&atid=1067632
+            if ( !strncmp(&*it, "://", 3 )) {
+                it += 3;
+                continue;
+            }
+            if (it == in.begin() && !strncmp(&*it, "\\\\", 2)) {
+                it += 2;
+                continue;
+            }
+
+            // Cleanup path delimiters
+            if (*it == '/' || (*it) == '\\') {
+                *it = separator;
+
+                // And we're removing double delimiters, frequent issue with
+                // incorrectly composited paths ...
+                if (last == *it) {
+                    it = in.erase(it);
+                    --it;
+                }
+            } else if (*it == '%' && in.end() - it > 2) {
+                // Hex sequence in URIs
+                if( IsHex((&*it)[0]) && IsHex((&*it)[1]) ) {
+                    *it = HexOctetToDecimal(&*it);
+                    it = in.erase(it+1,it+2);
+                    --it;
+                }
+            }
+
+            last = *it;
+        }
+    }
+
+private:
+    IOSystem *mWrapped;
+    std::string mSrc_file, mBase;
+    char mSep;
+};
+
+} //!ns Assimp
+
+#endif //AI_DEFAULTIOSYSTEM_H_INC

+ 300 - 0
thirdparty/assimp/code/FindDegenerates.cpp

@@ -0,0 +1,300 @@
+/*
+---------------------------------------------------------------------------
+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  FindDegenerates.cpp
+ *  @brief Implementation of the FindDegenerates post-process step.
+*/
+
+
+
+// internal headers
+#include "ProcessHelper.h"
+#include "FindDegenerates.h"
+#include <assimp/Exceptional.h>
+
+using namespace Assimp;
+
+//remove mesh at position 'index' from the scene
+static void removeMesh(aiScene* pScene, unsigned const index);
+//correct node indices to meshes and remove references to deleted mesh
+static void updateSceneGraph(aiNode* pNode, unsigned const index);
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+FindDegeneratesProcess::FindDegeneratesProcess()
+: mConfigRemoveDegenerates( false )
+, mConfigCheckAreaOfTriangle( false ){
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+FindDegeneratesProcess::~FindDegeneratesProcess() {
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool FindDegeneratesProcess::IsActive( unsigned int pFlags) const {
+    return 0 != (pFlags & aiProcess_FindDegenerates);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup import configuration
+void FindDegeneratesProcess::SetupProperties(const Importer* pImp) {
+    // Get the current value of AI_CONFIG_PP_FD_REMOVE
+    mConfigRemoveDegenerates = (0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_REMOVE,0));
+    mConfigCheckAreaOfTriangle = ( 0 != pImp->GetPropertyInteger(AI_CONFIG_PP_FD_CHECKAREA) );
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void FindDegeneratesProcess::Execute( aiScene* pScene) {
+    ASSIMP_LOG_DEBUG("FindDegeneratesProcess begin");
+    for (unsigned int i = 0; i < pScene->mNumMeshes;++i)
+    {
+        //Do not process point cloud, ExecuteOnMesh works only with faces data
+        if ((pScene->mMeshes[i]->mPrimitiveTypes != aiPrimitiveType::aiPrimitiveType_POINT) && ExecuteOnMesh(pScene->mMeshes[i])) {
+            removeMesh(pScene, i);
+            --i; //the current i is removed, do not skip the next one
+        }
+    }
+    ASSIMP_LOG_DEBUG("FindDegeneratesProcess finished");
+}
+
+static void removeMesh(aiScene* pScene, unsigned const index) {
+    //we start at index and copy the pointers one position forward
+    //save the mesh pointer to delete it later
+    auto delete_me = pScene->mMeshes[index];
+    for (unsigned i = index; i < pScene->mNumMeshes - 1; ++i) {
+        pScene->mMeshes[i] = pScene->mMeshes[i+1];
+    }
+    pScene->mMeshes[pScene->mNumMeshes - 1] = nullptr;
+    --(pScene->mNumMeshes);
+    delete delete_me;
+
+    //removing a mesh also requires updating all references to it in the scene graph
+    updateSceneGraph(pScene->mRootNode, index);
+}
+
+static void updateSceneGraph(aiNode* pNode, unsigned const index) {
+    for (unsigned i = 0; i < pNode->mNumMeshes; ++i) {
+        if (pNode->mMeshes[i] > index) {
+            --(pNode->mMeshes[i]);
+            continue;
+        }
+        if (pNode->mMeshes[i] == index) {
+            for (unsigned j = i; j < pNode->mNumMeshes -1; ++j) {
+                pNode->mMeshes[j] = pNode->mMeshes[j+1];
+            }
+            --(pNode->mNumMeshes);
+            --i;
+            continue;
+        }
+    }
+    //recurse to all children
+    for (unsigned i = 0; i < pNode->mNumChildren; ++i) {
+        updateSceneGraph(pNode->mChildren[i], index);
+    }
+}
+
+static ai_real heron( ai_real a, ai_real b, ai_real c ) {
+    ai_real s = (a + b + c) / 2;
+    ai_real area = pow((s * ( s - a ) * ( s - b ) * ( s - c ) ), (ai_real)0.5 );
+    return area;
+}
+
+static ai_real distance3D( const aiVector3D &vA, aiVector3D &vB ) {
+    const ai_real lx = ( vB.x - vA.x );
+    const ai_real ly = ( vB.y - vA.y );
+    const ai_real lz = ( vB.z - vA.z );
+    ai_real a = lx*lx + ly*ly + lz*lz;
+    ai_real d = pow( a, (ai_real)0.5 );
+
+    return d;
+}
+
+static ai_real calculateAreaOfTriangle( const aiFace& face, aiMesh* mesh ) {
+    ai_real area = 0;
+
+    aiVector3D vA( mesh->mVertices[ face.mIndices[ 0 ] ] );
+    aiVector3D vB( mesh->mVertices[ face.mIndices[ 1 ] ] );
+    aiVector3D vC( mesh->mVertices[ face.mIndices[ 2 ] ] );
+
+    ai_real a( distance3D( vA, vB ) );
+    ai_real b( distance3D( vB, vC ) );
+    ai_real c( distance3D( vC, vA ) );
+    area = heron( a, b, c );
+
+    return area;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported mesh
+bool FindDegeneratesProcess::ExecuteOnMesh( aiMesh* mesh) {
+    mesh->mPrimitiveTypes = 0;
+
+    std::vector<bool> remove_me;
+    if (mConfigRemoveDegenerates) {
+        remove_me.resize( mesh->mNumFaces, false );
+    }
+
+    unsigned int deg = 0, limit;
+    for ( unsigned int a = 0; a < mesh->mNumFaces; ++a ) {
+        aiFace& face = mesh->mFaces[a];
+        bool first = true;
+
+        // check whether the face contains degenerated entries
+        for (unsigned int i = 0; i < face.mNumIndices; ++i) {
+            // Polygons with more than 4 points are allowed to have double points, that is
+            // simulating polygons with holes just with concave polygons. However,
+            // double points may not come directly after another.
+            limit = face.mNumIndices;
+            if (face.mNumIndices > 4) {
+                limit = std::min( limit, i+2 );
+            }
+
+            for (unsigned int t = i+1; t < limit; ++t) {
+                if (mesh->mVertices[face.mIndices[ i ] ] == mesh->mVertices[ face.mIndices[ t ] ]) {
+                    // we have found a matching vertex position
+                    // remove the corresponding index from the array
+                    --face.mNumIndices;
+                    --limit;
+                    for (unsigned int m = t; m < face.mNumIndices; ++m) {
+                        face.mIndices[ m ] = face.mIndices[ m+1 ];
+                    }
+                    --t;
+
+                    // NOTE: we set the removed vertex index to an unique value
+                    // to make sure the developer gets notified when his
+                    // application attempts to access this data.
+                    face.mIndices[ face.mNumIndices ] = 0xdeadbeef;
+
+                    if(first) {
+                        ++deg;
+                        first = false;
+                    }
+
+                    if ( mConfigRemoveDegenerates ) {
+                        remove_me[ a ] = true;
+                        goto evil_jump_outside; // hrhrhrh ... yeah, this rocks baby!
+                    }
+                }
+            }
+
+            if ( mConfigCheckAreaOfTriangle ) {
+                if ( face.mNumIndices == 3 ) {
+                    ai_real area = calculateAreaOfTriangle( face, mesh );
+                    if ( area < 1e-6 ) {
+                        if ( mConfigRemoveDegenerates ) {
+                            remove_me[ a ] = true;
+                            goto evil_jump_outside;
+                        }
+
+                        // todo: check for index which is corrupt.
+                    }
+                }
+            }
+        }
+
+        // We need to update the primitive flags array of the mesh.
+        switch (face.mNumIndices)
+        {
+        case 1u:
+            mesh->mPrimitiveTypes |= aiPrimitiveType_POINT;
+            break;
+        case 2u:
+            mesh->mPrimitiveTypes |= aiPrimitiveType_LINE;
+            break;
+        case 3u:
+            mesh->mPrimitiveTypes |= aiPrimitiveType_TRIANGLE;
+            break;
+        default:
+            mesh->mPrimitiveTypes |= aiPrimitiveType_POLYGON;
+            break;
+        };
+evil_jump_outside:
+        continue;
+    }
+
+    // If AI_CONFIG_PP_FD_REMOVE is true, remove degenerated faces from the import
+    if (mConfigRemoveDegenerates && deg) {
+        unsigned int n = 0;
+        for (unsigned int a = 0; a < mesh->mNumFaces; ++a)
+        {
+            aiFace& face_src = mesh->mFaces[a];
+            if (!remove_me[a]) {
+                aiFace& face_dest = mesh->mFaces[n++];
+
+                // Do a manual copy, keep the index array
+                face_dest.mNumIndices = face_src.mNumIndices;
+                face_dest.mIndices    = face_src.mIndices;
+
+                if (&face_src != &face_dest) {
+                    // clear source
+                    face_src.mNumIndices = 0;
+                    face_src.mIndices = nullptr;
+                }
+            }
+            else {
+                // Otherwise delete it if we don't need this face
+                delete[] face_src.mIndices;
+                face_src.mIndices = nullptr;
+                face_src.mNumIndices = 0;
+            }
+        }
+        // Just leave the rest of the array unreferenced, we don't care for now
+        mesh->mNumFaces = n;
+        if (!mesh->mNumFaces) {
+            //The whole mesh consists of degenerated faces
+            //signal upward, that this mesh should be deleted.
+            ASSIMP_LOG_DEBUG("FindDegeneratesProcess removed a mesh full of degenerated primitives");
+            return true;
+        }
+    }
+
+    if (deg && !DefaultLogger::isNullLogger()) {
+        ASSIMP_LOG_WARN_F( "Found ", deg, " degenerated primitives");
+    }
+    return false;
+}

+ 129 - 0
thirdparty/assimp/code/FindDegenerates.h

@@ -0,0 +1,129 @@
+/*
+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 Defines a post processing step to search all meshes for
+  degenerated faces */
+#ifndef AI_FINDDEGENERATESPROCESS_H_INC
+#define AI_FINDDEGENERATESPROCESS_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/mesh.h>
+
+class FindDegeneratesProcessTest;
+namespace Assimp    {
+
+
+// ---------------------------------------------------------------------------
+/** FindDegeneratesProcess: Searches a mesh for degenerated triangles.
+*/
+class ASSIMP_API FindDegeneratesProcess : public BaseProcess {
+public:
+    FindDegeneratesProcess();
+    ~FindDegeneratesProcess();
+
+    // -------------------------------------------------------------------
+    // Check whether step is active
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    // Execute step on a given scene
+    void Execute( aiScene* pScene);
+
+    // -------------------------------------------------------------------
+    // Setup import settings
+    void SetupProperties(const Importer* pImp);
+
+    // -------------------------------------------------------------------
+    // Execute step on a given mesh
+    ///@returns true if the current mesh should be deleted, false otherwise
+    bool ExecuteOnMesh( aiMesh* mesh);
+
+    // -------------------------------------------------------------------
+    /// @brief Enable the instant removal of degenerated primitives
+    /// @param enabled  true for enabled.
+    void EnableInstantRemoval(bool enabled);
+
+    // -------------------------------------------------------------------
+    /// @brief Check whether instant removal is currently enabled
+    /// @return The instant removal state.
+    bool IsInstantRemoval() const;
+
+    // -------------------------------------------------------------------
+    /// @brief Enable the area check for triangles.
+    /// @param enabled  true for enabled.
+    void EnableAreaCheck( bool enabled );
+
+    // -------------------------------------------------------------------
+    /// @brief Check whether the area check is enabled.
+    /// @return The area check state.
+    bool isAreaCheckEnabled() const;
+
+private:
+    //! Configuration option: remove degenerates faces immediately
+    bool mConfigRemoveDegenerates;
+    //! Configuration option: check for area
+    bool mConfigCheckAreaOfTriangle;
+};
+
+inline
+void FindDegeneratesProcess::EnableInstantRemoval(bool enabled) {
+    mConfigRemoveDegenerates = enabled;
+}
+
+inline
+bool FindDegeneratesProcess::IsInstantRemoval() const {
+    return mConfigRemoveDegenerates;
+}
+
+inline
+void FindDegeneratesProcess::EnableAreaCheck( bool enabled ) {
+    mConfigCheckAreaOfTriangle = enabled;
+}
+
+inline
+bool FindDegeneratesProcess::isAreaCheckEnabled() const {
+    return mConfigCheckAreaOfTriangle;
+}
+
+} // Namespace Assimp
+
+#endif // !! AI_FINDDEGENERATESPROCESS_H_INC

+ 277 - 0
thirdparty/assimp/code/FindInstancesProcess.cpp

@@ -0,0 +1,277 @@
+/*
+---------------------------------------------------------------------------
+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  FindInstancesProcess.cpp
+ *  @brief Implementation of the aiProcess_FindInstances postprocessing step
+*/
+
+
+#include "FindInstancesProcess.h"
+#include <memory>
+#include <stdio.h>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+FindInstancesProcess::FindInstancesProcess()
+:   configSpeedFlag (false)
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+FindInstancesProcess::~FindInstancesProcess()
+{}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool FindInstancesProcess::IsActive( unsigned int pFlags) const
+{
+    // FindInstances makes absolutely no sense together with PreTransformVertices
+    // fixme: spawn error message somewhere else?
+    return 0 != (pFlags & aiProcess_FindInstances) && 0 == (pFlags & aiProcess_PreTransformVertices);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup properties for the step
+void FindInstancesProcess::SetupProperties(const Importer* pImp)
+{
+    // AI_CONFIG_FAVOUR_SPEED
+    configSpeedFlag = (0 != pImp->GetPropertyInteger(AI_CONFIG_FAVOUR_SPEED,0));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Compare the bones of two meshes
+bool CompareBones(const aiMesh* orig, const aiMesh* inst)
+{
+    for (unsigned int i = 0; i < orig->mNumBones;++i) {
+        aiBone* aha = orig->mBones[i];
+        aiBone* oha = inst->mBones[i];
+
+        if (aha->mNumWeights   != oha->mNumWeights   ||
+            aha->mOffsetMatrix != oha->mOffsetMatrix) {
+            return false;
+        }
+
+        // compare weight per weight ---
+        for (unsigned int n = 0; n < aha->mNumWeights;++n) {
+            if  (aha->mWeights[n].mVertexId != oha->mWeights[n].mVertexId ||
+                (aha->mWeights[n].mWeight - oha->mWeights[n].mWeight) < 10e-3f) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Update mesh indices in the node graph
+void UpdateMeshIndices(aiNode* node, unsigned int* lookup)
+{
+    for (unsigned int n = 0; n < node->mNumMeshes;++n)
+        node->mMeshes[n] = lookup[node->mMeshes[n]];
+
+    for (unsigned int n = 0; n < node->mNumChildren;++n)
+        UpdateMeshIndices(node->mChildren[n],lookup);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void FindInstancesProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("FindInstancesProcess begin");
+    if (pScene->mNumMeshes) {
+
+        // use a pseudo hash for all meshes in the scene to quickly find
+        // the ones which are possibly equal. This step is executed early
+        // in the pipeline, so we could, depending on the file format,
+        // have several thousand small meshes. That's too much for a brute
+        // everyone-against-everyone check involving up to 10 comparisons
+        // each.
+        std::unique_ptr<uint64_t[]> hashes (new uint64_t[pScene->mNumMeshes]);
+        std::unique_ptr<unsigned int[]> remapping (new unsigned int[pScene->mNumMeshes]);
+
+        unsigned int numMeshesOut = 0;
+        for (unsigned int i = 0; i < pScene->mNumMeshes; ++i) {
+
+            aiMesh* inst = pScene->mMeshes[i];
+            hashes[i] = GetMeshHash(inst);
+
+            for (int a = i-1; a >= 0; --a) {
+                if (hashes[i] == hashes[a])
+                {
+                    aiMesh* orig = pScene->mMeshes[a];
+                    if (!orig)
+                        continue;
+
+                    // check for hash collision .. we needn't check
+                    // the vertex format, it *must* match due to the
+                    // (brilliant) construction of the hash
+                    if (orig->mNumBones       != inst->mNumBones      ||
+                        orig->mNumFaces       != inst->mNumFaces      ||
+                        orig->mNumVertices    != inst->mNumVertices   ||
+                        orig->mMaterialIndex  != inst->mMaterialIndex ||
+                        orig->mPrimitiveTypes != inst->mPrimitiveTypes)
+                        continue;
+
+                    // up to now the meshes are equal. find an appropriate
+                    // epsilon to compare position differences against
+                    float epsilon = ComputePositionEpsilon(inst);
+                    epsilon *= epsilon;
+
+                    // now compare vertex positions, normals,
+                    // tangents and bitangents using this epsilon.
+                    if (orig->HasPositions()) {
+                        if(!CompareArrays(orig->mVertices,inst->mVertices,orig->mNumVertices,epsilon))
+                            continue;
+                    }
+                    if (orig->HasNormals()) {
+                        if(!CompareArrays(orig->mNormals,inst->mNormals,orig->mNumVertices,epsilon))
+                            continue;
+                    }
+                    if (orig->HasTangentsAndBitangents()) {
+                        if (!CompareArrays(orig->mTangents,inst->mTangents,orig->mNumVertices,epsilon) ||
+                            !CompareArrays(orig->mBitangents,inst->mBitangents,orig->mNumVertices,epsilon))
+                            continue;
+                    }
+
+                    // use a constant epsilon for colors and UV coordinates
+                    static const float uvEpsilon = 10e-4f;
+                    {
+                        unsigned int j, end = orig->GetNumUVChannels();
+                        for(j = 0; j < end; ++j) {
+                            if (!orig->mTextureCoords[j]) {
+                                continue;
+                            }
+                            if(!CompareArrays(orig->mTextureCoords[j],inst->mTextureCoords[j],orig->mNumVertices,uvEpsilon)) {
+                                break;
+                            }
+                        }
+                        if (j != end) {
+                            continue;
+                        }
+                    }
+                    {
+                        unsigned int j, end = orig->GetNumColorChannels();
+                        for(j = 0; j < end; ++j) {
+                            if (!orig->mColors[j]) {
+                                continue;
+                            }
+                            if(!CompareArrays(orig->mColors[j],inst->mColors[j],orig->mNumVertices,uvEpsilon)) {
+                                break;
+                            }
+                        }
+                        if (j != end) {
+                            continue;
+                        }
+                    }
+
+                    // These two checks are actually quite expensive and almost *never* required.
+                    // Almost. That's why they're still here. But there's no reason to do them
+                    // in speed-targeted imports.
+                    if (!configSpeedFlag) {
+
+                        // It seems to be strange, but we really need to check whether the
+                        // bones are identical too. Although it's extremely unprobable
+                        // that they're not if control reaches here, we need to deal
+                        // with unprobable cases, too. It could still be that there are
+                        // equal shapes which are deformed differently.
+                        if (!CompareBones(orig,inst))
+                            continue;
+
+                        // For completeness ... compare even the index buffers for equality
+                        // face order & winding order doesn't care. Input data is in verbose format.
+                        std::unique_ptr<unsigned int[]> ftbl_orig(new unsigned int[orig->mNumVertices]);
+                        std::unique_ptr<unsigned int[]> ftbl_inst(new unsigned int[orig->mNumVertices]);
+
+                        for (unsigned int tt = 0; tt < orig->mNumFaces;++tt) {
+                            aiFace& f = orig->mFaces[tt];
+                            for (unsigned int nn = 0; nn < f.mNumIndices;++nn)
+                                ftbl_orig[f.mIndices[nn]] = tt;
+
+                            aiFace& f2 = inst->mFaces[tt];
+                            for (unsigned int nn = 0; nn < f2.mNumIndices;++nn)
+                                ftbl_inst[f2.mIndices[nn]] = tt;
+                        }
+                        if (0 != ::memcmp(ftbl_inst.get(),ftbl_orig.get(),orig->mNumVertices*sizeof(unsigned int)))
+                            continue;
+                    }
+
+                    // We're still here. Or in other words: 'inst' is an instance of 'orig'.
+                    // Place a marker in our list that we can easily update mesh indices.
+                    remapping[i] = remapping[a];
+
+                    // Delete the instanced mesh, we don't need it anymore
+                    delete inst;
+                    pScene->mMeshes[i] = NULL;
+                    break;
+                }
+            }
+
+            // If we didn't find a match for the current mesh: keep it
+            if (pScene->mMeshes[i]) {
+                remapping[i] = numMeshesOut++;
+            }
+        }
+        ai_assert(0 != numMeshesOut);
+        if (numMeshesOut != pScene->mNumMeshes) {
+
+            // Collapse the meshes array by removing all NULL entries
+            for (unsigned int real = 0, i = 0; real < numMeshesOut; ++i) {
+                if (pScene->mMeshes[i])
+                    pScene->mMeshes[real++] = pScene->mMeshes[i];
+            }
+
+            // And update the node graph with our nice lookup table
+            UpdateMeshIndices(pScene->mRootNode,remapping.get());
+
+            // write to log
+            if (!DefaultLogger::isNullLogger()) {
+                ASSIMP_LOG_INFO_F( "FindInstancesProcess finished. Found ", (pScene->mNumMeshes - numMeshesOut), " instances" );
+            }
+            pScene->mNumMeshes = numMeshesOut;
+        } else {
+            ASSIMP_LOG_DEBUG("FindInstancesProcess finished. No instanced meshes found");
+        }
+    }
+}

+ 137 - 0
thirdparty/assimp/code/FindInstancesProcess.h

@@ -0,0 +1,137 @@
+/*
+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  FindInstancesProcess.h
+ *  @brief Declares the aiProcess_FindInstances post-process step
+ */
+#ifndef AI_FINDINSTANCES_H_INC
+#define AI_FINDINSTANCES_H_INC
+
+#include "BaseProcess.h"
+#include "ProcessHelper.h"
+
+class FindInstancesProcessTest;
+namespace Assimp    {
+
+// -------------------------------------------------------------------------------
+/** @brief Get a pseudo(!)-hash representing a mesh.
+ *
+ *  The hash is built from number of vertices, faces, primitive types,
+ *  .... but *not* from the real mesh data. The funcction is not a perfect hash.
+ *  @param in Input mesh
+ *  @return Hash.
+ */
+inline
+uint64_t GetMeshHash(aiMesh* in) {
+    ai_assert(nullptr != in);
+
+    // ... get an unique value representing the vertex format of the mesh
+    const unsigned int fhash = GetMeshVFormatUnique(in);
+
+    // and bake it with number of vertices/faces/bones/matidx/ptypes
+    return ((uint64_t)fhash << 32u) | ((
+        (in->mNumBones << 16u) ^  (in->mNumVertices)       ^
+        (in->mNumFaces<<4u)    ^  (in->mMaterialIndex<<15) ^
+        (in->mPrimitiveTypes<<28)) & 0xffffffff );
+}
+
+// -------------------------------------------------------------------------------
+/** @brief Perform a component-wise comparison of two arrays
+ *
+ *  @param first First array
+ *  @param second Second array
+ *  @param size Size of both arrays
+ *  @param e Epsilon
+ *  @return true if the arrays are identical
+ */
+inline
+bool CompareArrays(const aiVector3D* first, const aiVector3D* second,
+        unsigned int size, float e) {
+    for (const aiVector3D* end = first+size; first != end; ++first,++second) {
+        if ( (*first - *second).SquareLength() >= e)
+            return false;
+    }
+    return true;
+}
+
+// and the same for colors ...
+inline bool CompareArrays(const aiColor4D* first, const aiColor4D* second,
+    unsigned int size, float e)
+{
+    for (const aiColor4D* end = first+size; first != end; ++first,++second) {
+        if ( GetColorDifference(*first,*second) >= e)
+            return false;
+    }
+    return true;
+}
+
+// ---------------------------------------------------------------------------
+/** @brief A post-processing steps to search for instanced meshes
+*/
+class FindInstancesProcess : public BaseProcess
+{
+public:
+
+    FindInstancesProcess();
+    ~FindInstancesProcess();
+
+public:
+    // -------------------------------------------------------------------
+    // Check whether step is active in given flags combination
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    // Execute step on a given scene
+    void Execute( aiScene* pScene);
+
+    // -------------------------------------------------------------------
+    // Setup properties prior to executing the process
+    void SetupProperties(const Importer* pImp);
+
+private:
+
+    bool configSpeedFlag;
+
+}; // ! end class FindInstancesProcess
+}  // ! end namespace Assimp
+
+#endif // !! AI_FINDINSTANCES_H_INC

+ 424 - 0
thirdparty/assimp/code/FindInvalidDataProcess.cpp

@@ -0,0 +1,424 @@
+/*
+---------------------------------------------------------------------------
+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 Defines a post processing step to search an importer's output
+    for data that is obviously invalid  */
+
+
+
+#ifndef ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS
+
+// internal headers
+#include "FindInvalidDataProcess.h"
+#include "ProcessHelper.h"
+
+#include <assimp/Macros.h>
+#include <assimp/Exceptional.h>
+#include <assimp/qnan.h>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+FindInvalidDataProcess::FindInvalidDataProcess()
+: configEpsilon(0.0)
+, mIgnoreTexCoods( false ){
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+FindInvalidDataProcess::~FindInvalidDataProcess() {
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool FindInvalidDataProcess::IsActive( unsigned int pFlags) const {
+    return 0 != (pFlags & aiProcess_FindInvalidData);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup import configuration
+void FindInvalidDataProcess::SetupProperties(const Importer* pImp) {
+    // Get the current value of AI_CONFIG_PP_FID_ANIM_ACCURACY
+    configEpsilon = (0 != pImp->GetPropertyFloat(AI_CONFIG_PP_FID_ANIM_ACCURACY,0.f));
+    mIgnoreTexCoods = pImp->GetPropertyBool(AI_CONFIG_PP_FID_IGNORE_TEXTURECOORDS, false);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Update mesh references in the node graph
+void UpdateMeshReferences(aiNode* node, const std::vector<unsigned int>& meshMapping) {
+    if (node->mNumMeshes)   {
+        unsigned int out = 0;
+        for (unsigned int a = 0; a < node->mNumMeshes;++a)  {
+
+            unsigned int ref = node->mMeshes[a];
+            if (UINT_MAX != (ref = meshMapping[ref]))   {
+                node->mMeshes[out++] = ref;
+            }
+        }
+        // just let the members that are unused, that's much cheaper
+        // than a full array realloc'n'copy party ...
+        if(!(node->mNumMeshes = out))   {
+
+            delete[] node->mMeshes;
+            node->mMeshes = NULL;
+        }
+    }
+    // recursively update all children
+    for (unsigned int i = 0; i < node->mNumChildren;++i) {
+        UpdateMeshReferences(node->mChildren[i],meshMapping);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void FindInvalidDataProcess::Execute( aiScene* pScene) {
+    ASSIMP_LOG_DEBUG("FindInvalidDataProcess begin");
+
+    bool out = false;
+    std::vector<unsigned int> meshMapping(pScene->mNumMeshes);
+    unsigned int real = 0;
+
+    // Process meshes
+    for( unsigned int a = 0; a < pScene->mNumMeshes; a++)   {
+
+        int result;
+        if ((result = ProcessMesh( pScene->mMeshes[a])))    {
+            out = true;
+
+            if (2 == result)    {
+                // remove this mesh
+                delete pScene->mMeshes[a];
+                AI_DEBUG_INVALIDATE_PTR(pScene->mMeshes[a]);
+
+                meshMapping[a] = UINT_MAX;
+                continue;
+            }
+        }
+        pScene->mMeshes[real] = pScene->mMeshes[a];
+        meshMapping[a] = real++;
+    }
+
+    // Process animations
+    for (unsigned int a = 0; a < pScene->mNumAnimations;++a) {
+        ProcessAnimation( pScene->mAnimations[a]);
+    }
+
+
+    if (out)    {
+        if ( real != pScene->mNumMeshes)    {
+            if (!real) {
+                throw DeadlyImportError("No meshes remaining");
+            }
+
+            // we need to remove some meshes.
+            // therefore we'll also need to remove all references
+            // to them from the scenegraph
+            UpdateMeshReferences(pScene->mRootNode,meshMapping);
+            pScene->mNumMeshes = real;
+        }
+
+        ASSIMP_LOG_INFO("FindInvalidDataProcess finished. Found issues ...");
+    } else {
+        ASSIMP_LOG_DEBUG("FindInvalidDataProcess finished. Everything seems to be OK.");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+inline
+const char* ValidateArrayContents(const T* /*arr*/, unsigned int /*size*/,
+        const std::vector<bool>& /*dirtyMask*/, bool /*mayBeIdentical = false*/, bool /*mayBeZero = true*/)
+{
+    return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+template <>
+inline
+const char* ValidateArrayContents<aiVector3D>(const aiVector3D* arr, unsigned int size,
+        const std::vector<bool>& dirtyMask, bool mayBeIdentical , bool mayBeZero ) {
+    bool b = false;
+    unsigned int cnt = 0;
+    for (unsigned int i = 0; i < size;++i)  {
+
+        if (dirtyMask.size() && dirtyMask[i]) {
+            continue;
+        }
+        ++cnt;
+
+        const aiVector3D& v = arr[i];
+        if (is_special_float(v.x) || is_special_float(v.y) || is_special_float(v.z))    {
+            return "INF/NAN was found in a vector component";
+        }
+        if (!mayBeZero && !v.x && !v.y && !v.z )    {
+            return "Found zero-length vector";
+        }
+        if (i && v != arr[i-1])b = true;
+    }
+    if (cnt > 1 && !b && !mayBeIdentical) {
+        return "All vectors are identical";
+    }
+    return nullptr;
+}
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+inline 
+bool ProcessArray(T*& in, unsigned int num,const char* name,
+        const std::vector<bool>& dirtyMask, bool mayBeIdentical = false, bool mayBeZero = true) {
+    const char* err = ValidateArrayContents(in,num,dirtyMask,mayBeIdentical,mayBeZero);
+    if (err)    {
+        ASSIMP_LOG_ERROR_F( "FindInvalidDataProcess fails on mesh ", name, ": ", err);
+        delete[] in;
+        in = NULL;
+        return true;
+    }
+    return false;
+}
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+AI_FORCE_INLINE bool EpsilonCompare(const T& n, const T& s, ai_real epsilon);
+
+// ------------------------------------------------------------------------------------------------
+AI_FORCE_INLINE bool EpsilonCompare(ai_real n, ai_real s, ai_real epsilon) {
+    return std::fabs(n-s)>epsilon;
+}
+
+// ------------------------------------------------------------------------------------------------
+template <>
+bool EpsilonCompare<aiVectorKey>(const aiVectorKey& n, const aiVectorKey& s, ai_real epsilon) {
+    return
+        EpsilonCompare(n.mValue.x,s.mValue.x,epsilon) &&
+        EpsilonCompare(n.mValue.y,s.mValue.y,epsilon) &&
+        EpsilonCompare(n.mValue.z,s.mValue.z,epsilon);
+}
+
+// ------------------------------------------------------------------------------------------------
+template <>
+bool EpsilonCompare<aiQuatKey>(const aiQuatKey& n, const aiQuatKey& s, ai_real epsilon)   {
+    return
+        EpsilonCompare(n.mValue.x,s.mValue.x,epsilon) &&
+        EpsilonCompare(n.mValue.y,s.mValue.y,epsilon) &&
+        EpsilonCompare(n.mValue.z,s.mValue.z,epsilon) &&
+        EpsilonCompare(n.mValue.w,s.mValue.w,epsilon);
+}
+
+// ------------------------------------------------------------------------------------------------
+template <typename T>
+inline
+bool AllIdentical(T* in, unsigned int num, ai_real epsilon) {
+    if (num <= 1) {
+        return true;
+    }
+
+    if (fabs(epsilon) > 0.f) {
+        for (unsigned int i = 0; i < num-1;++i) {
+            if (!EpsilonCompare(in[i],in[i+1],epsilon)) {
+                return false;
+            }
+        }
+    } else {
+        for (unsigned int i = 0; i < num-1;++i) {
+            if (in[i] != in[i+1]) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Search an animation for invalid content
+void FindInvalidDataProcess::ProcessAnimation (aiAnimation* anim) {
+    // Process all animation channels
+    for ( unsigned int a = 0; a < anim->mNumChannels; ++a ) {
+        ProcessAnimationChannel( anim->mChannels[a]);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+void FindInvalidDataProcess::ProcessAnimationChannel (aiNodeAnim* anim) {
+    ai_assert( nullptr != anim );
+    if (anim->mNumPositionKeys == 0 && anim->mNumRotationKeys == 0 && anim->mNumScalingKeys == 0) {
+        ai_assert_entry();
+        return;
+    }
+
+    // Check whether all values in a tracks are identical - in this case
+    // we can remove al keys except one.
+    // POSITIONS
+    int i = 0;
+    if (anim->mNumPositionKeys > 1 && AllIdentical(anim->mPositionKeys,anim->mNumPositionKeys,configEpsilon)) {
+        aiVectorKey v = anim->mPositionKeys[0];
+
+        // Reallocate ... we need just ONE element, it makes no sense to reuse the array
+        delete[] anim->mPositionKeys;
+        anim->mPositionKeys = new aiVectorKey[anim->mNumPositionKeys = 1];
+        anim->mPositionKeys[0] = v;
+        i = 1;
+    }
+
+    // ROTATIONS
+    if (anim->mNumRotationKeys > 1 && AllIdentical(anim->mRotationKeys,anim->mNumRotationKeys,configEpsilon)) {
+        aiQuatKey v = anim->mRotationKeys[0];
+
+        // Reallocate ... we need just ONE element, it makes no sense to reuse the array
+        delete[] anim->mRotationKeys;
+        anim->mRotationKeys = new aiQuatKey[anim->mNumRotationKeys = 1];
+        anim->mRotationKeys[0] = v;
+        i = 1;
+    }
+
+    // SCALINGS
+    if (anim->mNumScalingKeys > 1 && AllIdentical(anim->mScalingKeys,anim->mNumScalingKeys,configEpsilon)) {
+        aiVectorKey v = anim->mScalingKeys[0];
+
+        // Reallocate ... we need just ONE element, it makes no sense to reuse the array
+        delete[] anim->mScalingKeys;
+        anim->mScalingKeys = new aiVectorKey[anim->mNumScalingKeys = 1];
+        anim->mScalingKeys[0] = v;
+        i = 1;
+    }
+    if ( 1 == i ) {
+        ASSIMP_LOG_WARN("Simplified dummy tracks with just one key");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Search a mesh for invalid contents
+int FindInvalidDataProcess::ProcessMesh(aiMesh* pMesh)
+{
+    bool ret = false;
+    std::vector<bool> dirtyMask(pMesh->mNumVertices, pMesh->mNumFaces != 0);
+
+    // Ignore elements that are not referenced by vertices.
+    // (they are, for example, caused by the FindDegenerates step)
+    for (unsigned int m = 0; m < pMesh->mNumFaces; ++m) {
+        const aiFace& f = pMesh->mFaces[m];
+
+        for (unsigned int i = 0; i < f.mNumIndices; ++i) {
+            dirtyMask[f.mIndices[i]] = false;
+        }
+    }
+
+    // Process vertex positions
+    if (pMesh->mVertices && ProcessArray(pMesh->mVertices, pMesh->mNumVertices, "positions", dirtyMask)) {
+        ASSIMP_LOG_ERROR("Deleting mesh: Unable to continue without vertex positions");
+
+        return 2;
+    }
+
+    // process texture coordinates
+    if (!mIgnoreTexCoods) {
+        for (unsigned int i = 0; i < AI_MAX_NUMBER_OF_TEXTURECOORDS && pMesh->mTextureCoords[i]; ++i) {
+            if (ProcessArray(pMesh->mTextureCoords[i], pMesh->mNumVertices, "uvcoords", dirtyMask)) {
+                pMesh->mNumUVComponents[i] = 0;
+
+                // delete all subsequent texture coordinate sets.
+                for (unsigned int a = i + 1; a < AI_MAX_NUMBER_OF_TEXTURECOORDS; ++a) {
+                    delete[] pMesh->mTextureCoords[a];
+                    pMesh->mTextureCoords[a] = NULL;
+                    pMesh->mNumUVComponents[a] = 0;
+                }
+
+                ret = true;
+            }
+        }
+    }
+
+    // -- we don't validate vertex colors, it's difficult to say whether
+    // they are invalid or not.
+
+    // Normals and tangents are undefined for point and line faces.
+    if (pMesh->mNormals || pMesh->mTangents)    {
+
+        if (aiPrimitiveType_POINT & pMesh->mPrimitiveTypes ||
+            aiPrimitiveType_LINE  & pMesh->mPrimitiveTypes)
+        {
+            if (aiPrimitiveType_TRIANGLE & pMesh->mPrimitiveTypes ||
+                aiPrimitiveType_POLYGON  & pMesh->mPrimitiveTypes)
+            {
+                // We need to update the lookup-table
+                for (unsigned int m = 0; m < pMesh->mNumFaces;++m) {
+                    const aiFace& f = pMesh->mFaces[ m ];
+
+                    if (f.mNumIndices < 3)  {
+                        dirtyMask[f.mIndices[0]] = true;
+                        if (f.mNumIndices == 2) {
+                            dirtyMask[f.mIndices[1]] = true;
+                        }
+                    }
+                }
+            }
+            // Normals, tangents and bitangents are undefined for
+            // the whole mesh (and should not even be there)
+            else {
+                return ret;
+            }
+        }
+
+        // Process mesh normals
+        if (pMesh->mNormals && ProcessArray(pMesh->mNormals,pMesh->mNumVertices,
+            "normals",dirtyMask,true,false))
+            ret = true;
+
+        // Process mesh tangents
+        if (pMesh->mTangents && ProcessArray(pMesh->mTangents,pMesh->mNumVertices,"tangents",dirtyMask))    {
+            delete[] pMesh->mBitangents; pMesh->mBitangents = NULL;
+            ret = true;
+        }
+
+        // Process mesh bitangents
+        if (pMesh->mBitangents && ProcessArray(pMesh->mBitangents,pMesh->mNumVertices,"bitangents",dirtyMask))  {
+            delete[] pMesh->mTangents; pMesh->mTangents = NULL;
+            ret = true;
+        }
+    }
+    return ret ? 1 : 0;
+}
+
+#endif // !! ASSIMP_BUILD_NO_FINDINVALIDDATA_PROCESS

+ 105 - 0
thirdparty/assimp/code/FindInvalidDataProcess.h

@@ -0,0 +1,105 @@
+/*
+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 Defines a post processing step to search an importer's output
+ *   for data that is obviously invalid
+ */
+#ifndef AI_FINDINVALIDDATA_H_INC
+#define AI_FINDINVALIDDATA_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/types.h>
+#include <assimp/anim.h>
+
+struct aiMesh;
+
+class FindInvalidDataProcessTest;
+
+namespace Assimp    {
+
+// ---------------------------------------------------------------------------
+/** The FindInvalidData post-processing step. It searches the mesh data
+ *  for parts that are obviously invalid and removes them.
+ *
+ *  Originally this was a workaround for some models written by Blender
+ *  which have zero normal vectors. */
+class ASSIMP_API FindInvalidDataProcess : public BaseProcess {
+public:
+    FindInvalidDataProcess();
+    ~FindInvalidDataProcess();
+
+    // -------------------------------------------------------------------
+    //
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    // Setup import settings
+    void SetupProperties(const Importer* pImp);
+
+    // -------------------------------------------------------------------
+    // Run the step
+    void Execute( aiScene* pScene);
+
+    // -------------------------------------------------------------------
+    /** Executes the post-processing step on the given mesh
+     * @param pMesh The mesh to process.
+     * @return 0 - nothing, 1 - removed sth, 2 - please delete me  */
+    int ProcessMesh( aiMesh* pMesh);
+
+    // -------------------------------------------------------------------
+    /** Executes the post-processing step on the given animation
+     * @param anim The animation to process.  */
+    void ProcessAnimation (aiAnimation* anim);
+
+    // -------------------------------------------------------------------
+    /** Executes the post-processing step on the given anim channel
+     * @param anim The animation channel to process.*/
+    void ProcessAnimationChannel (aiNodeAnim* anim);
+
+private:
+    ai_real configEpsilon;
+    bool mIgnoreTexCoods;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_AI_FINDINVALIDDATA_H_INC

+ 184 - 0
thirdparty/assimp/code/FixNormalsStep.cpp

@@ -0,0 +1,184 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of the post processing step to invert
+ * all normals in meshes with infacing normals.
+ */
+
+// internal headers
+#include "FixNormalsStep.h"
+#include <assimp/StringUtils.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <stdio.h>
+
+
+using namespace Assimp;
+
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+FixInfacingNormalsProcess::FixInfacingNormalsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+FixInfacingNormalsProcess::~FixInfacingNormalsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool FixInfacingNormalsProcess::IsActive( unsigned int pFlags) const
+{
+    return (pFlags & aiProcess_FixInfacingNormals) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void FixInfacingNormalsProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess begin");
+
+    bool bHas( false );
+    for (unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
+        if (ProcessMesh(pScene->mMeshes[a], a)) {
+            bHas = true;
+        }
+    }
+
+    if (bHas) {
+        ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess finished. Found issues.");
+    } else {
+        ASSIMP_LOG_DEBUG("FixInfacingNormalsProcess finished. No changes to the scene.");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Apply the step to the mesh
+bool FixInfacingNormalsProcess::ProcessMesh( aiMesh* pcMesh, unsigned int index)
+{
+    ai_assert(nullptr != pcMesh);
+
+    // Nothing to do if there are no model normals
+    if (!pcMesh->HasNormals()) {
+        return false;
+    }
+
+    // Compute the bounding box of both the model vertices + normals and
+    // the unmodified model vertices. Then check whether the first BB
+    // is smaller than the second. In this case we can assume that the
+    // normals need to be flipped, although there are a few special cases ..
+    // convex, concave, planar models ...
+
+    aiVector3D vMin0 (1e10f,1e10f,1e10f);
+    aiVector3D vMin1 (1e10f,1e10f,1e10f);
+    aiVector3D vMax0 (-1e10f,-1e10f,-1e10f);
+    aiVector3D vMax1 (-1e10f,-1e10f,-1e10f);
+
+    for (unsigned int i = 0; i < pcMesh->mNumVertices;++i)
+    {
+        vMin1.x = std::min(vMin1.x,pcMesh->mVertices[i].x);
+        vMin1.y = std::min(vMin1.y,pcMesh->mVertices[i].y);
+        vMin1.z = std::min(vMin1.z,pcMesh->mVertices[i].z);
+
+        vMax1.x = std::max(vMax1.x,pcMesh->mVertices[i].x);
+        vMax1.y = std::max(vMax1.y,pcMesh->mVertices[i].y);
+        vMax1.z = std::max(vMax1.z,pcMesh->mVertices[i].z);
+
+        const aiVector3D vWithNormal = pcMesh->mVertices[i] + pcMesh->mNormals[i];
+
+        vMin0.x = std::min(vMin0.x,vWithNormal.x);
+        vMin0.y = std::min(vMin0.y,vWithNormal.y);
+        vMin0.z = std::min(vMin0.z,vWithNormal.z);
+
+        vMax0.x = std::max(vMax0.x,vWithNormal.x);
+        vMax0.y = std::max(vMax0.y,vWithNormal.y);
+        vMax0.z = std::max(vMax0.z,vWithNormal.z);
+    }
+
+    const float fDelta0_x = (vMax0.x - vMin0.x);
+    const float fDelta0_y = (vMax0.y - vMin0.y);
+    const float fDelta0_z = (vMax0.z - vMin0.z);
+
+    const float fDelta1_x = (vMax1.x - vMin1.x);
+    const float fDelta1_y = (vMax1.y - vMin1.y);
+    const float fDelta1_z = (vMax1.z - vMin1.z);
+
+    // Check whether the boxes are overlapping
+    if ((fDelta0_x > 0.0f) != (fDelta1_x > 0.0f))return false;
+    if ((fDelta0_y > 0.0f) != (fDelta1_y > 0.0f))return false;
+    if ((fDelta0_z > 0.0f) != (fDelta1_z > 0.0f))return false;
+
+    // Check whether this is a planar surface
+    const float fDelta1_yz = fDelta1_y * fDelta1_z;
+
+    if (fDelta1_x < 0.05f * std::sqrt( fDelta1_yz ))return false;
+    if (fDelta1_y < 0.05f * std::sqrt( fDelta1_z * fDelta1_x ))return false;
+    if (fDelta1_z < 0.05f * std::sqrt( fDelta1_y * fDelta1_x ))return false;
+
+    // now compare the volumes of the bounding boxes
+    if (std::fabs(fDelta0_x * fDelta0_y * fDelta0_z) < std::fabs(fDelta1_x * fDelta1_yz)) {
+        if (!DefaultLogger::isNullLogger()) {
+            ASSIMP_LOG_INFO_F("Mesh ", index, ": Normals are facing inwards (or the mesh is planar)", index);
+        }
+
+        // Invert normals
+        for (unsigned int i = 0; i < pcMesh->mNumVertices;++i)
+            pcMesh->mNormals[i] *= -1.0f;
+
+        // ... and flip faces
+        for (unsigned int i = 0; i < pcMesh->mNumFaces;++i)
+        {
+            aiFace& face = pcMesh->mFaces[i];
+            for( unsigned int b = 0; b < face.mNumIndices / 2; b++)
+                std::swap( face.mIndices[b], face.mIndices[ face.mNumIndices - 1 - b]);
+        }
+        return true;
+    }
+    return false;
+}

+ 91 - 0
thirdparty/assimp/code/FixNormalsStep.h

@@ -0,0 +1,91 @@
+/*
+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 Defines a post processing step to fix infacing normals */
+#ifndef AI_FIXNORMALSPROCESS_H_INC
+#define AI_FIXNORMALSPROCESS_H_INC
+
+#include "BaseProcess.h"
+
+struct aiMesh;
+
+namespace Assimp
+{
+
+// ---------------------------------------------------------------------------
+/** The FixInfacingNormalsProcess tries to determine whether the normal
+ * vectors of an object are facing inwards. In this case they will be
+ * flipped.
+ */
+class FixInfacingNormalsProcess : public BaseProcess {
+public:
+    FixInfacingNormalsProcess();
+    ~FixInfacingNormalsProcess();
+
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag field.
+     * @param pFlags The processing flags the importer was called with. A bitwise
+     *   combination of #aiPostProcessSteps.
+     * @return true if the process is present in this flag fields, false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+protected:
+
+    // -------------------------------------------------------------------
+    /** Executes the step on the given mesh
+     * @param pMesh The mesh to process.
+     */
+    bool ProcessMesh( aiMesh* pMesh, unsigned int index);
+};
+
+} // end of namespace Assimp
+
+#endif // AI_FIXNORMALSPROCESS_H_INC

+ 146 - 0
thirdparty/assimp/code/GenFaceNormalsProcess.cpp

@@ -0,0 +1,146 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of the post processing step to generate face
+* normals for all imported faces.
+*/
+
+
+#include "GenFaceNormalsProcess.h"
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/Exceptional.h>
+#include <assimp/qnan.h>
+
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+GenFaceNormalsProcess::GenFaceNormalsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+GenFaceNormalsProcess::~GenFaceNormalsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool GenFaceNormalsProcess::IsActive( unsigned int pFlags) const {
+    force_ = (pFlags & aiProcess_ForceGenNormals) != 0;
+    return  (pFlags & aiProcess_GenNormals) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void GenFaceNormalsProcess::Execute( aiScene* pScene) {
+    ASSIMP_LOG_DEBUG("GenFaceNormalsProcess begin");
+
+    if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) {
+        throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here");
+    }
+
+    bool bHas = false;
+    for( unsigned int a = 0; a < pScene->mNumMeshes; a++)   {
+        if(this->GenMeshFaceNormals( pScene->mMeshes[a])) {
+            bHas = true;
+        }
+    }
+    if (bHas)   {
+        ASSIMP_LOG_INFO("GenFaceNormalsProcess finished. "
+            "Face normals have been calculated");
+    } else {
+        ASSIMP_LOG_DEBUG("GenFaceNormalsProcess finished. "
+            "Normals are already there");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+bool GenFaceNormalsProcess::GenMeshFaceNormals (aiMesh* pMesh)
+{
+    if (NULL != pMesh->mNormals) {
+        if (force_) delete[] pMesh->mNormals;
+        else return false;
+    }
+
+    // If the mesh consists of lines and/or points but not of
+    // triangles or higher-order polygons the normal vectors
+    // are undefined.
+    if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON)))   {
+        ASSIMP_LOG_INFO("Normal vectors are undefined for line and point meshes");
+        return false;
+    }
+
+    // allocate an array to hold the output normals
+    pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
+    const float qnan = get_qnan();
+
+    // iterate through all faces and compute per-face normals but store them per-vertex.
+    for( unsigned int a = 0; a < pMesh->mNumFaces; a++) {
+        const aiFace& face = pMesh->mFaces[a];
+        if (face.mNumIndices < 3)   {
+            // either a point or a line -> no well-defined normal vector
+            for (unsigned int i = 0;i < face.mNumIndices;++i) {
+                pMesh->mNormals[face.mIndices[i]] = aiVector3D(qnan);
+            }
+            continue;
+        }
+
+        const aiVector3D* pV1 = &pMesh->mVertices[face.mIndices[0]];
+        const aiVector3D* pV2 = &pMesh->mVertices[face.mIndices[1]];
+        const aiVector3D* pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices-1]];
+        const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe();
+
+        for (unsigned int i = 0;i < face.mNumIndices;++i) {
+            pMesh->mNormals[face.mIndices[i]] = vNor;
+        }
+    }
+    return true;
+}

+ 87 - 0
thirdparty/assimp/code/GenFaceNormalsProcess.h

@@ -0,0 +1,87 @@
+/*
+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 Defines a post processing step to compute face normals for all loaded faces*/
+#ifndef AI_GENFACENORMALPROCESS_H_INC
+#define AI_GENFACENORMALPROCESS_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/mesh.h>
+
+namespace Assimp
+{
+
+// ---------------------------------------------------------------------------
+/** The GenFaceNormalsProcess computes face normals for all faces of all meshes
+*/
+class ASSIMP_API_WINONLY GenFaceNormalsProcess : public BaseProcess
+{
+public:
+
+    GenFaceNormalsProcess();
+    ~GenFaceNormalsProcess();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag field.
+    * @param pFlags The processing flags the importer was called with. A bitwise
+    *   combination of #aiPostProcessSteps.
+    * @return true if the process is present in this flag fields, false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+
+private:
+    bool GenMeshFaceNormals(aiMesh* pcMesh);
+    mutable bool force_ = false;
+};
+
+} // end of namespace Assimp
+
+#endif // !!AI_GENFACENORMALPROCESS_H_INC

+ 239 - 0
thirdparty/assimp/code/GenVertexNormalsProcess.cpp

@@ -0,0 +1,239 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of the post processing step to generate face
+* normals for all imported faces.
+*/
+
+
+
+// internal headers
+#include "GenVertexNormalsProcess.h"
+#include "ProcessHelper.h"
+#include <assimp/Exceptional.h>
+#include <assimp/qnan.h>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+GenVertexNormalsProcess::GenVertexNormalsProcess()
+: configMaxAngle( AI_DEG_TO_RAD( 175.f ) ) {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+GenVertexNormalsProcess::~GenVertexNormalsProcess() {
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool GenVertexNormalsProcess::IsActive( unsigned int pFlags) const
+{
+    force_ = (pFlags & aiProcess_ForceGenNormals) != 0;
+    return (pFlags & aiProcess_GenSmoothNormals) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void GenVertexNormalsProcess::SetupProperties(const Importer* pImp)
+{
+    // Get the current value of the AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE property
+    configMaxAngle = pImp->GetPropertyFloat(AI_CONFIG_PP_GSN_MAX_SMOOTHING_ANGLE,(ai_real)175.0);
+    configMaxAngle = AI_DEG_TO_RAD(std::max(std::min(configMaxAngle,(ai_real)175.0),(ai_real)0.0));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void GenVertexNormalsProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("GenVertexNormalsProcess begin");
+
+    if (pScene->mFlags & AI_SCENE_FLAGS_NON_VERBOSE_FORMAT) {
+        throw DeadlyImportError("Post-processing order mismatch: expecting pseudo-indexed (\"verbose\") vertices here");
+    }
+
+    bool bHas = false;
+    for( unsigned int a = 0; a < pScene->mNumMeshes; ++a) {
+        if(GenMeshVertexNormals( pScene->mMeshes[a],a))
+            bHas = true;
+    }
+
+    if (bHas)   {
+        ASSIMP_LOG_INFO("GenVertexNormalsProcess finished. "
+            "Vertex normals have been calculated");
+    } else {
+        ASSIMP_LOG_DEBUG("GenVertexNormalsProcess finished. "
+            "Normals are already there");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+bool GenVertexNormalsProcess::GenMeshVertexNormals (aiMesh* pMesh, unsigned int meshIndex)
+{
+    if (NULL != pMesh->mNormals) {
+        if (force_) delete[] pMesh->mNormals;
+        else return false;
+    }
+
+    // If the mesh consists of lines and/or points but not of
+    // triangles or higher-order polygons the normal vectors
+    // are undefined.
+    if (!(pMesh->mPrimitiveTypes & (aiPrimitiveType_TRIANGLE | aiPrimitiveType_POLYGON)))
+    {
+        ASSIMP_LOG_INFO("Normal vectors are undefined for line and point meshes");
+        return false;
+    }
+
+    // Allocate the array to hold the output normals
+    const float qnan = std::numeric_limits<ai_real>::quiet_NaN();
+    pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
+
+    // Compute per-face normals but store them per-vertex
+    for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
+    {
+        const aiFace& face = pMesh->mFaces[a];
+        if (face.mNumIndices < 3)
+        {
+            // either a point or a line -> no normal vector
+            for (unsigned int i = 0;i < face.mNumIndices;++i) {
+                pMesh->mNormals[face.mIndices[i]] = aiVector3D(qnan);
+            }
+
+            continue;
+        }
+
+        const aiVector3D* pV1 = &pMesh->mVertices[face.mIndices[0]];
+        const aiVector3D* pV2 = &pMesh->mVertices[face.mIndices[1]];
+        const aiVector3D* pV3 = &pMesh->mVertices[face.mIndices[face.mNumIndices-1]];
+        const aiVector3D vNor = ((*pV2 - *pV1) ^ (*pV3 - *pV1)).NormalizeSafe();
+
+        for (unsigned int i = 0;i < face.mNumIndices;++i) {
+            pMesh->mNormals[face.mIndices[i]] = vNor;
+        }
+    }
+
+    // Set up a SpatialSort to quickly find all vertices close to a given position
+    // check whether we can reuse the SpatialSort of a previous step.
+    SpatialSort* vertexFinder = NULL;
+    SpatialSort  _vertexFinder;
+    ai_real posEpsilon = ai_real( 1e-5 );
+    if (shared) {
+        std::vector<std::pair<SpatialSort,ai_real> >* avf;
+        shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
+        if (avf)
+        {
+            std::pair<SpatialSort,ai_real>& blubb = avf->operator [] (meshIndex);
+            vertexFinder = &blubb.first;
+            posEpsilon = blubb.second;
+        }
+    }
+    if (!vertexFinder)  {
+        _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
+        vertexFinder = &_vertexFinder;
+        posEpsilon = ComputePositionEpsilon(pMesh);
+    }
+    std::vector<unsigned int> verticesFound;
+    aiVector3D* pcNew = new aiVector3D[pMesh->mNumVertices];
+
+    if (configMaxAngle >= AI_DEG_TO_RAD( 175.f ))   {
+        // There is no angle limit. Thus all vertices with positions close
+        // to each other will receive the same vertex normal. This allows us
+        // to optimize the whole algorithm a little bit ...
+        std::vector<bool> abHad(pMesh->mNumVertices,false);
+        for (unsigned int i = 0; i < pMesh->mNumVertices;++i)   {
+            if (abHad[i]) {
+                continue;
+            }
+
+            // Get all vertices that share this one ...
+            vertexFinder->FindPositions( pMesh->mVertices[i], posEpsilon, verticesFound);
+
+            aiVector3D pcNor;
+            for (unsigned int a = 0; a < verticesFound.size(); ++a) {
+                const aiVector3D& v = pMesh->mNormals[verticesFound[a]];
+                if (is_not_qnan(v.x))pcNor += v;
+            }
+            pcNor.NormalizeSafe();
+
+            // Write the smoothed normal back to all affected normals
+            for (unsigned int a = 0; a < verticesFound.size(); ++a)
+            {
+                unsigned int vidx = verticesFound[a];
+                pcNew[vidx] = pcNor;
+                abHad[vidx] = true;
+            }
+        }
+    }
+    // Slower code path if a smooth angle is set. There are many ways to achieve
+    // the effect, this one is the most straightforward one.
+    else    {
+        const ai_real fLimit = std::cos(configMaxAngle);
+        for (unsigned int i = 0; i < pMesh->mNumVertices;++i)   {
+            // Get all vertices that share this one ...
+            vertexFinder->FindPositions( pMesh->mVertices[i] , posEpsilon, verticesFound);
+
+            aiVector3D vr = pMesh->mNormals[i];
+
+            aiVector3D pcNor;
+            for (unsigned int a = 0; a < verticesFound.size(); ++a) {
+                aiVector3D v = pMesh->mNormals[verticesFound[a]];
+
+                // Check whether the angle between the two normals is not too large.
+                // Skip the angle check on our own normal to avoid false negatives
+                // (v*v is not guaranteed to be 1.0 for all unit vectors v)
+                if (is_not_qnan(v.x) && (verticesFound[a] == i || (v * vr >= fLimit)))
+                    pcNor += v;
+            }
+            pcNew[i] = pcNor.NormalizeSafe();
+        }
+    }
+
+    delete[] pMesh->mNormals;
+    pMesh->mNormals = pcNew;
+
+    return true;
+}

+ 115 - 0
thirdparty/assimp/code/GenVertexNormalsProcess.h

@@ -0,0 +1,115 @@
+/*
+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 Defines a post processing step to compute vertex normals
+    for all loaded vertizes */
+#ifndef AI_GENVERTEXNORMALPROCESS_H_INC
+#define AI_GENVERTEXNORMALPROCESS_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/mesh.h>
+
+class GenNormalsTest;
+
+namespace Assimp {
+
+// ---------------------------------------------------------------------------
+/** The GenFaceNormalsProcess computes vertex normals for all vertizes
+*/
+class ASSIMP_API GenVertexNormalsProcess : public BaseProcess
+{
+public:
+
+    GenVertexNormalsProcess();
+    ~GenVertexNormalsProcess();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag.
+    * @param pFlags The processing flags the importer was called with.
+    *   A bitwise combination of #aiPostProcessSteps.
+    * @return true if the process is present in this flag fields,
+    *   false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Called prior to ExecuteOnScene().
+    * The function is a request to the process to update its configuration
+    * basing on the Importer's configuration property list.
+    */
+    void SetupProperties(const Importer* pImp);
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+
+    // setter for configMaxAngle
+    inline void SetMaxSmoothAngle(ai_real f)
+    {
+        configMaxAngle =f;
+    }
+
+public:
+
+    // -------------------------------------------------------------------
+    /** Computes normals for a specific mesh
+    *  @param pcMesh Mesh
+    *  @param meshIndex Index of the mesh
+    *  @return true if vertex normals have been computed
+    */
+    bool GenMeshVertexNormals (aiMesh* pcMesh, unsigned int meshIndex);
+
+private:
+
+    /** Configuration option: maximum smoothing angle, in radians*/
+    ai_real configMaxAngle;
+    mutable bool force_ = false;
+};
+
+} // end of namespace Assimp
+
+#endif // !!AI_GENVERTEXNORMALPROCESS_H_INC

+ 1171 - 0
thirdparty/assimp/code/Importer.cpp

@@ -0,0 +1,1171 @@
+/*
+---------------------------------------------------------------------------
+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  Importer.cpp
+ *  @brief Implementation of the CPP-API class #Importer
+ */
+
+#include <assimp/version.h>
+#include <assimp/config.h>
+#include <assimp/importerdesc.h>
+
+// ------------------------------------------------------------------------------------------------
+/* Uncomment this line to prevent Assimp from catching unknown exceptions.
+ *
+ * Note that any Exception except DeadlyImportError may lead to
+ * undefined behaviour -> loaders could remain in an unusable state and
+ * further imports with the same Importer instance could fail/crash/burn ...
+ */
+// ------------------------------------------------------------------------------------------------
+#ifndef ASSIMP_BUILD_DEBUG
+#   define ASSIMP_CATCH_GLOBAL_EXCEPTIONS
+#endif
+
+// ------------------------------------------------------------------------------------------------
+// Internal headers
+// ------------------------------------------------------------------------------------------------
+#include "Importer.h"
+#include <assimp/BaseImporter.h>
+#include "BaseProcess.h"
+
+#include "DefaultProgressHandler.h"
+#include <assimp/GenericProperty.h>
+#include "ProcessHelper.h"
+#include "ScenePreprocessor.h"
+#include "ScenePrivate.h"
+#include <assimp/MemoryIOWrapper.h>
+#include <assimp/Profiler.h>
+#include <assimp/TinyFormatter.h>
+#include <assimp/Exceptional.h>
+#include <assimp/Profiler.h>
+#include <set>
+#include <memory>
+#include <cctype>
+
+#include <assimp/DefaultIOStream.h>
+#include <assimp/DefaultIOSystem.h>
+
+#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+#   include "ValidateDataStructure.h"
+#endif
+
+using namespace Assimp::Profiling;
+using namespace Assimp::Formatter;
+
+namespace Assimp {
+    // ImporterRegistry.cpp
+    void GetImporterInstanceList(std::vector< BaseImporter* >& out);
+	void DeleteImporterInstanceList(std::vector< BaseImporter* >& out);
+
+    // PostStepRegistry.cpp
+    void GetPostProcessingStepInstanceList(std::vector< BaseProcess* >& out);
+}
+
+using namespace Assimp;
+using namespace Assimp::Intern;
+
+// ------------------------------------------------------------------------------------------------
+// Intern::AllocateFromAssimpHeap serves as abstract base class. It overrides
+// new and delete (and their array counterparts) of public API classes (e.g. Logger) to
+// utilize our DLL heap.
+// See http://www.gotw.ca/publications/mill15.htm
+// ------------------------------------------------------------------------------------------------
+void* AllocateFromAssimpHeap::operator new ( size_t num_bytes)  {
+    return ::operator new(num_bytes);
+}
+
+void* AllocateFromAssimpHeap::operator new ( size_t num_bytes, const std::nothrow_t& ) throw()  {
+    try {
+        return AllocateFromAssimpHeap::operator new( num_bytes );
+    }
+    catch( ... )    {
+        return NULL;
+    }
+}
+
+void AllocateFromAssimpHeap::operator delete ( void* data)  {
+    return ::operator delete(data);
+}
+
+void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes)    {
+    return ::operator new[](num_bytes);
+}
+
+void* AllocateFromAssimpHeap::operator new[] ( size_t num_bytes, const std::nothrow_t& ) throw() {
+    try {
+        return AllocateFromAssimpHeap::operator new[]( num_bytes );
+    }
+    catch( ... )    {
+        return NULL;
+    }
+}
+
+void AllocateFromAssimpHeap::operator delete[] ( void* data)    {
+    return ::operator delete[](data);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Importer constructor.
+Importer::Importer()
+ : pimpl( new ImporterPimpl ) {
+    pimpl->mScene = NULL;
+    pimpl->mErrorString = "";
+
+    // Allocate a default IO handler
+    pimpl->mIOHandler = new DefaultIOSystem;
+    pimpl->mIsDefaultHandler = true;
+    pimpl->bExtraVerbose     = false; // disable extra verbose mode by default
+
+    pimpl->mProgressHandler = new DefaultProgressHandler();
+    pimpl->mIsDefaultProgressHandler = true;
+
+    GetImporterInstanceList(pimpl->mImporter);
+    GetPostProcessingStepInstanceList(pimpl->mPostProcessingSteps);
+
+    // Allocate a SharedPostProcessInfo object and store pointers to it in all post-process steps in the list.
+    pimpl->mPPShared = new SharedPostProcessInfo();
+    for (std::vector<BaseProcess*>::iterator it =  pimpl->mPostProcessingSteps.begin();
+        it != pimpl->mPostProcessingSteps.end();
+        ++it)   {
+
+        (*it)->SetSharedData(pimpl->mPPShared);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor of Importer
+Importer::~Importer()
+{
+    // Delete all import plugins
+	DeleteImporterInstanceList(pimpl->mImporter);
+
+    // Delete all post-processing plug-ins
+    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)
+        delete pimpl->mPostProcessingSteps[a];
+
+    // Delete the assigned IO and progress handler
+    delete pimpl->mIOHandler;
+    delete pimpl->mProgressHandler;
+
+    // Kill imported scene. Destructor's should do that recursively
+    delete pimpl->mScene;
+
+    // Delete shared post-processing data
+    delete pimpl->mPPShared;
+
+    // and finally the pimpl itself
+    delete pimpl;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Register a custom post-processing step
+aiReturn Importer::RegisterPPStep(BaseProcess* pImp)
+{
+    ai_assert(NULL != pImp);
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+        pimpl->mPostProcessingSteps.push_back(pImp);
+        ASSIMP_LOG_INFO("Registering custom post-processing step");
+
+    ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    return AI_SUCCESS;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Register a custom loader plugin
+aiReturn Importer::RegisterLoader(BaseImporter* pImp)
+{
+    ai_assert(NULL != pImp);
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // --------------------------------------------------------------------
+    // Check whether we would have two loaders for the same file extension
+    // This is absolutely OK, but we should warn the developer of the new
+    // loader that his code will probably never be called if the first
+    // loader is a bit too lazy in his file checking.
+    // --------------------------------------------------------------------
+    std::set<std::string> st;
+    std::string baked;
+    pImp->GetExtensionList(st);
+
+    for(std::set<std::string>::const_iterator it = st.begin(); it != st.end(); ++it) {
+
+#ifdef ASSIMP_BUILD_DEBUG
+        if (IsExtensionSupported(*it)) {
+            ASSIMP_LOG_WARN_F("The file extension ", *it, " is already in use");
+        }
+#endif
+        baked += *it;
+    }
+
+    // add the loader
+    pimpl->mImporter.push_back(pImp);
+    ASSIMP_LOG_INFO_F("Registering custom importer for these file extensions: ", baked);
+    ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    return AI_SUCCESS;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Unregister a custom loader plugin
+aiReturn Importer::UnregisterLoader(BaseImporter* pImp)
+{
+    if(!pImp) {
+        // unregistering a NULL importer is no problem for us ... really!
+        return AI_SUCCESS;
+    }
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    std::vector<BaseImporter*>::iterator it = std::find(pimpl->mImporter.begin(),
+        pimpl->mImporter.end(),pImp);
+
+    if (it != pimpl->mImporter.end())   {
+        pimpl->mImporter.erase(it);
+        ASSIMP_LOG_INFO("Unregistering custom importer: ");
+        return AI_SUCCESS;
+    }
+    ASSIMP_LOG_WARN("Unable to remove custom importer: I can't find you ...");
+    ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    return AI_FAILURE;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Unregister a custom loader plugin
+aiReturn Importer::UnregisterPPStep(BaseProcess* pImp)
+{
+    if(!pImp) {
+        // unregistering a NULL ppstep is no problem for us ... really!
+        return AI_SUCCESS;
+    }
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    std::vector<BaseProcess*>::iterator it = std::find(pimpl->mPostProcessingSteps.begin(),
+        pimpl->mPostProcessingSteps.end(),pImp);
+
+    if (it != pimpl->mPostProcessingSteps.end())    {
+        pimpl->mPostProcessingSteps.erase(it);
+        ASSIMP_LOG_INFO("Unregistering custom post-processing step");
+        return AI_SUCCESS;
+    }
+    ASSIMP_LOG_WARN("Unable to remove custom post-processing step: I can't find you ..");
+    ASSIMP_END_EXCEPTION_REGION(aiReturn);
+    return AI_FAILURE;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Supplies a custom IO handler to the importer to open and access files.
+void Importer::SetIOHandler( IOSystem* pIOHandler)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    // If the new handler is zero, allocate a default IO implementation.
+    if (!pIOHandler)
+    {
+        // Release pointer in the possession of the caller
+        pimpl->mIOHandler = new DefaultIOSystem();
+        pimpl->mIsDefaultHandler = true;
+    }
+    // Otherwise register the custom handler
+    else if (pimpl->mIOHandler != pIOHandler)
+    {
+        delete pimpl->mIOHandler;
+        pimpl->mIOHandler = pIOHandler;
+        pimpl->mIsDefaultHandler = false;
+    }
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the currently set IO handler
+IOSystem* Importer::GetIOHandler() const {
+    return pimpl->mIOHandler;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check whether a custom IO handler is currently set
+bool Importer::IsDefaultIOHandler() const {
+    return pimpl->mIsDefaultHandler;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Supplies a custom progress handler to get regular callbacks during importing
+void Importer::SetProgressHandler ( ProgressHandler* pHandler ) {
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    // If the new handler is zero, allocate a default implementation.
+    if (!pHandler)
+    {
+        // Release pointer in the possession of the caller
+        pimpl->mProgressHandler = new DefaultProgressHandler();
+        pimpl->mIsDefaultProgressHandler = true;
+    }
+    // Otherwise register the custom handler
+    else if (pimpl->mProgressHandler != pHandler)
+    {
+        delete pimpl->mProgressHandler;
+        pimpl->mProgressHandler = pHandler;
+        pimpl->mIsDefaultProgressHandler = false;
+    }
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the currently set progress handler
+ProgressHandler* Importer::GetProgressHandler() const {
+    return pimpl->mProgressHandler;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Check whether a custom progress handler is currently set
+bool Importer::IsDefaultProgressHandler() const {
+    return pimpl->mIsDefaultProgressHandler;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Validate post process step flags
+bool _ValidateFlags(unsigned int pFlags)
+{
+    if (pFlags & aiProcess_GenSmoothNormals && pFlags & aiProcess_GenNormals)   {
+        ASSIMP_LOG_ERROR("#aiProcess_GenSmoothNormals and #aiProcess_GenNormals are incompatible");
+        return false;
+    }
+    if (pFlags & aiProcess_OptimizeGraph && pFlags & aiProcess_PreTransformVertices)    {
+        ASSIMP_LOG_ERROR("#aiProcess_OptimizeGraph and #aiProcess_PreTransformVertices are incompatible");
+        return false;
+    }
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Free the current scene
+void Importer::FreeScene( )
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    delete pimpl->mScene;
+    pimpl->mScene = NULL;
+
+    pimpl->mErrorString = "";
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the current error string, if any
+const char* Importer::GetErrorString() const
+{
+     /* Must remain valid as long as ReadFile() or FreeFile() are not called */
+    return pimpl->mErrorString.c_str();
+}
+
+// ------------------------------------------------------------------------------------------------
+// Enable extra-verbose mode
+void Importer::SetExtraVerbose(bool bDo)
+{
+    pimpl->bExtraVerbose = bDo;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the current scene
+const aiScene* Importer::GetScene() const
+{
+    return pimpl->mScene;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Orphan the current scene and return it.
+aiScene* Importer::GetOrphanedScene()
+{
+    aiScene* s = pimpl->mScene;
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    pimpl->mScene = NULL;
+
+    pimpl->mErrorString = ""; /* reset error string */
+    ASSIMP_END_EXCEPTION_REGION(aiScene*);
+    return s;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Validate post-processing flags
+bool Importer::ValidateFlags(unsigned int pFlags) const
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    // run basic checks for mutually exclusive flags
+    if(!_ValidateFlags(pFlags)) {
+        return false;
+    }
+
+    // ValidateDS does not anymore occur in the pp list, it plays an awesome extra role ...
+#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+    if (pFlags & aiProcess_ValidateDataStructure) {
+        return false;
+    }
+#endif
+    pFlags &= ~aiProcess_ValidateDataStructure;
+
+    // Now iterate through all bits which are set in the flags and check whether we find at least
+    // one pp plugin which handles it.
+    for (unsigned int mask = 1; mask < (1u << (sizeof(unsigned int)*8-1));mask <<= 1) {
+
+        if (pFlags & mask) {
+
+            bool have = false;
+            for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)   {
+                if (pimpl->mPostProcessingSteps[a]-> IsActive(mask) ) {
+
+                    have = true;
+                    break;
+                }
+            }
+            if (!have) {
+                return false;
+            }
+        }
+    }
+    ASSIMP_END_EXCEPTION_REGION(bool);
+    return true;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiScene* Importer::ReadFileFromMemory( const void* pBuffer,
+    size_t pLength,
+    unsigned int pFlags,
+    const char* pHint /*= ""*/)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    if (!pHint) {
+        pHint = "";
+    }
+
+    if (!pBuffer || !pLength || strlen(pHint) > MaxLenHint ) {
+        pimpl->mErrorString = "Invalid parameters passed to ReadFileFromMemory()";
+        return NULL;
+    }
+
+    // prevent deletion of the previous IOHandler
+    IOSystem* io = pimpl->mIOHandler;
+    pimpl->mIOHandler = NULL;
+
+    SetIOHandler(new MemoryIOSystem((const uint8_t*)pBuffer,pLength,io));
+
+    // read the file and recover the previous IOSystem
+    static const size_t BufSize(Importer::MaxLenHint + 28);
+    char fbuff[BufSize];
+    ai_snprintf(fbuff, BufSize, "%s.%s",AI_MEMORYIO_MAGIC_FILENAME,pHint);
+
+    ReadFile(fbuff,pFlags);
+    SetIOHandler(io);
+
+    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    return pimpl->mScene;
+}
+
+// ------------------------------------------------------------------------------------------------
+void WriteLogOpening(const std::string& file)
+{
+    ASSIMP_LOG_INFO_F("Load ", file);
+
+    // print a full version dump. This is nice because we don't
+    // need to ask the authors of incoming bug reports for
+    // the library version they're using - a log dump is
+    // sufficient.
+    const unsigned int flags( aiGetCompileFlags() );
+    std::stringstream stream;
+    stream << "Assimp " << aiGetVersionMajor() << "." << aiGetVersionMinor() << "." << aiGetVersionRevision() << " "
+#if defined(ASSIMP_BUILD_ARCHITECTURE)
+        << ASSIMP_BUILD_ARCHITECTURE
+#elif defined(_M_IX86) || defined(__x86_32__) || defined(__i386__)
+        << "x86"
+#elif defined(_M_X64) || defined(__x86_64__)
+        << "amd64"
+#elif defined(_M_IA64) || defined(__ia64__)
+        << "itanium"
+#elif defined(__ppc__) || defined(__powerpc__)
+        << "ppc32"
+#elif defined(__powerpc64__)
+        << "ppc64"
+#elif defined(__arm__)
+        << "arm"
+#else
+        << "<unknown architecture>"
+#endif
+        << " "
+#if defined(ASSIMP_BUILD_COMPILER)
+        << ( ASSIMP_BUILD_COMPILER )
+#elif defined(_MSC_VER)
+        << "msvc"
+#elif defined(__GNUC__)
+        << "gcc"
+#else
+        << "<unknown compiler>"
+#endif
+
+#ifdef ASSIMP_BUILD_DEBUG
+        << " debug"
+#endif
+
+        << (flags & ASSIMP_CFLAGS_NOBOOST ? " noboost" : "")
+        << (flags & ASSIMP_CFLAGS_SHARED  ? " shared" : "")
+        << (flags & ASSIMP_CFLAGS_SINGLETHREADED  ? " singlethreaded" : "");
+
+        ASSIMP_LOG_DEBUG(stream.str());
+}
+
+// ------------------------------------------------------------------------------------------------
+// Reads the given file and returns its contents if successful.
+const aiScene* Importer::ReadFile( const char* _pFile, unsigned int pFlags)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    const std::string pFile(_pFile);
+
+    // ----------------------------------------------------------------------
+    // Put a large try block around everything to catch all std::exception's
+    // that might be thrown by STL containers or by new().
+    // ImportErrorException's are throw by ourselves and caught elsewhere.
+    //-----------------------------------------------------------------------
+
+    WriteLogOpening(pFile);
+
+#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
+    try
+#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
+    {
+        // Check whether this Importer instance has already loaded
+        // a scene. In this case we need to delete the old one
+        if (pimpl->mScene)  {
+
+            ASSIMP_LOG_DEBUG("(Deleting previous scene)");
+            FreeScene();
+        }
+
+        // First check if the file is accessible at all
+        if( !pimpl->mIOHandler->Exists( pFile)) {
+
+            pimpl->mErrorString = "Unable to open file \"" + pFile + "\".";
+            ASSIMP_LOG_ERROR(pimpl->mErrorString);
+            return NULL;
+        }
+
+        std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
+        if (profiler) {
+            profiler->BeginRegion("total");
+        }
+
+        // Find an worker class which can handle the file
+        BaseImporter* imp = NULL;
+        for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
+
+            if( pimpl->mImporter[a]->CanRead( pFile, pimpl->mIOHandler, false)) {
+                imp = pimpl->mImporter[a];
+                break;
+            }
+        }
+
+        if (!imp)   {
+            // not so bad yet ... try format auto detection.
+            const std::string::size_type s = pFile.find_last_of('.');
+            if (s != std::string::npos) {
+                ASSIMP_LOG_INFO("File extension not known, trying signature-based detection");
+                for( unsigned int a = 0; a < pimpl->mImporter.size(); a++)  {
+                    if( pimpl->mImporter[a]->CanRead( pFile, pimpl->mIOHandler, true)) {
+                        imp = pimpl->mImporter[a];
+                        break;
+                    }
+                }
+            }
+            // Put a proper error message if no suitable importer was found
+            if( !imp)   {
+                pimpl->mErrorString = "No suitable reader found for the file format of file \"" + pFile + "\".";
+                ASSIMP_LOG_ERROR(pimpl->mErrorString);
+                return NULL;
+            }
+        }
+
+        // Get file size for progress handler
+        IOStream * fileIO = pimpl->mIOHandler->Open( pFile );
+        uint32_t fileSize = 0;
+        if (fileIO)
+        {
+            fileSize = static_cast<uint32_t>(fileIO->FileSize());
+            pimpl->mIOHandler->Close( fileIO );
+        }
+
+        // Dispatch the reading to the worker class for this format
+        const aiImporterDesc *desc( imp->GetInfo() );
+        std::string ext( "unknown" );
+        if ( NULL != desc ) {
+            ext = desc->mName;
+        }
+        ASSIMP_LOG_INFO("Found a matching importer for this file format: " + ext + "." );
+        pimpl->mProgressHandler->UpdateFileRead( 0, fileSize );
+
+        if (profiler) {
+            profiler->BeginRegion("import");
+        }
+
+        pimpl->mScene = imp->ReadFile( this, pFile, pimpl->mIOHandler);
+        pimpl->mProgressHandler->UpdateFileRead( fileSize, fileSize );
+
+        if (profiler) {
+            profiler->EndRegion("import");
+        }
+
+        SetPropertyString("sourceFilePath", pFile);
+
+        // If successful, apply all active post processing steps to the imported data
+        if( pimpl->mScene)  {
+
+#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+            // The ValidateDS process is an exception. It is executed first, even before ScenePreprocessor is called.
+            if (pFlags & aiProcess_ValidateDataStructure)
+            {
+                ValidateDSProcess ds;
+                ds.ExecuteOnScene (this);
+                if (!pimpl->mScene) {
+                    return NULL;
+                }
+            }
+#endif // no validation
+
+            // Preprocess the scene and prepare it for post-processing
+            if (profiler) {
+                profiler->BeginRegion("preprocess");
+            }
+
+            ScenePreprocessor pre(pimpl->mScene);
+            pre.ProcessScene();
+
+            if (profiler) {
+                profiler->EndRegion("preprocess");
+            }
+
+            // Ensure that the validation process won't be called twice
+            ApplyPostProcessing(pFlags & (~aiProcess_ValidateDataStructure));
+        }
+        // if failed, extract the error string
+        else if( !pimpl->mScene) {
+            pimpl->mErrorString = imp->GetErrorText();
+        }
+
+        // clear any data allocated by post-process steps
+        pimpl->mPPShared->Clean();
+
+        if (profiler) {
+            profiler->EndRegion("total");
+        }
+    }
+#ifdef ASSIMP_CATCH_GLOBAL_EXCEPTIONS
+    catch (std::exception &e)
+    {
+#if (defined _MSC_VER) &&   (defined _CPPRTTI)
+        // if we have RTTI get the full name of the exception that occurred
+        pimpl->mErrorString = std::string(typeid( e ).name()) + ": " + e.what();
+#else
+        pimpl->mErrorString = std::string("std::exception: ") + e.what();
+#endif
+
+        ASSIMP_LOG_ERROR(pimpl->mErrorString);
+        delete pimpl->mScene; pimpl->mScene = NULL;
+    }
+#endif // ! ASSIMP_CATCH_GLOBAL_EXCEPTIONS
+
+    // either successful or failure - the pointer expresses it anyways
+    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    return pimpl->mScene;
+}
+
+
+// ------------------------------------------------------------------------------------------------
+// Apply post-processing to the currently bound scene
+const aiScene* Importer::ApplyPostProcessing(unsigned int pFlags)
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    // Return immediately if no scene is active
+    if (!pimpl->mScene) {
+        return NULL;
+    }
+
+    // If no flags are given, return the current scene with no further action
+    if (!pFlags) {
+        return pimpl->mScene;
+    }
+
+    // In debug builds: run basic flag validation
+    ai_assert(_ValidateFlags(pFlags));
+    ASSIMP_LOG_INFO("Entering post processing pipeline");
+
+#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+    // The ValidateDS process plays an exceptional role. It isn't contained in the global
+    // list of post-processing steps, so we need to call it manually.
+    if (pFlags & aiProcess_ValidateDataStructure)
+    {
+        ValidateDSProcess ds;
+        ds.ExecuteOnScene (this);
+        if (!pimpl->mScene) {
+            return NULL;
+        }
+    }
+#endif // no validation
+#ifdef ASSIMP_BUILD_DEBUG
+    if (pimpl->bExtraVerbose)
+    {
+#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+        ASSIMP_LOG_ERROR("Verbose Import is not available due to build settings");
+#endif  // no validation
+        pFlags |= aiProcess_ValidateDataStructure;
+    }
+#else
+    if (pimpl->bExtraVerbose) {
+        ASSIMP_LOG_WARN("Not a debug build, ignoring extra verbose setting");
+    }
+#endif // ! DEBUG
+
+    std::unique_ptr<Profiler> profiler(GetPropertyInteger(AI_CONFIG_GLOB_MEASURE_TIME,0)?new Profiler():NULL);
+    for( unsigned int a = 0; a < pimpl->mPostProcessingSteps.size(); a++)   {
+
+        BaseProcess* process = pimpl->mPostProcessingSteps[a];
+        pimpl->mProgressHandler->UpdatePostProcess(static_cast<int>(a), static_cast<int>(pimpl->mPostProcessingSteps.size()) );
+        if( process->IsActive( pFlags)) {
+
+            if (profiler) {
+                profiler->BeginRegion("postprocess");
+            }
+
+            process->ExecuteOnScene ( this );
+
+            if (profiler) {
+                profiler->EndRegion("postprocess");
+            }
+        }
+        if( !pimpl->mScene) {
+            break;
+        }
+#ifdef ASSIMP_BUILD_DEBUG
+
+#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+        continue;
+#endif  // no validation
+
+        // If the extra verbose mode is active, execute the ValidateDataStructureStep again - after each step
+        if (pimpl->bExtraVerbose)   {
+            ASSIMP_LOG_DEBUG("Verbose Import: re-validating data structures");
+
+            ValidateDSProcess ds;
+            ds.ExecuteOnScene (this);
+            if( !pimpl->mScene) {
+                ASSIMP_LOG_ERROR("Verbose Import: failed to re-validate data structures");
+                break;
+            }
+        }
+#endif // ! DEBUG
+    }
+    pimpl->mProgressHandler->UpdatePostProcess( static_cast<int>(pimpl->mPostProcessingSteps.size()), 
+        static_cast<int>(pimpl->mPostProcessingSteps.size()) );
+
+    // update private scene flags
+    if( pimpl->mScene )
+      ScenePriv(pimpl->mScene)->mPPStepsApplied |= pFlags;
+
+    // clear any data allocated by post-process steps
+    pimpl->mPPShared->Clean();
+    ASSIMP_LOG_INFO("Leaving post processing pipeline");
+
+    ASSIMP_END_EXCEPTION_REGION(const aiScene*);
+    return pimpl->mScene;
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiScene* Importer::ApplyCustomizedPostProcessing( BaseProcess *rootProcess, bool requestValidation ) {
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // Return immediately if no scene is active
+    if ( NULL == pimpl->mScene ) {
+        return NULL;
+    }
+
+    // If no flags are given, return the current scene with no further action
+    if ( NULL == rootProcess ) {
+        return pimpl->mScene;
+    }
+
+    // In debug builds: run basic flag validation
+    ASSIMP_LOG_INFO( "Entering customized post processing pipeline" );
+
+#ifndef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+    // The ValidateDS process plays an exceptional role. It isn't contained in the global
+    // list of post-processing steps, so we need to call it manually.
+    if ( requestValidation )
+    {
+        ValidateDSProcess ds;
+        ds.ExecuteOnScene( this );
+        if ( !pimpl->mScene ) {
+            return NULL;
+        }
+    }
+#endif // no validation
+#ifdef ASSIMP_BUILD_DEBUG
+    if ( pimpl->bExtraVerbose )
+    {
+#ifdef ASSIMP_BUILD_NO_VALIDATEDS_PROCESS
+        ASSIMP_LOG_ERROR( "Verbose Import is not available due to build settings" );
+#endif  // no validation
+    }
+#else
+    if ( pimpl->bExtraVerbose ) {
+        ASSIMP_LOG_WARN( "Not a debug build, ignoring extra verbose setting" );
+    }
+#endif // ! DEBUG
+
+    std::unique_ptr<Profiler> profiler( GetPropertyInteger( AI_CONFIG_GLOB_MEASURE_TIME, 0 ) ? new Profiler() : NULL );
+
+    if ( profiler ) {
+        profiler->BeginRegion( "postprocess" );
+    }
+
+    rootProcess->ExecuteOnScene( this );
+
+    if ( profiler ) {
+        profiler->EndRegion( "postprocess" );
+    }
+
+    // If the extra verbose mode is active, execute the ValidateDataStructureStep again - after each step
+    if ( pimpl->bExtraVerbose || requestValidation  ) {
+        ASSIMP_LOG_DEBUG( "Verbose Import: revalidating data structures" );
+
+        ValidateDSProcess ds;
+        ds.ExecuteOnScene( this );
+        if ( !pimpl->mScene ) {
+            ASSIMP_LOG_ERROR( "Verbose Import: failed to revalidate data structures" );
+        }
+    }
+
+    // clear any data allocated by post-process steps
+    pimpl->mPPShared->Clean();
+    ASSIMP_LOG_INFO( "Leaving customized post processing pipeline" );
+
+    ASSIMP_END_EXCEPTION_REGION( const aiScene* );
+
+    return pimpl->mScene;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Helper function to check whether an extension is supported by ASSIMP
+bool Importer::IsExtensionSupported(const char* szExtension) const
+{
+    return nullptr != GetImporter(szExtension);
+}
+
+// ------------------------------------------------------------------------------------------------
+size_t Importer::GetImporterCount() const
+{
+    return pimpl->mImporter.size();
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiImporterDesc* Importer::GetImporterInfo(size_t index) const
+{
+    if (index >= pimpl->mImporter.size()) {
+        return NULL;
+    }
+    return pimpl->mImporter[index]->GetInfo();
+}
+
+
+// ------------------------------------------------------------------------------------------------
+BaseImporter* Importer::GetImporter (size_t index) const
+{
+    if (index >= pimpl->mImporter.size()) {
+        return NULL;
+    }
+    return pimpl->mImporter[index];
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find a loader plugin for a given file extension
+BaseImporter* Importer::GetImporter (const char* szExtension) const
+{
+    return GetImporter(GetImporterIndex(szExtension));
+}
+
+// ------------------------------------------------------------------------------------------------
+// Find a loader plugin for a given file extension
+size_t Importer::GetImporterIndex (const char* szExtension) const {
+    ai_assert(nullptr != szExtension);
+
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+
+    // skip over wildcard and dot characters at string head --
+    for ( ; *szExtension == '*' || *szExtension == '.'; ++szExtension );
+
+    std::string ext(szExtension);
+    if (ext.empty()) {
+        return static_cast<size_t>(-1);
+    }
+    std::transform( ext.begin(), ext.end(), ext.begin(), ToLower<char> );
+
+    std::set<std::string> str;
+    for (std::vector<BaseImporter*>::const_iterator i =  pimpl->mImporter.begin();i != pimpl->mImporter.end();++i)  {
+        str.clear();
+
+        (*i)->GetExtensionList(str);
+        for (std::set<std::string>::const_iterator it = str.begin(); it != str.end(); ++it) {
+            if (ext == *it) {
+                return std::distance(static_cast< std::vector<BaseImporter*>::const_iterator >(pimpl->mImporter.begin()), i);
+            }
+        }
+    }
+    ASSIMP_END_EXCEPTION_REGION(size_t);
+    return static_cast<size_t>(-1);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Helper function to build a list of all file extensions supported by ASSIMP
+void Importer::GetExtensionList(aiString& szOut) const
+{
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+    std::set<std::string> str;
+    for (std::vector<BaseImporter*>::const_iterator i =  pimpl->mImporter.begin();i != pimpl->mImporter.end();++i)  {
+        (*i)->GetExtensionList(str);
+    }
+
+	// List can be empty
+	if( !str.empty() ) {
+		for (std::set<std::string>::const_iterator it = str.begin();; ) {
+			szOut.Append("*.");
+			szOut.Append((*it).c_str());
+
+			if (++it == str.end()) {
+				break;
+			}
+			szOut.Append(";");
+		}
+	}
+    ASSIMP_END_EXCEPTION_REGION(void);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool Importer::SetPropertyInteger(const char* szName, int iValue)
+{
+    bool existing;
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+        existing = SetGenericProperty<int>(pimpl->mIntProperties, szName,iValue);
+    ASSIMP_END_EXCEPTION_REGION(bool);
+    return existing;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool Importer::SetPropertyFloat(const char* szName, ai_real iValue)
+{
+    bool existing;
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+        existing = SetGenericProperty<ai_real>(pimpl->mFloatProperties, szName,iValue);
+    ASSIMP_END_EXCEPTION_REGION(bool);
+    return existing;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool Importer::SetPropertyString(const char* szName, const std::string& value)
+{
+    bool existing;
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+        existing = SetGenericProperty<std::string>(pimpl->mStringProperties, szName,value);
+    ASSIMP_END_EXCEPTION_REGION(bool);
+    return existing;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Set a configuration property
+bool Importer::SetPropertyMatrix(const char* szName, const aiMatrix4x4& value)
+{
+    bool existing;
+    ASSIMP_BEGIN_EXCEPTION_REGION();
+        existing = SetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties, szName,value);
+    ASSIMP_END_EXCEPTION_REGION(bool);
+    return existing;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+int Importer::GetPropertyInteger(const char* szName,
+    int iErrorReturn /*= 0xffffffff*/) const
+{
+    return GetGenericProperty<int>(pimpl->mIntProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+ai_real Importer::GetPropertyFloat(const char* szName,
+    ai_real iErrorReturn /*= 10e10*/) const
+{
+    return GetGenericProperty<ai_real>(pimpl->mFloatProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+const std::string Importer::GetPropertyString(const char* szName,
+    const std::string& iErrorReturn /*= ""*/) const
+{
+    return GetGenericProperty<std::string>(pimpl->mStringProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get a configuration property
+const aiMatrix4x4 Importer::GetPropertyMatrix(const char* szName,
+    const aiMatrix4x4& iErrorReturn /*= aiMatrix4x4()*/) const
+{
+    return GetGenericProperty<aiMatrix4x4>(pimpl->mMatrixProperties,szName,iErrorReturn);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the memory requirements of a single node
+inline void AddNodeWeight(unsigned int& iScene,const aiNode* pcNode)
+{
+    iScene += sizeof(aiNode);
+    iScene += sizeof(unsigned int) * pcNode->mNumMeshes;
+    iScene += sizeof(void*) * pcNode->mNumChildren;
+
+    for (unsigned int i = 0; i < pcNode->mNumChildren;++i) {
+        AddNodeWeight(iScene,pcNode->mChildren[i]);
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Get the memory requirements of the scene
+void Importer::GetMemoryRequirements(aiMemoryInfo& in) const
+{
+    in = aiMemoryInfo();
+    aiScene* mScene = pimpl->mScene;
+
+    // return if we have no scene loaded
+    if (!pimpl->mScene)
+        return;
+
+
+    in.total = sizeof(aiScene);
+
+    // add all meshes
+    for (unsigned int i = 0; i < mScene->mNumMeshes;++i)
+    {
+        in.meshes += sizeof(aiMesh);
+        if (mScene->mMeshes[i]->HasPositions()) {
+            in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
+        }
+
+        if (mScene->mMeshes[i]->HasNormals()) {
+            in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
+        }
+
+        if (mScene->mMeshes[i]->HasTangentsAndBitangents()) {
+            in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices * 2;
+        }
+
+        for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_COLOR_SETS;++a) {
+            if (mScene->mMeshes[i]->HasVertexColors(a)) {
+                in.meshes += sizeof(aiColor4D) * mScene->mMeshes[i]->mNumVertices;
+            }
+            else break;
+        }
+        for (unsigned int a = 0; a < AI_MAX_NUMBER_OF_TEXTURECOORDS;++a) {
+            if (mScene->mMeshes[i]->HasTextureCoords(a)) {
+                in.meshes += sizeof(aiVector3D) * mScene->mMeshes[i]->mNumVertices;
+            }
+            else break;
+        }
+        if (mScene->mMeshes[i]->HasBones()) {
+            in.meshes += sizeof(void*) * mScene->mMeshes[i]->mNumBones;
+            for (unsigned int p = 0; p < mScene->mMeshes[i]->mNumBones;++p) {
+                in.meshes += sizeof(aiBone);
+                in.meshes += mScene->mMeshes[i]->mBones[p]->mNumWeights * sizeof(aiVertexWeight);
+            }
+        }
+        in.meshes += (sizeof(aiFace) + 3 * sizeof(unsigned int))*mScene->mMeshes[i]->mNumFaces;
+    }
+    in.total += in.meshes;
+
+    // add all embedded textures
+    for (unsigned int i = 0; i < mScene->mNumTextures;++i) {
+        const aiTexture* pc = mScene->mTextures[i];
+        in.textures += sizeof(aiTexture);
+        if (pc->mHeight) {
+            in.textures += 4 * pc->mHeight * pc->mWidth;
+        }
+        else in.textures += pc->mWidth;
+    }
+    in.total += in.textures;
+
+    // add all animations
+    for (unsigned int i = 0; i < mScene->mNumAnimations;++i) {
+        const aiAnimation* pc = mScene->mAnimations[i];
+        in.animations += sizeof(aiAnimation);
+
+        // add all bone anims
+        for (unsigned int a = 0; a < pc->mNumChannels; ++a) {
+            const aiNodeAnim* pc2 = pc->mChannels[i];
+            in.animations += sizeof(aiNodeAnim);
+            in.animations += pc2->mNumPositionKeys * sizeof(aiVectorKey);
+            in.animations += pc2->mNumScalingKeys * sizeof(aiVectorKey);
+            in.animations += pc2->mNumRotationKeys * sizeof(aiQuatKey);
+        }
+    }
+    in.total += in.animations;
+
+    // add all cameras and all lights
+    in.total += in.cameras = sizeof(aiCamera) *  mScene->mNumCameras;
+    in.total += in.lights  = sizeof(aiLight)  *  mScene->mNumLights;
+
+    // add all nodes
+    AddNodeWeight(in.nodes,mScene->mRootNode);
+    in.total += in.nodes;
+
+    // add all materials
+    for (unsigned int i = 0; i < mScene->mNumMaterials;++i) {
+        const aiMaterial* pc = mScene->mMaterials[i];
+        in.materials += sizeof(aiMaterial);
+        in.materials += pc->mNumAllocated * sizeof(void*);
+
+        for (unsigned int a = 0; a < pc->mNumProperties;++a) {
+            in.materials += pc->mProperties[a]->mDataLength;
+        }
+    }
+    in.total += in.materials;
+}

+ 247 - 0
thirdparty/assimp/code/Importer.h

@@ -0,0 +1,247 @@
+/*
+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 Importer.h mostly internal stuff for use by #Assimp::Importer */
+#pragma once
+#ifndef INCLUDED_AI_IMPORTER_H
+#define INCLUDED_AI_IMPORTER_H
+
+#include <map>
+#include <vector>
+#include <string>
+#include <assimp/matrix4x4.h>
+
+struct aiScene;
+
+namespace Assimp    {
+    class ProgressHandler;
+    class IOSystem;
+    class BaseImporter;
+    class BaseProcess;
+    class SharedPostProcessInfo;
+
+
+//! @cond never
+// ---------------------------------------------------------------------------
+/** @brief Internal PIMPL implementation for Assimp::Importer
+ *
+ *  Using this idiom here allows us to drop the dependency from
+ *  std::vector and std::map in the public headers. Furthermore we are dropping
+ *  any STL interface problems caused by mismatching STL settings. All
+ *  size calculation are now done by us, not the app heap. */
+class ImporterPimpl {
+public:
+    // Data type to store the key hash
+    typedef unsigned int KeyType;
+
+    // typedefs for our four configuration maps.
+    // We don't need more, so there is no need for a generic solution
+    typedef std::map<KeyType, int> IntPropertyMap;
+    typedef std::map<KeyType, ai_real> FloatPropertyMap;
+    typedef std::map<KeyType, std::string> StringPropertyMap;
+    typedef std::map<KeyType, aiMatrix4x4> MatrixPropertyMap;
+
+    /** IO handler to use for all file accesses. */
+    IOSystem* mIOHandler;
+    bool mIsDefaultHandler;
+
+    /** Progress handler for feedback. */
+    ProgressHandler* mProgressHandler;
+    bool mIsDefaultProgressHandler;
+
+    /** Format-specific importer worker objects - one for each format we can read.*/
+    std::vector< BaseImporter* > mImporter;
+
+    /** Post processing steps we can apply at the imported data. */
+    std::vector< BaseProcess* > mPostProcessingSteps;
+
+    /** The imported data, if ReadFile() was successful, NULL otherwise. */
+    aiScene* mScene;
+
+    /** The error description, if there was one. */
+    std::string mErrorString;
+
+    /** List of integer properties */
+    IntPropertyMap mIntProperties;
+
+    /** List of floating-point properties */
+    FloatPropertyMap mFloatProperties;
+
+    /** List of string properties */
+    StringPropertyMap mStringProperties;
+
+    /** List of Matrix properties */
+    MatrixPropertyMap mMatrixProperties;
+
+    /** Used for testing - extra verbose mode causes the ValidateDataStructure-Step
+     *  to be executed before and after every single post-process step */
+    bool bExtraVerbose;
+
+    /** Used by post-process steps to share data */
+    SharedPostProcessInfo* mPPShared;
+
+    /// The default class constructor.
+    ImporterPimpl() AI_NO_EXCEPT;
+};
+
+inline
+ImporterPimpl::ImporterPimpl() AI_NO_EXCEPT
+: mIOHandler( nullptr )
+, mIsDefaultHandler( false )
+, mProgressHandler( nullptr )
+, mIsDefaultProgressHandler( false )
+, mImporter()
+, mPostProcessingSteps()
+, mScene( nullptr )
+, mErrorString()
+, mIntProperties()
+, mFloatProperties()
+, mStringProperties()
+, mMatrixProperties()
+, bExtraVerbose( false )
+, mPPShared( nullptr ) {
+    // empty
+}
+//! @endcond
+
+
+struct BatchData;
+
+// ---------------------------------------------------------------------------
+/** FOR IMPORTER PLUGINS ONLY: A helper class to the pleasure of importers
+ *  that need to load many external meshes recursively.
+ *
+ *  The class uses several threads to load these meshes (or at least it
+ *  could, this has not yet been implemented at the moment).
+ *
+ *  @note The class may not be used by more than one thread*/
+class ASSIMP_API BatchLoader
+{
+    // friend of Importer
+
+public:
+    //! @cond never
+    // -------------------------------------------------------------------
+    /** Wraps a full list of configuration properties for an importer.
+     *  Properties can be set using SetGenericProperty */
+    struct PropertyMap
+    {
+        ImporterPimpl::IntPropertyMap     ints;
+        ImporterPimpl::FloatPropertyMap   floats;
+        ImporterPimpl::StringPropertyMap  strings;
+        ImporterPimpl::MatrixPropertyMap  matrices;
+
+        bool operator == (const PropertyMap& prop) const {
+            // fixme: really isocpp? gcc complains
+            return ints == prop.ints && floats == prop.floats && strings == prop.strings && matrices == prop.matrices;
+        }
+
+        bool empty () const {
+            return ints.empty() && floats.empty() && strings.empty() && matrices.empty();
+        }
+    };
+    //! @endcond
+
+public:
+    // -------------------------------------------------------------------
+    /** Construct a batch loader from a given IO system to be used
+     *  to access external files 
+     */
+    explicit BatchLoader(IOSystem* pIO, bool validate = false );
+
+    // -------------------------------------------------------------------
+    /** The class destructor.
+     */
+    ~BatchLoader();
+
+    // -------------------------------------------------------------------
+    /** Sets the validation step. True for enable validation during postprocess.
+     *  @param  enable  True for validation.
+     */
+    void setValidation( bool enabled );
+    
+    // -------------------------------------------------------------------
+    /** Returns the current validation step.
+     *  @return The current validation step.
+     */
+    bool getValidation() const;
+    
+    // -------------------------------------------------------------------
+    /** Add a new file to the list of files to be loaded.
+     *  @param file File to be loaded
+     *  @param steps Post-processing steps to be executed on the file
+     *  @param map Optional configuration properties
+     *  @return 'Load request channel' - an unique ID that can later
+     *    be used to access the imported file data.
+     *  @see GetImport */
+    unsigned int AddLoadRequest (
+        const std::string& file,
+        unsigned int steps = 0,
+        const PropertyMap* map = NULL
+        );
+
+    // -------------------------------------------------------------------
+    /** Get an imported scene.
+     *  This polls the import from the internal request list.
+     *  If an import is requested several times, this function
+     *  can be called several times, too.
+     *
+     *  @param which LRWC returned by AddLoadRequest().
+     *  @return NULL if there is no scene with this file name
+     *  in the queue of the scene hasn't been loaded yet. */
+    aiScene* GetImport(
+        unsigned int which
+        );
+
+    // -------------------------------------------------------------------
+    /** Waits until all scenes have been loaded. This returns
+     *  immediately if no scenes are queued.*/
+    void LoadAll();
+
+private:
+    // No need to have that in the public API ...
+    BatchData *m_data;
+};
+
+} // Namespace Assimp
+
+#endif // INCLUDED_AI_IMPORTER_H

+ 371 - 0
thirdparty/assimp/code/ImporterRegistry.cpp

@@ -0,0 +1,371 @@
+/*
+---------------------------------------------------------------------------
+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 ImporterRegistry.cpp
+
+Central registry for all importers available. Do not edit this file
+directly (unless you are adding new loaders), instead use the
+corresponding preprocessor flag to selectively disable formats.
+*/
+
+#include <vector>
+#include <assimp/BaseImporter.h>
+
+// ------------------------------------------------------------------------------------------------
+// Importers
+// (include_new_importers_here)
+// ------------------------------------------------------------------------------------------------
+#ifndef ASSIMP_BUILD_NO_X_IMPORTER
+#   include "XFileImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
+#   include "AMFImporter.hpp"
+#endif
+#ifndef ASSIMP_BUILD_NO_3DS_IMPORTER
+#   include "3DSLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_MD3_IMPORTER
+#   include "MD3Loader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_MDL_IMPORTER
+#   include "MDLLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_MD2_IMPORTER
+#   include "MD2Loader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_PLY_IMPORTER
+#   include "PlyLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_ASE_IMPORTER
+#   include "ASELoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_OBJ_IMPORTER
+#   include "ObjFileImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_HMP_IMPORTER
+#   include "HMPLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_SMD_IMPORTER
+#   include "SMDLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_MDC_IMPORTER
+#   include "MDCLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_MD5_IMPORTER
+#   include "MD5Loader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_STL_IMPORTER
+#   include "STLLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_LWO_IMPORTER
+#   include "LWOLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_DXF_IMPORTER
+#   include "DXFLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_NFF_IMPORTER
+#   include "NFFLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_RAW_IMPORTER
+#   include "RawLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_SIB_IMPORTER
+#   include "SIBImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_OFF_IMPORTER
+#   include "OFFLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_AC_IMPORTER
+#   include "ACLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_BVH_IMPORTER
+#   include "BVHLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_IRRMESH_IMPORTER
+#   include "IRRMeshLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_IRR_IMPORTER
+#   include "IRRLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_Q3D_IMPORTER
+#   include "Q3DLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_B3D_IMPORTER
+#   include "B3DImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_COLLADA_IMPORTER
+#   include "ColladaLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_TERRAGEN_IMPORTER
+#   include "TerragenLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_CSM_IMPORTER
+#   include "CSMLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_3D_IMPORTER
+#   include "UnrealLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_LWS_IMPORTER
+#   include "LWSLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_OGRE_IMPORTER
+#   include "OgreImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_OPENGEX_IMPORTER
+#   include "OpenGEXImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_MS3D_IMPORTER
+#   include "MS3DLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_COB_IMPORTER
+#   include "COBLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_BLEND_IMPORTER
+#   include "BlenderLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_Q3BSP_IMPORTER
+#   include "Q3BSPFileImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_NDO_IMPORTER
+#   include "NDOLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_IFC_IMPORTER
+#   include "Importer/IFC/IFCLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_XGL_IMPORTER
+#   include "XGLLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_FBX_IMPORTER
+#   include "FBXImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_ASSBIN_IMPORTER
+#   include "AssbinLoader.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_GLTF_IMPORTER
+#   include "glTFImporter.h"
+#   include "glTF2Importer.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_C4D_IMPORTER
+#   include "C4DImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_3MF_IMPORTER
+#   include "D3MFImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
+#   include "X3DImporter.hpp"
+#endif
+#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
+#   include "MMDImporter.h"
+#endif
+#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
+#   include "Importer/StepFile/StepFileImporter.h"
+#endif
+
+namespace Assimp {
+
+// ------------------------------------------------------------------------------------------------
+void GetImporterInstanceList(std::vector< BaseImporter* >& out)
+{
+    // ----------------------------------------------------------------------------
+    // Add an instance of each worker class here
+    // (register_new_importers_here)
+    // ----------------------------------------------------------------------------
+    out.reserve(64);
+#if (!defined ASSIMP_BUILD_NO_X_IMPORTER)
+    out.push_back( new XFileImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_OBJ_IMPORTER)
+    out.push_back( new ObjFileImporter());
+#endif
+#ifndef ASSIMP_BUILD_NO_AMF_IMPORTER
+	out.push_back( new AMFImporter() );
+#endif
+#if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER)
+    out.push_back( new Discreet3DSImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_MD3_IMPORTER)
+    out.push_back( new MD3Importer());
+#endif
+#if (!defined ASSIMP_BUILD_NO_MD2_IMPORTER)
+    out.push_back( new MD2Importer());
+#endif
+#if (!defined ASSIMP_BUILD_NO_PLY_IMPORTER)
+    out.push_back( new PLYImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_MDL_IMPORTER)
+    out.push_back( new MDLImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_ASE_IMPORTER)
+  #if (!defined ASSIMP_BUILD_NO_3DS_IMPORTER)
+    out.push_back( new ASEImporter());
+#  endif
+#endif
+#if (!defined ASSIMP_BUILD_NO_HMP_IMPORTER)
+    out.push_back( new HMPImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_SMD_IMPORTER)
+    out.push_back( new SMDImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_MDC_IMPORTER)
+    out.push_back( new MDCImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_MD5_IMPORTER)
+    out.push_back( new MD5Importer());
+#endif
+#if (!defined ASSIMP_BUILD_NO_STL_IMPORTER)
+    out.push_back( new STLImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_LWO_IMPORTER)
+    out.push_back( new LWOImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_DXF_IMPORTER)
+    out.push_back( new DXFImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_NFF_IMPORTER)
+    out.push_back( new NFFImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_RAW_IMPORTER)
+    out.push_back( new RAWImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_SIB_IMPORTER)
+    out.push_back( new SIBImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_OFF_IMPORTER)
+    out.push_back( new OFFImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_AC_IMPORTER)
+    out.push_back( new AC3DImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_BVH_IMPORTER)
+    out.push_back( new BVHLoader());
+#endif
+#if (!defined ASSIMP_BUILD_NO_IRRMESH_IMPORTER)
+    out.push_back( new IRRMeshImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_IRR_IMPORTER)
+    out.push_back( new IRRImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_Q3D_IMPORTER)
+    out.push_back( new Q3DImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_B3D_IMPORTER)
+    out.push_back( new B3DImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_COLLADA_IMPORTER)
+    out.push_back( new ColladaLoader());
+#endif
+#if (!defined ASSIMP_BUILD_NO_TERRAGEN_IMPORTER)
+    out.push_back( new TerragenImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_CSM_IMPORTER)
+    out.push_back( new CSMImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_3D_IMPORTER)
+    out.push_back( new UnrealImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_LWS_IMPORTER)
+    out.push_back( new LWSImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_OGRE_IMPORTER)
+    out.push_back( new Ogre::OgreImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_OPENGEX_IMPORTER )
+    out.push_back( new OpenGEX::OpenGEXImporter() );
+#endif
+#if (!defined ASSIMP_BUILD_NO_MS3D_IMPORTER)
+    out.push_back( new MS3DImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_COB_IMPORTER)
+    out.push_back( new COBImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_BLEND_IMPORTER)
+    out.push_back( new BlenderImporter());
+#endif
+#if (!defined ASSIMP_BUILD_NO_Q3BSP_IMPORTER)
+    out.push_back( new Q3BSPFileImporter() );
+#endif
+#if (!defined ASSIMP_BUILD_NO_NDO_IMPORTER)
+    out.push_back( new NDOImporter() );
+#endif
+#if (!defined ASSIMP_BUILD_NO_IFC_IMPORTER)
+    out.push_back( new IFCImporter() );
+#endif
+#if ( !defined ASSIMP_BUILD_NO_XGL_IMPORTER )
+    out.push_back( new XGLImporter() );
+#endif
+#if ( !defined ASSIMP_BUILD_NO_FBX_IMPORTER )
+    out.push_back( new FBXImporter() );
+#endif
+#if ( !defined ASSIMP_BUILD_NO_ASSBIN_IMPORTER )
+    out.push_back( new AssbinImporter() );
+#endif
+#if ( !defined ASSIMP_BUILD_NO_GLTF_IMPORTER )
+    out.push_back( new glTFImporter() );
+    out.push_back( new glTF2Importer() );
+#endif
+#if ( !defined ASSIMP_BUILD_NO_C4D_IMPORTER )
+    out.push_back( new C4DImporter() );
+#endif
+#if ( !defined ASSIMP_BUILD_NO_3MF_IMPORTER )
+    out.push_back( new D3MFImporter() );
+#endif
+#ifndef ASSIMP_BUILD_NO_X3D_IMPORTER
+    out.push_back( new X3DImporter() );
+#endif
+#ifndef ASSIMP_BUILD_NO_MMD_IMPORTER
+    out.push_back( new MMDImporter() );
+#endif
+#ifndef ASSIMP_BUILD_NO_STEP_IMPORTER
+    out.push_back(new StepFile::StepFileImporter());
+#endif
+}
+
+/** will delete all registered importers. */
+void DeleteImporterInstanceList(std::vector< BaseImporter* >& deleteList){
+	for(size_t i= 0; i<deleteList.size();++i){
+		delete deleteList[i];
+		deleteList[i]=NULL;
+	}//for
+}
+
+} // namespace Assimp

+ 386 - 0
thirdparty/assimp/code/ImproveCacheLocality.cpp

@@ -0,0 +1,386 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of the post processing step to improve the cache locality of a mesh.
+ * <br>
+ * The algorithm is roughly basing on this paper:
+ * http://www.cs.princeton.edu/gfx/pubs/Sander_2007_%3ETR/tipsy.pdf
+ *   .. although overdraw rduction isn't implemented yet ...
+ */
+
+
+
+// internal headers
+#include "ImproveCacheLocality.h"
+#include "VertexTriangleAdjacency.h"
+#include <assimp/StringUtils.h>
+#include <assimp/postprocess.h>
+#include <assimp/scene.h>
+#include <assimp/DefaultLogger.hpp>
+#include <stdio.h>
+#include <stack>
+
+using namespace Assimp;
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+ImproveCacheLocalityProcess::ImproveCacheLocalityProcess() {
+    configCacheDepth = PP_ICL_PTCACHE_SIZE;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+ImproveCacheLocalityProcess::~ImproveCacheLocalityProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool ImproveCacheLocalityProcess::IsActive( unsigned int pFlags) const
+{
+    return (pFlags & aiProcess_ImproveCacheLocality) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Setup configuration
+void ImproveCacheLocalityProcess::SetupProperties(const Importer* pImp)
+{
+    // AI_CONFIG_PP_ICL_PTCACHE_SIZE controls the target cache size for the optimizer
+    configCacheDepth = pImp->GetPropertyInteger(AI_CONFIG_PP_ICL_PTCACHE_SIZE,PP_ICL_PTCACHE_SIZE);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void ImproveCacheLocalityProcess::Execute( aiScene* pScene)
+{
+    if (!pScene->mNumMeshes) {
+        ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess skipped; there are no meshes");
+        return;
+    }
+
+    ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess begin");
+
+    float out = 0.f;
+    unsigned int numf = 0, numm = 0;
+    for( unsigned int a = 0; a < pScene->mNumMeshes; a++){
+        const float res = ProcessMesh( pScene->mMeshes[a],a);
+        if (res) {
+            numf += pScene->mMeshes[a]->mNumFaces;
+            out  += res;
+            ++numm;
+        }
+    }
+    if (!DefaultLogger::isNullLogger()) {
+        if (numf > 0) {
+            ASSIMP_LOG_INFO_F("Cache relevant are ", numm, " meshes (", numf, " faces). Average output ACMR is ", out / numf);
+        }
+        ASSIMP_LOG_DEBUG("ImproveCacheLocalityProcess finished. ");
+    }
+}
+
+// ------------------------------------------------------------------------------------------------
+// Improves the cache coherency of a specific mesh
+float ImproveCacheLocalityProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshNum)
+{
+    // TODO: rewrite this to use std::vector or boost::shared_array
+    ai_assert(NULL != pMesh);
+
+    // Check whether the input data is valid
+    // - there must be vertices and faces
+    // - all faces must be triangulated or we can't operate on them
+    if (!pMesh->HasFaces() || !pMesh->HasPositions())
+        return 0.f;
+
+    if (pMesh->mPrimitiveTypes != aiPrimitiveType_TRIANGLE) {
+        ASSIMP_LOG_ERROR("This algorithm works on triangle meshes only");
+        return 0.f;
+    }
+
+    if(pMesh->mNumVertices <= configCacheDepth) {
+        return 0.f;
+    }
+
+    float fACMR = 3.f;
+    const aiFace* const pcEnd = pMesh->mFaces+pMesh->mNumFaces;
+
+    // Input ACMR is for logging purposes only
+    if (!DefaultLogger::isNullLogger())     {
+
+        unsigned int* piFIFOStack = new unsigned int[configCacheDepth];
+        memset(piFIFOStack,0xff,configCacheDepth*sizeof(unsigned int));
+        unsigned int* piCur = piFIFOStack;
+        const unsigned int* const piCurEnd = piFIFOStack + configCacheDepth;
+
+        // count the number of cache misses
+        unsigned int iCacheMisses = 0;
+        for (const aiFace* pcFace = pMesh->mFaces;pcFace != pcEnd;++pcFace) {
+
+            for (unsigned int qq = 0; qq < 3;++qq) {
+                bool bInCache = false;
+
+                for (unsigned int* pp = piFIFOStack;pp < piCurEnd;++pp) {
+                    if (*pp == pcFace->mIndices[qq])    {
+                        // the vertex is in cache
+                        bInCache = true;
+                        break;
+                    }
+                }
+                if (!bInCache)  {
+                    ++iCacheMisses;
+                    if (piCurEnd == piCur) {
+                        piCur = piFIFOStack;
+                    }
+                    *piCur++ = pcFace->mIndices[qq];
+                }
+            }
+        }
+        delete[] piFIFOStack;
+        fACMR = (float)iCacheMisses / pMesh->mNumFaces;
+        if (3.0 == fACMR)   {
+            char szBuff[128]; // should be sufficiently large in every case
+
+            // the JoinIdenticalVertices process has not been executed on this
+            // mesh, otherwise this value would normally be at least minimally
+            // smaller than 3.0 ...
+            ai_snprintf(szBuff,128,"Mesh %u: Not suitable for vcache optimization",meshNum);
+            ASSIMP_LOG_WARN(szBuff);
+            return 0.f;
+        }
+    }
+
+    // first we need to build a vertex-triangle adjacency list
+    VertexTriangleAdjacency adj(pMesh->mFaces,pMesh->mNumFaces, pMesh->mNumVertices,true);
+
+    // build a list to store per-vertex caching time stamps
+    unsigned int* const piCachingStamps = new unsigned int[pMesh->mNumVertices];
+    memset(piCachingStamps,0x0,pMesh->mNumVertices*sizeof(unsigned int));
+
+    // allocate an empty output index buffer. We store the output indices in one large array.
+    // Since the number of triangles won't change the input faces can be reused. This is how
+    // we save thousands of redundant mini allocations for aiFace::mIndices
+    const unsigned int iIdxCnt = pMesh->mNumFaces*3;
+    unsigned int* const piIBOutput = new unsigned int[iIdxCnt];
+    unsigned int* piCSIter = piIBOutput;
+
+    // allocate the flag array to hold the information
+    // whether a face has already been emitted or not
+    std::vector<bool> abEmitted(pMesh->mNumFaces,false);
+
+    // dead-end vertex index stack
+    std::stack<unsigned int, std::vector<unsigned int> > sDeadEndVStack;
+
+    // create a copy of the piNumTriPtr buffer
+    unsigned int* const piNumTriPtr = adj.mLiveTriangles;
+    const std::vector<unsigned int> piNumTriPtrNoModify(piNumTriPtr, piNumTriPtr + pMesh->mNumVertices);
+
+    // get the largest number of referenced triangles and allocate the "candidate buffer"
+    unsigned int iMaxRefTris = 0; {
+        const unsigned int* piCur = adj.mLiveTriangles;
+        const unsigned int* const piCurEnd = adj.mLiveTriangles+pMesh->mNumVertices;
+        for (;piCur != piCurEnd;++piCur) {
+            iMaxRefTris = std::max(iMaxRefTris,*piCur);
+        }
+    }
+    ai_assert(iMaxRefTris > 0);
+    unsigned int* piCandidates = new unsigned int[iMaxRefTris*3];
+    unsigned int iCacheMisses = 0;
+
+    // ...................................................................................
+    /** PSEUDOCODE for the algorithm
+
+        A = Build-Adjacency(I) Vertex-triangle adjacency
+        L = Get-Triangle-Counts(A) Per-vertex live triangle counts
+        C = Zero(Vertex-Count(I)) Per-vertex caching time stamps
+        D = Empty-Stack() Dead-end vertex stack
+        E = False(Triangle-Count(I)) Per triangle emitted flag
+        O = Empty-Index-Buffer() Empty output buffer
+        f = 0 Arbitrary starting vertex
+        s = k+1, i = 1 Time stamp and cursor
+        while f >= 0 For all valid fanning vertices
+            N = Empty-Set() 1-ring of next candidates
+            for each Triangle t in Neighbors(A, f)
+                if !Emitted(E,t)
+                    for each Vertex v in t
+                        Append(O,v) Output vertex
+                        Push(D,v) Add to dead-end stack
+                        Insert(N,v) Register as candidate
+                        L[v] = L[v]-1 Decrease live triangle count
+                        if s-C[v] > k If not in cache
+                            C[v] = s Set time stamp
+                            s = s+1 Increment time stamp
+                    E[t] = true Flag triangle as emitted
+            Select next fanning vertex
+            f = Get-Next-Vertex(I,i,k,N,C,s,L,D)
+        return O
+        */
+    // ...................................................................................
+
+    int ivdx = 0;
+    int ics = 1;
+    int iStampCnt = configCacheDepth+1;
+    while (ivdx >= 0)   {
+
+        unsigned int icnt = piNumTriPtrNoModify[ivdx];
+        unsigned int* piList = adj.GetAdjacentTriangles(ivdx);
+        unsigned int* piCurCandidate = piCandidates;
+
+        // get all triangles in the neighborhood
+        for (unsigned int tri = 0; tri < icnt;++tri)    {
+
+            // if they have not yet been emitted, add them to the output IB
+            const unsigned int fidx = *piList++;
+            if (!abEmitted[fidx])   {
+
+                // so iterate through all vertices of the current triangle
+                const aiFace* pcFace = &pMesh->mFaces[ fidx ];
+                unsigned nind = pcFace->mNumIndices;
+                for (unsigned ind = 0; ind < nind; ind++) {
+                    unsigned dp = pcFace->mIndices[ind];
+
+                    // the current vertex won't have any free triangles after this step
+                    if (ivdx != (int)dp) {
+                        // append the vertex to the dead-end stack
+                        sDeadEndVStack.push(dp);
+
+                        // register as candidate for the next step
+                        *piCurCandidate++ = dp;
+
+                        // decrease the per-vertex triangle counts
+                        piNumTriPtr[dp]--;
+                    }
+
+                    // append the vertex to the output index buffer
+                    *piCSIter++ = dp;
+
+                    // if the vertex is not yet in cache, set its cache count
+                    if (iStampCnt-piCachingStamps[dp] > configCacheDepth) {
+                        piCachingStamps[dp] = iStampCnt++;
+                        ++iCacheMisses;
+                    }
+                }
+                // flag triangle as emitted
+                abEmitted[fidx] = true;
+            }
+        }
+
+        // the vertex has now no living adjacent triangles anymore
+        piNumTriPtr[ivdx] = 0;
+
+        // get next fanning vertex
+        ivdx = -1;
+        int max_priority = -1;
+        for (unsigned int* piCur = piCandidates;piCur != piCurCandidate;++piCur)    {
+            const unsigned int dp = *piCur;
+
+            // must have live triangles
+            if (piNumTriPtr[dp] > 0)    {
+                int priority = 0;
+
+                // will the vertex be in cache, even after fanning occurs?
+                unsigned int tmp;
+                if ((tmp = iStampCnt-piCachingStamps[dp]) + 2*piNumTriPtr[dp] <= configCacheDepth) {
+                    priority = tmp;
+                }
+
+                // keep best candidate
+                if (priority > max_priority) {
+                    max_priority = priority;
+                    ivdx = dp;
+                }
+            }
+        }
+        // did we reach a dead end?
+        if (-1 == ivdx) {
+            // need to get a non-local vertex for which we have a good chance that it is still
+            // in the cache ...
+            while (!sDeadEndVStack.empty()) {
+                unsigned int iCachedIdx = sDeadEndVStack.top();
+                sDeadEndVStack.pop();
+                if (piNumTriPtr[ iCachedIdx ] > 0)  {
+                    ivdx = iCachedIdx;
+                    break;
+                }
+            }
+
+            if (-1 == ivdx) {
+                // well, there isn't such a vertex. Simply get the next vertex in input order and
+                // hope it is not too bad ...
+                while (ics < (int)pMesh->mNumVertices)  {
+                    ++ics;
+                    if (piNumTriPtr[ics] > 0)   {
+                        ivdx = ics;
+                        break;
+                    }
+                }
+            }
+        }
+    }
+    float fACMR2 = 0.0f;
+    if (!DefaultLogger::isNullLogger()) {
+        fACMR2 = (float)iCacheMisses / pMesh->mNumFaces;
+
+        // very intense verbose logging ... prepare for much text if there are many meshes
+        if ( DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE) {
+            ASSIMP_LOG_DEBUG_F("Mesh %u | ACMR in: ", meshNum, " out: ", fACMR, " | ~", fACMR2, ((fACMR - fACMR2) / fACMR) * 100.f);
+        }
+
+        fACMR2 *= pMesh->mNumFaces;
+    }
+    // sort the output index buffer back to the input array
+    piCSIter = piIBOutput;
+    for (aiFace* pcFace = pMesh->mFaces; pcFace != pcEnd;++pcFace)  {
+        unsigned nind = pcFace->mNumIndices;
+        unsigned * ind = pcFace->mIndices;
+        if (nind > 0) ind[0] = *piCSIter++;
+        if (nind > 1) ind[1] = *piCSIter++;
+        if (nind > 2) ind[2] = *piCSIter++;
+    }
+
+    // delete temporary storage
+    delete[] piCachingStamps;
+    delete[] piIBOutput;
+    delete[] piCandidates;
+
+    return fACMR2;
+}

+ 100 - 0
thirdparty/assimp/code/ImproveCacheLocality.h

@@ -0,0 +1,100 @@
+/*
+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 Defines a post processing step to reorder faces for
+ better cache locality*/
+#ifndef AI_IMPROVECACHELOCALITY_H_INC
+#define AI_IMPROVECACHELOCALITY_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/types.h>
+
+struct aiMesh;
+
+namespace Assimp
+{
+
+// ---------------------------------------------------------------------------
+/** The ImproveCacheLocalityProcess reorders all faces for improved vertex
+ *  cache locality. It tries to arrange all faces to fans and to render
+ *  faces which share vertices directly one after the other.
+ *
+ *  @note This step expects triagulated input data.
+ */
+class ImproveCacheLocalityProcess : public BaseProcess
+{
+public:
+
+    ImproveCacheLocalityProcess();
+    ~ImproveCacheLocalityProcess();
+
+public:
+
+    // -------------------------------------------------------------------
+    // Check whether the pp step is active
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    // Executes the pp step on a given scene
+    void Execute( aiScene* pScene);
+
+    // -------------------------------------------------------------------
+    // Configures the pp step
+    void SetupProperties(const Importer* pImp);
+
+protected:
+    // -------------------------------------------------------------------
+    /** Executes the postprocessing step on the given mesh
+     * @param pMesh The mesh to process.
+     * @param meshNum Index of the mesh to process
+     */
+    float ProcessMesh( aiMesh* pMesh, unsigned int meshNum);
+
+private:
+    //! Configuration parameter: specifies the size of the cache to
+    //! optimize the vertex data for.
+    unsigned int configCacheDepth;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_IMPROVECACHELOCALITY_H_INC

+ 463 - 0
thirdparty/assimp/code/JoinVerticesProcess.cpp

@@ -0,0 +1,463 @@
+/*
+---------------------------------------------------------------------------
+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 Implementation of the post processing step to join identical vertices
+ * for all imported meshes
+ */
+
+
+#ifndef ASSIMP_BUILD_NO_JOINVERTICES_PROCESS
+
+#include "JoinVerticesProcess.h"
+#include "ProcessHelper.h"
+#include <assimp/Vertex.h>
+#include <assimp/TinyFormatter.h>
+#include <stdio.h>
+#include <unordered_set>
+
+using namespace Assimp;
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+JoinVerticesProcess::JoinVerticesProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+JoinVerticesProcess::~JoinVerticesProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool JoinVerticesProcess::IsActive( unsigned int pFlags) const
+{
+    return (pFlags & aiProcess_JoinIdenticalVertices) != 0;
+}
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void JoinVerticesProcess::Execute( aiScene* pScene)
+{
+    ASSIMP_LOG_DEBUG("JoinVerticesProcess begin");
+
+    // get the total number of vertices BEFORE the step is executed
+    int iNumOldVertices = 0;
+    if (!DefaultLogger::isNullLogger()) {
+        for( unsigned int a = 0; a < pScene->mNumMeshes; a++)   {
+            iNumOldVertices +=  pScene->mMeshes[a]->mNumVertices;
+        }
+    }
+
+    // execute the step
+    int iNumVertices = 0;
+    for( unsigned int a = 0; a < pScene->mNumMeshes; a++)
+        iNumVertices += ProcessMesh( pScene->mMeshes[a],a);
+
+    // if logging is active, print detailed statistics
+    if (!DefaultLogger::isNullLogger()) {
+        if (iNumOldVertices == iNumVertices) {
+            ASSIMP_LOG_DEBUG("JoinVerticesProcess finished ");
+        } else {
+            ASSIMP_LOG_INFO_F("JoinVerticesProcess finished | Verts in: ", iNumOldVertices,
+                " out: ", iNumVertices, " | ~",
+                ((iNumOldVertices - iNumVertices) / (float)iNumOldVertices) * 100.f );
+        }
+    }
+
+    pScene->mFlags |= AI_SCENE_FLAGS_NON_VERBOSE_FORMAT;
+}
+
+namespace {
+
+bool areVerticesEqual(const Vertex &lhs, const Vertex &rhs, bool complex)
+{
+    // A little helper to find locally close vertices faster.
+    // Try to reuse the lookup table from the last step.
+    const static float epsilon = 1e-5f;
+    // Squared because we check against squared length of the vector difference
+    static const float squareEpsilon = epsilon * epsilon;
+
+    // Square compare is useful for animeshes vertices compare
+    if ((lhs.position - rhs.position).SquareLength() > squareEpsilon) {
+        return false;
+    }
+
+    // We just test the other attributes even if they're not present in the mesh.
+    // In this case they're initialized to 0 so the comparison succeeds.
+    // By this method the non-present attributes are effectively ignored in the comparison.
+    if ((lhs.normal - rhs.normal).SquareLength() > squareEpsilon) {
+        return false;
+    }
+
+    if ((lhs.texcoords[0] - rhs.texcoords[0]).SquareLength() > squareEpsilon) {
+        return false;
+    }
+
+    if ((lhs.tangent - rhs.tangent).SquareLength() > squareEpsilon) {
+        return false;
+    }
+
+    if ((lhs.bitangent - rhs.bitangent).SquareLength() > squareEpsilon) {
+        return false;
+    }
+
+    // Usually we won't have vertex colors or multiple UVs, so we can skip from here
+    // Actually this increases runtime performance slightly, at least if branch
+    // prediction is on our side.
+    if (complex) {
+        for (int i = 0; i < 8; i++) {
+            if (i > 0 && (lhs.texcoords[i] - rhs.texcoords[i]).SquareLength() > squareEpsilon) {
+                return false;
+            }
+            if (GetColorDifference(lhs.colors[i], rhs.colors[i]) > squareEpsilon) {
+                return false;
+            }
+        }
+    }
+    return true;
+}
+
+template<class XMesh>
+void updateXMeshVertices(XMesh *pMesh, std::vector<Vertex> &uniqueVertices) {
+    // replace vertex data with the unique data sets
+    pMesh->mNumVertices = (unsigned int)uniqueVertices.size();
+
+    // ----------------------------------------------------------------------------
+    // NOTE - we're *not* calling Vertex::SortBack() because it would check for
+    // presence of every single vertex component once PER VERTEX. And our CPU
+    // dislikes branches, even if they're easily predictable.
+    // ----------------------------------------------------------------------------
+
+    // Position, if present (check made for aiAnimMesh)
+    if (pMesh->mVertices)
+    {
+        delete [] pMesh->mVertices;
+        pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
+        for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
+            pMesh->mVertices[a] = uniqueVertices[a].position;
+        }
+    }
+
+    // Normals, if present
+    if (pMesh->mNormals)
+    {
+        delete [] pMesh->mNormals;
+        pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
+        for( unsigned int a = 0; a < pMesh->mNumVertices; a++) {
+            pMesh->mNormals[a] = uniqueVertices[a].normal;
+        }
+    }
+    // Tangents, if present
+    if (pMesh->mTangents)
+    {
+        delete [] pMesh->mTangents;
+        pMesh->mTangents = new aiVector3D[pMesh->mNumVertices];
+        for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
+            pMesh->mTangents[a] = uniqueVertices[a].tangent;
+        }
+    }
+    // Bitangents as well
+    if (pMesh->mBitangents)
+    {
+        delete [] pMesh->mBitangents;
+        pMesh->mBitangents = new aiVector3D[pMesh->mNumVertices];
+        for (unsigned int a = 0; a < pMesh->mNumVertices; a++) {
+            pMesh->mBitangents[a] = uniqueVertices[a].bitangent;
+        }
+    }
+    // Vertex colors
+    for (unsigned int a = 0; pMesh->HasVertexColors(a); a++)
+    {
+        delete [] pMesh->mColors[a];
+        pMesh->mColors[a] = new aiColor4D[pMesh->mNumVertices];
+        for( unsigned int b = 0; b < pMesh->mNumVertices; b++) {
+            pMesh->mColors[a][b] = uniqueVertices[b].colors[a];
+        }
+    }
+    // Texture coords
+    for (unsigned int a = 0; pMesh->HasTextureCoords(a); a++)
+    {
+        delete [] pMesh->mTextureCoords[a];
+        pMesh->mTextureCoords[a] = new aiVector3D[pMesh->mNumVertices];
+        for (unsigned int b = 0; b < pMesh->mNumVertices; b++) {
+            pMesh->mTextureCoords[a][b] = uniqueVertices[b].texcoords[a];
+        }
+    }
+}
+} // namespace
+
+// ------------------------------------------------------------------------------------------------
+// Unites identical vertices in the given mesh
+int JoinVerticesProcess::ProcessMesh( aiMesh* pMesh, unsigned int meshIndex)
+{
+    static_assert( AI_MAX_NUMBER_OF_COLOR_SETS    == 8, "AI_MAX_NUMBER_OF_COLOR_SETS    == 8");
+	static_assert( AI_MAX_NUMBER_OF_TEXTURECOORDS == 8, "AI_MAX_NUMBER_OF_TEXTURECOORDS == 8");
+
+    // Return early if we don't have any positions
+    if (!pMesh->HasPositions() || !pMesh->HasFaces()) {
+        return 0;
+    }
+
+    // We should care only about used vertices, not all of them
+    // (this can happen due to original file vertices buffer being used by
+    // multiple meshes)
+    std::unordered_set<unsigned int> usedVertexIndices;
+    usedVertexIndices.reserve(pMesh->mNumVertices);
+    for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
+    {
+        aiFace& face = pMesh->mFaces[a];
+        for( unsigned int b = 0; b < face.mNumIndices; b++) {
+            usedVertexIndices.insert(face.mIndices[b]);
+        }
+    }
+
+    // We'll never have more vertices afterwards.
+    std::vector<Vertex> uniqueVertices;
+    uniqueVertices.reserve( pMesh->mNumVertices);
+
+    // For each vertex the index of the vertex it was replaced by.
+    // Since the maximal number of vertices is 2^31-1, the most significand bit can be used to mark
+    //  whether a new vertex was created for the index (true) or if it was replaced by an existing
+    //  unique vertex (false). This saves an additional std::vector<bool> and greatly enhances
+    //  branching performance.
+    static_assert(AI_MAX_VERTICES == 0x7fffffff, "AI_MAX_VERTICES == 0x7fffffff");
+    std::vector<unsigned int> replaceIndex( pMesh->mNumVertices, 0xffffffff);
+
+    // float posEpsilonSqr;
+    SpatialSort* vertexFinder = NULL;
+    SpatialSort _vertexFinder;
+
+    typedef std::pair<SpatialSort,float> SpatPair;
+    if (shared) {
+        std::vector<SpatPair >* avf;
+        shared->GetProperty(AI_SPP_SPATIAL_SORT,avf);
+        if (avf)    {
+            SpatPair& blubb = (*avf)[meshIndex];
+            vertexFinder  = &blubb.first;
+            // posEpsilonSqr = blubb.second;
+        }
+    }
+    if (!vertexFinder)  {
+        // bad, need to compute it.
+        _vertexFinder.Fill(pMesh->mVertices, pMesh->mNumVertices, sizeof( aiVector3D));
+        vertexFinder = &_vertexFinder;
+        // posEpsilonSqr = ComputePositionEpsilon(pMesh);
+    }
+
+    // Again, better waste some bytes than a realloc ...
+    std::vector<unsigned int> verticesFound;
+    verticesFound.reserve(10);
+
+    // Run an optimized code path if we don't have multiple UVs or vertex colors.
+    // This should yield false in more than 99% of all imports ...
+    const bool complex = ( pMesh->GetNumColorChannels() > 0 || pMesh->GetNumUVChannels() > 1);
+    const bool hasAnimMeshes = pMesh->mNumAnimMeshes > 0;
+
+    // We'll never have more vertices afterwards.
+    std::vector<std::vector<Vertex>> uniqueAnimatedVertices;
+    if (hasAnimMeshes) {
+        uniqueAnimatedVertices.resize(pMesh->mNumAnimMeshes);
+        for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
+            uniqueAnimatedVertices[animMeshIndex].reserve(pMesh->mNumVertices);
+        }
+    }
+
+    // Now check each vertex if it brings something new to the table
+    for( unsigned int a = 0; a < pMesh->mNumVertices; a++)  {
+        if (usedVertexIndices.find(a) == usedVertexIndices.end()) {
+            continue;
+        }
+
+        // collect the vertex data
+        Vertex v(pMesh,a);
+
+        // collect all vertices that are close enough to the given position
+        vertexFinder->FindIdenticalPositions( v.position, verticesFound);
+        unsigned int matchIndex = 0xffffffff;
+
+        // check all unique vertices close to the position if this vertex is already present among them
+        for( unsigned int b = 0; b < verticesFound.size(); b++) {
+            const unsigned int vidx = verticesFound[b];
+            const unsigned int uidx = replaceIndex[ vidx];
+            if( uidx & 0x80000000)
+                continue;
+
+            const Vertex& uv = uniqueVertices[ uidx];
+
+            if (!areVerticesEqual(v, uv, complex)) {
+                continue;
+            }
+
+            if (hasAnimMeshes) {
+                // If given vertex is animated, then it has to be preserver 1 to 1 (base mesh and animated mesh require same topology)
+                // NOTE: not doing this totaly breaks anim meshes as they don't have their own faces (they use pMesh->mFaces)
+                bool breaksAnimMesh = false;
+                for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
+                    const Vertex& animatedUV = uniqueAnimatedVertices[animMeshIndex][ uidx];
+                    Vertex aniMeshVertex(pMesh->mAnimMeshes[animMeshIndex], a);
+                    if (!areVerticesEqual(aniMeshVertex, animatedUV, complex)) {
+                        breaksAnimMesh = true;
+                        break;
+                    }
+                }
+                if (breaksAnimMesh) {
+                    continue;
+                }
+            }
+
+            // we're still here -> this vertex perfectly matches our given vertex
+            matchIndex = uidx;
+            break;
+        }
+
+        // found a replacement vertex among the uniques?
+        if( matchIndex != 0xffffffff)
+        {
+            // store where to found the matching unique vertex
+            replaceIndex[a] = matchIndex | 0x80000000;
+        }
+        else
+        {
+            // no unique vertex matches it up to now -> so add it
+            replaceIndex[a] = (unsigned int)uniqueVertices.size();
+            uniqueVertices.push_back( v);
+            if (hasAnimMeshes) {
+                for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
+                    Vertex aniMeshVertex(pMesh->mAnimMeshes[animMeshIndex], a);
+                    uniqueAnimatedVertices[animMeshIndex].push_back(aniMeshVertex);
+                }
+            }
+        }
+    }
+
+    if (!DefaultLogger::isNullLogger() && DefaultLogger::get()->getLogSeverity() == Logger::VERBOSE)    {
+        ASSIMP_LOG_DEBUG_F(
+            "Mesh ",meshIndex,
+            " (",
+            (pMesh->mName.length ? pMesh->mName.data : "unnamed"),
+            ") | Verts in: ",pMesh->mNumVertices,
+            " out: ",
+            uniqueVertices.size(),
+            " | ~",
+            ((pMesh->mNumVertices - uniqueVertices.size()) / (float)pMesh->mNumVertices) * 100.f,
+            "%"
+        );
+    }
+
+    updateXMeshVertices(pMesh, uniqueVertices);
+    if (hasAnimMeshes) {
+        for (unsigned int animMeshIndex = 0; animMeshIndex < pMesh->mNumAnimMeshes; animMeshIndex++) {
+            updateXMeshVertices(pMesh->mAnimMeshes[animMeshIndex], uniqueAnimatedVertices[animMeshIndex]);
+        }
+    }
+
+    // adjust the indices in all faces
+    for( unsigned int a = 0; a < pMesh->mNumFaces; a++)
+    {
+        aiFace& face = pMesh->mFaces[a];
+        for( unsigned int b = 0; b < face.mNumIndices; b++) {
+            face.mIndices[b] = replaceIndex[face.mIndices[b]] & ~0x80000000;
+        }
+    }
+
+    // adjust bone vertex weights.
+    for( int a = 0; a < (int)pMesh->mNumBones; a++) {
+        aiBone* bone = pMesh->mBones[a];
+        std::vector<aiVertexWeight> newWeights;
+        newWeights.reserve( bone->mNumWeights);
+
+        if ( NULL != bone->mWeights ) {
+            for ( unsigned int b = 0; b < bone->mNumWeights; b++ ) {
+                const aiVertexWeight& ow = bone->mWeights[ b ];
+                // if the vertex is a unique one, translate it
+                if ( !( replaceIndex[ ow.mVertexId ] & 0x80000000 ) ) {
+                    aiVertexWeight nw;
+                    nw.mVertexId = replaceIndex[ ow.mVertexId ];
+                    nw.mWeight = ow.mWeight;
+                    newWeights.push_back( nw );
+                }
+            }
+        } else {
+            ASSIMP_LOG_ERROR( "X-Export: aiBone shall contain weights, but pointer to them is NULL." );
+        }
+
+        if (newWeights.size() > 0) {
+            // kill the old and replace them with the translated weights
+            delete [] bone->mWeights;
+            bone->mNumWeights = (unsigned int)newWeights.size();
+
+            bone->mWeights = new aiVertexWeight[bone->mNumWeights];
+            memcpy( bone->mWeights, &newWeights[0], bone->mNumWeights * sizeof( aiVertexWeight));
+        }
+        else {
+
+            /*  NOTE:
+             *
+             *  In the algorithm above we're assuming that there are no vertices
+             *  with a different bone weight setup at the same position. That wouldn't
+             *  make sense, but it is not absolutely impossible. SkeletonMeshBuilder
+             *  for example generates such input data if two skeleton points
+             *  share the same position. Again this doesn't make sense but is
+             *  reality for some model formats (MD5 for example uses these special
+             *  nodes as attachment tags for its weapons).
+             *
+             *  Then it is possible that a bone has no weights anymore .... as a quick
+             *  workaround, we're just removing these bones. If they're animated,
+             *  model geometry might be modified but at least there's no risk of a crash.
+             */
+            delete bone;
+            --pMesh->mNumBones;
+            for (unsigned int n = a; n < pMesh->mNumBones; ++n)  {
+                pMesh->mBones[n] = pMesh->mBones[n+1];
+            }
+
+            --a;
+            ASSIMP_LOG_WARN("Removing bone -> no weights remaining");
+        }
+    }
+    return pMesh->mNumVertices;
+}
+
+#endif // !! ASSIMP_BUILD_NO_JOINVERTICES_PROCESS

+ 99 - 0
thirdparty/assimp/code/JoinVerticesProcess.h

@@ -0,0 +1,99 @@
+/*
+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 Defines a post processing step to join identical vertices
+    on all imported meshes.*/
+#ifndef AI_JOINVERTICESPROCESS_H_INC
+#define AI_JOINVERTICESPROCESS_H_INC
+
+#include "BaseProcess.h"
+#include <assimp/types.h>
+
+struct aiMesh;
+
+namespace Assimp
+{
+
+// ---------------------------------------------------------------------------
+/** The JoinVerticesProcess unites identical vertices in all imported meshes.
+ * By default the importer returns meshes where each face addressed its own
+ * set of vertices even if that means that identical vertices are stored multiple
+ * times. The JoinVerticesProcess finds these identical vertices and
+ * erases all but one of the copies. This usually reduces the number of vertices
+ * in a mesh by a serious amount and is the standard form to render a mesh.
+ */
+class ASSIMP_API JoinVerticesProcess : public BaseProcess
+{
+public:
+    JoinVerticesProcess();
+    ~JoinVerticesProcess();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag field.
+     * @param pFlags The processing flags the importer was called with. A bitwise
+     *   combination of #aiPostProcessSteps.
+     * @return true if the process is present in this flag fields, false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+public:
+    // -------------------------------------------------------------------
+    /** Unites identical vertices in the given mesh.
+     * @param pMesh The mesh to process.
+     * @param meshIndex Index of the mesh to process
+     */
+    int ProcessMesh( aiMesh* pMesh, unsigned int meshIndex);
+
+private:
+};
+
+} // end of namespace Assimp
+
+#endif // AI_CALCTANGENTSPROCESS_H_INC

+ 201 - 0
thirdparty/assimp/code/LimitBoneWeightsProcess.cpp

@@ -0,0 +1,201 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+/** Implementation of the LimitBoneWeightsProcess post processing step */
+
+
+#include "LimitBoneWeightsProcess.h"
+#include <assimp/StringUtils.h>
+#include <assimp/postprocess.h>
+#include <assimp/DefaultLogger.hpp>
+#include <assimp/scene.h>
+#include <stdio.h>
+
+using namespace Assimp;
+
+
+// ------------------------------------------------------------------------------------------------
+// Constructor to be privately used by Importer
+LimitBoneWeightsProcess::LimitBoneWeightsProcess()
+{
+    mMaxWeights = AI_LMW_MAX_WEIGHTS;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Destructor, private as well
+LimitBoneWeightsProcess::~LimitBoneWeightsProcess()
+{
+    // nothing to do here
+}
+
+// ------------------------------------------------------------------------------------------------
+// Returns whether the processing step is present in the given flag field.
+bool LimitBoneWeightsProcess::IsActive( unsigned int pFlags) const
+{
+    return (pFlags & aiProcess_LimitBoneWeights) != 0;
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void LimitBoneWeightsProcess::Execute( aiScene* pScene) {
+    ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess begin");
+    for (unsigned int a = 0; a < pScene->mNumMeshes; ++a ) {
+        ProcessMesh(pScene->mMeshes[a]);
+    }
+
+    ASSIMP_LOG_DEBUG("LimitBoneWeightsProcess end");
+}
+
+// ------------------------------------------------------------------------------------------------
+// Executes the post processing step on the given imported data.
+void LimitBoneWeightsProcess::SetupProperties(const Importer* pImp)
+{
+    // get the current value of the property
+    this->mMaxWeights = pImp->GetPropertyInteger(AI_CONFIG_PP_LBW_MAX_WEIGHTS,AI_LMW_MAX_WEIGHTS);
+}
+
+// ------------------------------------------------------------------------------------------------
+// Unites identical vertices in the given mesh
+void LimitBoneWeightsProcess::ProcessMesh( aiMesh* pMesh)
+{
+    if( !pMesh->HasBones())
+        return;
+
+    // collect all bone weights per vertex
+    typedef std::vector< std::vector< Weight > > WeightsPerVertex;
+    WeightsPerVertex vertexWeights( pMesh->mNumVertices);
+
+    // collect all weights per vertex
+    for( unsigned int a = 0; a < pMesh->mNumBones; a++)
+    {
+        const aiBone* bone = pMesh->mBones[a];
+        for( unsigned int b = 0; b < bone->mNumWeights; b++)
+        {
+            const aiVertexWeight& w = bone->mWeights[b];
+            vertexWeights[w.mVertexId].push_back( Weight( a, w.mWeight));
+        }
+    }
+
+    unsigned int removed = 0, old_bones = pMesh->mNumBones;
+
+    // now cut the weight count if it exceeds the maximum
+    bool bChanged = false;
+    for( WeightsPerVertex::iterator vit = vertexWeights.begin(); vit != vertexWeights.end(); ++vit)
+    {
+        if( vit->size() <= mMaxWeights)
+            continue;
+
+        bChanged = true;
+
+        // more than the defined maximum -> first sort by weight in descending order. That's
+        // why we defined the < operator in such a weird way.
+        std::sort( vit->begin(), vit->end());
+
+        // now kill everything beyond the maximum count
+        unsigned int m = static_cast<unsigned int>(vit->size());
+        vit->erase( vit->begin() + mMaxWeights, vit->end());
+        removed += static_cast<unsigned int>(m-vit->size());
+
+        // and renormalize the weights
+        float sum = 0.0f;
+        for( std::vector<Weight>::const_iterator it = vit->begin(); it != vit->end(); ++it ) {
+            sum += it->mWeight;
+        }
+        if( 0.0f != sum ) {
+            const float invSum = 1.0f / sum;
+            for( std::vector<Weight>::iterator it = vit->begin(); it != vit->end(); ++it ) {
+                it->mWeight *= invSum;
+            }
+        }
+    }
+
+    if (bChanged)   {
+        // rebuild the vertex weight array for all bones
+        typedef std::vector< std::vector< aiVertexWeight > > WeightsPerBone;
+        WeightsPerBone boneWeights( pMesh->mNumBones);
+        for( unsigned int a = 0; a < vertexWeights.size(); a++)
+        {
+            const std::vector<Weight>& vw = vertexWeights[a];
+            for( std::vector<Weight>::const_iterator it = vw.begin(); it != vw.end(); ++it)
+                boneWeights[it->mBone].push_back( aiVertexWeight( a, it->mWeight));
+        }
+
+        // and finally copy the vertex weight list over to the mesh's bones
+        std::vector<bool> abNoNeed(pMesh->mNumBones,false);
+        bChanged = false;
+
+        for( unsigned int a = 0; a < pMesh->mNumBones; a++)
+        {
+            const std::vector<aiVertexWeight>& bw = boneWeights[a];
+            aiBone* bone = pMesh->mBones[a];
+
+            if ( bw.empty() )
+            {
+                abNoNeed[a] = bChanged = true;
+                continue;
+            }
+
+            // copy the weight list. should always be less weights than before, so we don't need a new allocation
+            ai_assert( bw.size() <= bone->mNumWeights);
+            bone->mNumWeights = static_cast<unsigned int>( bw.size() );
+            ::memcpy( bone->mWeights, &bw[0], bw.size() * sizeof( aiVertexWeight));
+        }
+
+        if (bChanged)   {
+            // the number of new bones is smaller than before, so we can reuse the old array
+            aiBone** ppcCur = pMesh->mBones;aiBone** ppcSrc = ppcCur;
+
+            for (std::vector<bool>::const_iterator iter  = abNoNeed.begin();iter != abNoNeed.end()  ;++iter)    {
+                if (*iter)  {
+                    delete *ppcSrc;
+                    --pMesh->mNumBones;
+                }
+                else *ppcCur++ = *ppcSrc;
+                ++ppcSrc;
+            }
+        }
+
+        if (!DefaultLogger::isNullLogger()) {
+            ASSIMP_LOG_INFO_F("Removed ", removed, " weights. Input bones: ", old_bones, ". Output bones: ", pMesh->mNumBones );
+        }
+    }
+}

+ 148 - 0
thirdparty/assimp/code/LimitBoneWeightsProcess.h

@@ -0,0 +1,148 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+
+/** Defines a post processing step to limit the number of bones affecting a single vertex. */
+#ifndef AI_LIMITBONEWEIGHTSPROCESS_H_INC
+#define AI_LIMITBONEWEIGHTSPROCESS_H_INC
+
+#include "BaseProcess.h"
+
+struct aiMesh;
+
+class LimitBoneWeightsTest;
+
+namespace Assimp
+{
+
+// NOTE: If you change these limits, don't forget to change the
+// corresponding values in all Assimp ports
+
+// **********************************************************
+// Java: ConfigProperty.java,
+//  ConfigProperty.DEFAULT_BONE_WEIGHT_LIMIT
+// **********************************************************
+
+#if (!defined AI_LMW_MAX_WEIGHTS)
+#   define AI_LMW_MAX_WEIGHTS   0x4
+#endif // !! AI_LMW_MAX_WEIGHTS
+
+// ---------------------------------------------------------------------------
+/** This post processing step limits the number of bones affecting a vertex
+* to a certain maximum value. If a vertex is affected by more than that number
+* of bones, the bone weight with the least influence on this vertex are removed.
+* The other weights on this bone are then renormalized to assure the sum weight
+* to be 1.
+*/
+class ASSIMP_API LimitBoneWeightsProcess : public BaseProcess
+{
+public:
+
+    LimitBoneWeightsProcess();
+    ~LimitBoneWeightsProcess();
+
+public:
+    // -------------------------------------------------------------------
+    /** Returns whether the processing step is present in the given flag.
+    * @param pFlags The processing flags the importer was called with.
+    *   A bitwise combination of #aiPostProcessSteps.
+    * @return true if the process is present in this flag fields,
+    *   false if not.
+    */
+    bool IsActive( unsigned int pFlags) const;
+
+    // -------------------------------------------------------------------
+    /** Called prior to ExecuteOnScene().
+    * The function is a request to the process to update its configuration
+    * basing on the Importer's configuration property list.
+    */
+    void SetupProperties(const Importer* pImp);
+
+public:
+
+    // -------------------------------------------------------------------
+    /** Limits the bone weight count for all vertices in the given mesh.
+    * @param pMesh The mesh to process.
+    */
+    void ProcessMesh( aiMesh* pMesh);
+
+    // -------------------------------------------------------------------
+    /** Executes the post processing step on the given imported data.
+    * At the moment a process is not supposed to fail.
+    * @param pScene The imported data to work at.
+    */
+    void Execute( aiScene* pScene);
+
+
+public:
+
+    // -------------------------------------------------------------------
+    /** Describes a bone weight on a vertex */
+    struct Weight
+    {
+        unsigned int mBone; ///< Index of the bone
+        float mWeight;      ///< Weight of that bone on this vertex
+        Weight() AI_NO_EXCEPT
+        : mBone(0)
+        , mWeight(0.0f)
+        { }
+
+        Weight( unsigned int pBone, float pWeight)
+        {
+            mBone = pBone;
+            mWeight = pWeight;
+        }
+
+        /** Comparison operator to sort bone weights by descending weight */
+        bool operator < (const Weight& pWeight) const
+        {
+            return mWeight > pWeight.mWeight;
+        }
+    };
+
+public:
+    /** Maximum number of bones influencing any single vertex. */
+    unsigned int mMaxWeights;
+};
+
+} // end of namespace Assimp
+
+#endif // AI_LIMITBONEWEIGHTSPROCESS_H_INC

+ 83 - 0
thirdparty/assimp/code/MMDCpp14.h

@@ -0,0 +1,83 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+#pragma once
+
+#ifndef MMD_CPP14_H
+#define MMD_CPP14_H
+
+#include <cstddef>
+#include <memory>
+#include <type_traits>
+#include <utility>
+
+namespace mmd {
+    template<class T> struct _Unique_if {
+        typedef std::unique_ptr<T> _Single_object;
+    };
+
+    template<class T> struct _Unique_if<T[]> {
+        typedef std::unique_ptr<T[]> _Unknown_bound;
+    };
+
+    template<class T, size_t N> struct _Unique_if<T[N]> {
+        typedef void _Known_bound;
+    };
+
+    template<class T, class... Args>
+        typename _Unique_if<T>::_Single_object
+        make_unique(Args&&... args) {
+            return std::unique_ptr<T>(new T(std::forward<Args>(args)...));
+        }
+
+    template<class T>
+        typename _Unique_if<T>::_Unknown_bound
+        make_unique(size_t n) {
+            typedef typename std::remove_extent<T>::type U;
+            return std::unique_ptr<T>(new U[n]());
+        }
+
+    template<class T, class... Args>
+        typename _Unique_if<T>::_Known_bound
+        make_unique(Args&&...) = delete;
+}
+
+#endif

+ 370 - 0
thirdparty/assimp/code/MMDImporter.cpp

@@ -0,0 +1,370 @@
+/*
+---------------------------------------------------------------------------
+Open Asset Import Library (assimp)
+---------------------------------------------------------------------------
+
+Copyright (c) 2006-2016, 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 ASSIMP_BUILD_NO_MMD_IMPORTER
+
+#include "MMDImporter.h"
+#include "MMDPmdParser.h"
+#include "MMDPmxParser.h"
+#include "MMDVmdParser.h"
+#include "ConvertToLHProcess.h"
+#include <assimp/DefaultIOSystem.h>
+#include <assimp/Importer.hpp>
+#include <assimp/ai_assert.h>
+#include <assimp/scene.h>
+#include <fstream>
+#include <iomanip>
+#include <memory>
+
+static const aiImporterDesc desc = {"MMD Importer",
+                                    "",
+                                    "",
+                                    "surfaces supported?",
+                                    aiImporterFlags_SupportTextFlavour,
+                                    0,
+                                    0,
+                                    0,
+                                    0,
+                                    "pmx"};
+
+namespace Assimp {
+
+using namespace std;
+
+// ------------------------------------------------------------------------------------------------
+//  Default constructor
+MMDImporter::MMDImporter()
+: m_Buffer()
+, m_strAbsPath("") {
+    DefaultIOSystem io;
+    m_strAbsPath = io.getOsSeparator();
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Destructor.
+MMDImporter::~MMDImporter() {
+    // empty
+}
+
+// ------------------------------------------------------------------------------------------------
+//  Returns true, if file is an pmx file.
+bool MMDImporter::CanRead(const std::string &pFile, IOSystem *pIOHandler,
+                          bool checkSig) const {
+  if (!checkSig) // Check File Extension
+  {
+    return SimpleExtensionCheck(pFile, "pmx");
+  } else // Check file Header
+  {
+    static const char *pTokens[] = {"PMX "};
+    return BaseImporter::SearchFileHeaderForToken(pIOHandler, pFile, pTokens, 1);
+  }
+}
+
+// ------------------------------------------------------------------------------------------------
+const aiImporterDesc *MMDImporter::GetInfo() const { return &desc; }
+
+// ------------------------------------------------------------------------------------------------
+//  MMD import implementation
+void MMDImporter::InternReadFile(const std::string &file, aiScene *pScene,
+                                 IOSystem * /*pIOHandler*/) {
+  // Read file by istream
+  std::filebuf fb;
+  if (!fb.open(file, std::ios::in | std::ios::binary)) {
+    throw DeadlyImportError("Failed to open file " + file + ".");
+  }
+
+  std::istream fileStream(&fb);
+
+  // Get the file-size and validate it, throwing an exception when fails
+  fileStream.seekg(0, fileStream.end);
+  size_t fileSize = static_cast<size_t>(fileStream.tellg());
+  fileStream.seekg(0, fileStream.beg);
+
+  if (fileSize < sizeof(pmx::PmxModel)) {
+    throw DeadlyImportError(file + " is too small.");
+  }
+
+  pmx::PmxModel model;
+  model.Read(&fileStream);
+
+  CreateDataFromImport(&model, pScene);
+}
+
+// ------------------------------------------------------------------------------------------------
+void MMDImporter::CreateDataFromImport(const pmx::PmxModel *pModel,
+                                       aiScene *pScene) {
+  if (pModel == NULL) {
+    return;
+  }
+
+  aiNode *pNode = new aiNode;
+  if (!pModel->model_name.empty()) {
+    pNode->mName.Set(pModel->model_name);
+  }
+
+  pScene->mRootNode = pNode;
+
+  pNode = new aiNode;
+  pScene->mRootNode->addChildren(1, &pNode);
+  pNode->mName.Set(string(pModel->model_name) + string("_mesh"));
+
+  // split mesh by materials
+  pNode->mNumMeshes = pModel->material_count;
+  pNode->mMeshes = new unsigned int[pNode->mNumMeshes];
+  for (unsigned int index = 0; index < pNode->mNumMeshes; index++) {
+    pNode->mMeshes[index] = index;
+  }
+
+  pScene->mNumMeshes = pModel->material_count;
+  pScene->mMeshes = new aiMesh *[pScene->mNumMeshes];
+  for (unsigned int i = 0, indexStart = 0; i < pScene->mNumMeshes; i++) {
+    const int indexCount = pModel->materials[i].index_count;
+
+    pScene->mMeshes[i] = CreateMesh(pModel, indexStart, indexCount);
+    pScene->mMeshes[i]->mName = pModel->materials[i].material_name;
+    pScene->mMeshes[i]->mMaterialIndex = i;
+    indexStart += indexCount;
+  }
+
+  // create node hierarchy for bone position
+  std::unique_ptr<aiNode *[]> ppNode(new aiNode *[pModel->bone_count]);
+  for (auto i = 0; i < pModel->bone_count; i++) {
+    ppNode[i] = new aiNode(pModel->bones[i].bone_name);
+  }
+
+  for (auto i = 0; i < pModel->bone_count; i++) {
+    const pmx::PmxBone &bone = pModel->bones[i];
+
+    if (bone.parent_index < 0) {
+      pScene->mRootNode->addChildren(1, ppNode.get() + i);
+    } else {
+      ppNode[bone.parent_index]->addChildren(1, ppNode.get() + i);
+
+      aiVector3D v3 = aiVector3D(
+          bone.position[0] - pModel->bones[bone.parent_index].position[0],
+          bone.position[1] - pModel->bones[bone.parent_index].position[1],
+          bone.position[2] - pModel->bones[bone.parent_index].position[2]);
+      aiMatrix4x4::Translation(v3, ppNode[i]->mTransformation);
+    }
+  }
+
+  // create materials
+  pScene->mNumMaterials = pModel->material_count;
+  pScene->mMaterials = new aiMaterial *[pScene->mNumMaterials];
+  for (unsigned int i = 0; i < pScene->mNumMaterials; i++) {
+    pScene->mMaterials[i] = CreateMaterial(&pModel->materials[i], pModel);
+  }
+
+  // Convert everything to OpenGL space
+  MakeLeftHandedProcess convertProcess;
+  convertProcess.Execute(pScene);
+
+  FlipUVsProcess uvFlipper;
+  uvFlipper.Execute(pScene);
+
+  FlipWindingOrderProcess windingFlipper;
+  windingFlipper.Execute(pScene);
+}
+
+// ------------------------------------------------------------------------------------------------
+aiMesh *MMDImporter::CreateMesh(const pmx::PmxModel *pModel,
+                                const int indexStart, const int indexCount) {
+  aiMesh *pMesh = new aiMesh;
+
+  pMesh->mNumVertices = indexCount;
+
+  pMesh->mNumFaces = indexCount / 3;
+  pMesh->mFaces = new aiFace[pMesh->mNumFaces];
+
+  const int numIndices = 3; // triangular face
+  for (unsigned int index = 0; index < pMesh->mNumFaces; index++) {
+    pMesh->mFaces[index].mNumIndices = numIndices;
+    unsigned int *indices = new unsigned int[numIndices];
+    indices[0] = numIndices * index;
+    indices[1] = numIndices * index + 1;
+    indices[2] = numIndices * index + 2;
+    pMesh->mFaces[index].mIndices = indices;
+  }
+
+  pMesh->mVertices = new aiVector3D[pMesh->mNumVertices];
+  pMesh->mNormals = new aiVector3D[pMesh->mNumVertices];
+  pMesh->mTextureCoords[0] = new aiVector3D[pMesh->mNumVertices];
+  pMesh->mNumUVComponents[0] = 2;
+
+  // additional UVs
+  for (int i = 1; i <= pModel->setting.uv; i++) {
+    pMesh->mTextureCoords[i] = new aiVector3D[pMesh->mNumVertices];
+    pMesh->mNumUVComponents[i] = 4;
+  }
+
+  map<int, vector<aiVertexWeight>> bone_vertex_map;
+
+  // fill in contents and create bones
+  for (int index = 0; index < indexCount; index++) {
+    const pmx::PmxVertex *v =
+        &pModel->vertices[pModel->indices[indexStart + index]];
+    const float *position = v->position;
+    pMesh->mVertices[index].Set(position[0], position[1], position[2]);
+    const float *normal = v->normal;
+
+    pMesh->mNormals[index].Set(normal[0], normal[1], normal[2]);
+    pMesh->mTextureCoords[0][index].x = v->uv[0];
+    pMesh->mTextureCoords[0][index].y = v->uv[1];
+
+    for (int i = 1; i <= pModel->setting.uv; i++) {
+      // TODO: wrong here? use quaternion transform?
+      pMesh->mTextureCoords[i][index].x = v->uva[i][0];
+      pMesh->mTextureCoords[i][index].y = v->uva[i][1];
+    }
+
+    // handle bone map
+    const auto vsBDEF1_ptr =
+        dynamic_cast<pmx::PmxVertexSkinningBDEF1 *>(v->skinning.get());
+    const auto vsBDEF2_ptr =
+        dynamic_cast<pmx::PmxVertexSkinningBDEF2 *>(v->skinning.get());
+    const auto vsBDEF4_ptr =
+        dynamic_cast<pmx::PmxVertexSkinningBDEF4 *>(v->skinning.get());
+    const auto vsSDEF_ptr =
+        dynamic_cast<pmx::PmxVertexSkinningSDEF *>(v->skinning.get());
+    switch (v->skinning_type) {
+    case pmx::PmxVertexSkinningType::BDEF1:
+      bone_vertex_map[vsBDEF1_ptr->bone_index].push_back(
+          aiVertexWeight(index, 1.0));
+      break;
+    case pmx::PmxVertexSkinningType::BDEF2:
+      bone_vertex_map[vsBDEF2_ptr->bone_index1].push_back(
+          aiVertexWeight(index, vsBDEF2_ptr->bone_weight));
+      bone_vertex_map[vsBDEF2_ptr->bone_index2].push_back(
+          aiVertexWeight(index, 1.0f - vsBDEF2_ptr->bone_weight));
+      break;
+    case pmx::PmxVertexSkinningType::BDEF4:
+      bone_vertex_map[vsBDEF4_ptr->bone_index1].push_back(
+          aiVertexWeight(index, vsBDEF4_ptr->bone_weight1));
+      bone_vertex_map[vsBDEF4_ptr->bone_index2].push_back(
+          aiVertexWeight(index, vsBDEF4_ptr->bone_weight2));
+      bone_vertex_map[vsBDEF4_ptr->bone_index3].push_back(
+          aiVertexWeight(index, vsBDEF4_ptr->bone_weight3));
+      bone_vertex_map[vsBDEF4_ptr->bone_index4].push_back(
+          aiVertexWeight(index, vsBDEF4_ptr->bone_weight4));
+      break;
+    case pmx::PmxVertexSkinningType::SDEF: // TODO: how to use sdef_c, sdef_r0,
+                                           // sdef_r1?
+      bone_vertex_map[vsSDEF_ptr->bone_index1].push_back(
+          aiVertexWeight(index, vsSDEF_ptr->bone_weight));
+      bone_vertex_map[vsSDEF_ptr->bone_index2].push_back(
+          aiVertexWeight(index, 1.0f - vsSDEF_ptr->bone_weight));
+      break;
+    case pmx::PmxVertexSkinningType::QDEF:
+      const auto vsQDEF_ptr =
+          dynamic_cast<pmx::PmxVertexSkinningQDEF *>(v->skinning.get());
+      bone_vertex_map[vsQDEF_ptr->bone_index1].push_back(
+          aiVertexWeight(index, vsQDEF_ptr->bone_weight1));
+      bone_vertex_map[vsQDEF_ptr->bone_index2].push_back(
+          aiVertexWeight(index, vsQDEF_ptr->bone_weight2));
+      bone_vertex_map[vsQDEF_ptr->bone_index3].push_back(
+          aiVertexWeight(index, vsQDEF_ptr->bone_weight3));
+      bone_vertex_map[vsQDEF_ptr->bone_index4].push_back(
+          aiVertexWeight(index, vsQDEF_ptr->bone_weight4));
+      break;
+    }
+  }
+
+  // make all bones for each mesh
+  // assign bone weights to skinned bones (otherwise just initialize)
+  auto bone_ptr_ptr = new aiBone *[pModel->bone_count];
+  pMesh->mNumBones = pModel->bone_count;
+  pMesh->mBones = bone_ptr_ptr;
+  for (auto ii = 0; ii < pModel->bone_count; ++ii) {
+    auto pBone = new aiBone;
+    const auto &pmxBone = pModel->bones[ii];
+    pBone->mName = pmxBone.bone_name;
+    aiVector3D pos(pmxBone.position[0], pmxBone.position[1], pmxBone.position[2]);
+    aiMatrix4x4::Translation(-pos, pBone->mOffsetMatrix);
+    auto it = bone_vertex_map.find(ii);
+    if (it != bone_vertex_map.end()) {
+      pBone->mNumWeights = static_cast<unsigned int>(it->second.size());
+      pBone->mWeights = new aiVertexWeight[pBone->mNumWeights];
+      for (unsigned int j = 0; j < pBone->mNumWeights; j++) {
+          pBone->mWeights[j] = it->second[j];
+      }
+    }
+    bone_ptr_ptr[ii] = pBone;
+  }
+
+  return pMesh;
+}
+
+// ------------------------------------------------------------------------------------------------
+aiMaterial *MMDImporter::CreateMaterial(const pmx::PmxMaterial *pMat,
+                                        const pmx::PmxModel *pModel) {
+  aiMaterial *mat = new aiMaterial();
+  aiString name(pMat->material_english_name);
+  mat->AddProperty(&name, AI_MATKEY_NAME);
+
+  aiColor3D diffuse(pMat->diffuse[0], pMat->diffuse[1], pMat->diffuse[2]);
+  mat->AddProperty(&diffuse, 1, AI_MATKEY_COLOR_DIFFUSE);
+  aiColor3D specular(pMat->specular[0], pMat->specular[1], pMat->specular[2]);
+  mat->AddProperty(&specular, 1, AI_MATKEY_COLOR_SPECULAR);
+  aiColor3D ambient(pMat->ambient[0], pMat->ambient[1], pMat->ambient[2]);
+  mat->AddProperty(&ambient, 1, AI_MATKEY_COLOR_AMBIENT);
+
+  float opacity = pMat->diffuse[3];
+  mat->AddProperty(&opacity, 1, AI_MATKEY_OPACITY);
+  float shininess = pMat->specularlity;
+  mat->AddProperty(&shininess, 1, AI_MATKEY_SHININESS_STRENGTH);
+
+  if(pMat->diffuse_texture_index >= 0) {
+      aiString texture_path(pModel->textures[pMat->diffuse_texture_index]);
+      mat->AddProperty(&texture_path, AI_MATKEY_TEXTURE(aiTextureType_DIFFUSE, 0));
+  }
+
+  int mapping_uvwsrc = 0;
+  mat->AddProperty(&mapping_uvwsrc, 1,
+                   AI_MATKEY_UVWSRC(aiTextureType_DIFFUSE, 0));
+
+  return mat;
+}
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif // !! ASSIMP_BUILD_NO_MMD_IMPORTER

+ 96 - 0
thirdparty/assimp/code/MMDImporter.h

@@ -0,0 +1,96 @@
+/*
+Open Asset Import Library (assimp)
+----------------------------------------------------------------------
+
+Copyright (c) 2006-2016, 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 MMD_FILE_IMPORTER_H_INC
+#define MMD_FILE_IMPORTER_H_INC
+
+#include <assimp/BaseImporter.h>
+#include "MMDPmxParser.h"
+#include <assimp/material.h>
+#include <vector>
+
+struct aiMesh;
+
+namespace Assimp {
+
+// ------------------------------------------------------------------------------------------------
+/// \class  MMDImporter
+/// \brief  Imports MMD a pmx/pmd/vmd file
+// ------------------------------------------------------------------------------------------------
+class MMDImporter : public BaseImporter {
+public:
+    /// \brief  Default constructor
+    MMDImporter();
+
+    /// \brief  Destructor
+    ~MMDImporter();
+
+public:
+    /// \brief  Returns whether the class can handle the format of the given file.
+    /// \remark See BaseImporter::CanRead() for details.
+    bool CanRead( const std::string& pFile, IOSystem* pIOHandler, bool checkSig) const;
+
+private:
+    //! \brief  Appends the supported extension.
+    const aiImporterDesc* GetInfo () const;
+
+    //! \brief  File import implementation.
+    void InternReadFile(const std::string& pFile, aiScene* pScene, IOSystem* pIOHandler);
+
+    //! \brief  Create the data from imported content.
+    void CreateDataFromImport(const pmx::PmxModel* pModel, aiScene* pScene);
+
+    //! \brief Create the mesh
+    aiMesh* CreateMesh(const pmx::PmxModel* pModel, const int indexStart, const int indexCount);
+
+    //! \brief Create the material
+    aiMaterial* CreateMaterial(const pmx::PmxMaterial* pMat, const pmx::PmxModel* pModel);
+
+private:
+    //! Data buffer
+    std::vector<char> m_Buffer;
+    //! Absolute pathname of model in file system
+    std::string m_strAbsPath;
+};
+
+// ------------------------------------------------------------------------------------------------
+
+} // Namespace Assimp
+
+#endif

+ 597 - 0
thirdparty/assimp/code/MMDPmdParser.h

@@ -0,0 +1,597 @@
+/*
+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.
+
+----------------------------------------------------------------------
+*/
+#pragma once
+
+#include <vector>
+#include <string>
+#include <memory>
+#include <iostream>
+#include <fstream>
+#include "MMDCpp14.h"
+
+namespace pmd
+{
+	class PmdHeader
+	{
+	public:
+		std::string name;
+		std::string name_english;
+		std::string comment;
+		std::string comment_english;
+
+		bool Read(std::ifstream* stream)
+		{
+			char buffer[256];
+			stream->read(buffer, 20);
+			name = std::string(buffer);
+			stream->read(buffer, 256);
+			comment = std::string(buffer);
+			return true;
+		}
+
+		bool ReadExtension(std::ifstream* stream)
+		{
+			char buffer[256];
+			stream->read(buffer, 20);
+			name_english = std::string(buffer);
+			stream->read(buffer, 256);
+			comment_english = std::string(buffer);
+			return true;
+		}
+	};
+
+	class PmdVertex
+	{
+	public:
+		float position[3];
+
+		float normal[3];
+
+		float uv[2];
+
+		uint16_t bone_index[2];
+
+		uint8_t bone_weight;
+
+		bool edge_invisible;
+
+		bool Read(std::ifstream* stream)
+		{
+			stream->read((char*) position, sizeof(float) * 3);
+			stream->read((char*) normal, sizeof(float) * 3);
+			stream->read((char*) uv, sizeof(float) * 2);
+			stream->read((char*) bone_index, sizeof(uint16_t) * 2);
+			stream->read((char*) &bone_weight, sizeof(uint8_t));
+			stream->read((char*) &edge_invisible, sizeof(uint8_t));
+			return true;
+		}
+	};
+
+	class PmdMaterial
+	{
+	public:
+		float diffuse[4];
+		float power;
+		float specular[3];
+		float ambient[3];
+		uint8_t toon_index;
+		uint8_t edge_flag;
+		uint32_t index_count;
+		std::string texture_filename;
+		std::string sphere_filename;
+
+		bool Read(std::ifstream* stream)
+		{
+			char buffer[20];
+			stream->read((char*) &diffuse, sizeof(float) * 4);
+			stream->read((char*) &power, sizeof(float));
+			stream->read((char*) &specular, sizeof(float) * 3);
+			stream->read((char*) &ambient, sizeof(float) * 3);
+			stream->read((char*) &toon_index, sizeof(uint8_t));
+			stream->read((char*) &edge_flag, sizeof(uint8_t));
+			stream->read((char*) &index_count, sizeof(uint32_t));
+			stream->read((char*) &buffer, sizeof(char) * 20);
+			char* pstar = strchr(buffer, '*');
+			if (NULL == pstar)
+			{
+				texture_filename = std::string(buffer);
+				sphere_filename.clear();
+			}
+			else {
+				*pstar = 0;
+				texture_filename = std::string(buffer);
+				sphere_filename = std::string(pstar+1);
+			}
+			return true;
+		}
+	};
+
+	enum class BoneType : uint8_t
+	{
+		Rotation,
+		RotationAndMove,
+		IkEffector,
+		Unknown,
+		IkEffectable,
+		RotationEffectable,
+		IkTarget,
+		Invisible,
+		Twist,
+		RotationMovement
+	};
+
+	class PmdBone
+	{
+	public:
+		std::string name;
+		std::string name_english;
+		uint16_t parent_bone_index;
+		uint16_t tail_pos_bone_index;
+		BoneType bone_type;
+		uint16_t ik_parent_bone_index;
+		float bone_head_pos[3];
+
+		void Read(std::istream *stream)
+		{
+			char buffer[20];
+			stream->read(buffer, 20);
+			name = std::string(buffer);
+			stream->read((char*) &parent_bone_index, sizeof(uint16_t));
+			stream->read((char*) &tail_pos_bone_index, sizeof(uint16_t));
+			stream->read((char*) &bone_type, sizeof(uint8_t));
+			stream->read((char*) &ik_parent_bone_index, sizeof(uint16_t));
+			stream->read((char*) &bone_head_pos, sizeof(float) * 3);
+		}
+
+		void ReadExpantion(std::istream *stream)
+		{
+			char buffer[20];
+			stream->read(buffer, 20);
+			name_english = std::string(buffer);
+		}
+	};
+
+	class PmdIk
+	{
+	public:
+		uint16_t ik_bone_index;
+		uint16_t target_bone_index;
+		uint16_t interations;
+		float angle_limit;
+		std::vector<uint16_t> ik_child_bone_index;
+
+		void Read(std::istream *stream)
+		{
+			stream->read((char *) &ik_bone_index, sizeof(uint16_t));
+			stream->read((char *) &target_bone_index, sizeof(uint16_t));
+			uint8_t ik_chain_length;
+			stream->read((char*) &ik_chain_length, sizeof(uint8_t));
+			stream->read((char *) &interations, sizeof(uint16_t));
+			stream->read((char *) &angle_limit, sizeof(float));
+			ik_child_bone_index.resize(ik_chain_length);
+			for (int i = 0; i < ik_chain_length; i++)
+			{
+				stream->read((char *) &ik_child_bone_index[i], sizeof(uint16_t));
+			}
+		}
+	};
+
+	class PmdFaceVertex
+	{
+	public:
+		int vertex_index;
+		float position[3];
+
+		void Read(std::istream *stream)
+		{
+			stream->read((char *) &vertex_index, sizeof(int));
+			stream->read((char *) position, sizeof(float) * 3);
+		}
+	};
+
+	enum class FaceCategory : uint8_t
+	{
+		Base,
+		Eyebrow,
+		Eye,
+		Mouth,
+		Other
+	};
+
+	class PmdFace
+	{
+	public:
+		std::string name;
+		FaceCategory type;
+		std::vector<PmdFaceVertex> vertices;
+		std::string name_english;
+
+		void Read(std::istream *stream)
+		{
+			char buffer[20];
+			stream->read(buffer, 20);
+			name = std::string(buffer);
+			int vertex_count;
+			stream->read((char*) &vertex_count, sizeof(int));
+			stream->read((char*) &type, sizeof(uint8_t));
+			vertices.resize(vertex_count);
+			for (int i = 0; i < vertex_count; i++)
+			{
+				vertices[i].Read(stream);
+			}
+		}
+
+		void ReadExpantion(std::istream *stream)
+		{
+			char buffer[20];
+			stream->read(buffer, 20);
+			name_english = std::string(buffer);
+		}
+	};
+
+	class PmdBoneDispName
+	{
+	public:
+		std::string bone_disp_name;
+		std::string bone_disp_name_english;
+
+		void Read(std::istream *stream)
+		{
+			char buffer[50];
+			stream->read(buffer, 50);
+			bone_disp_name = std::string(buffer);
+			bone_disp_name_english.clear();
+		}
+		void ReadExpantion(std::istream *stream)
+		{
+			char buffer[50];
+			stream->read(buffer, 50);
+			bone_disp_name_english = std::string(buffer);
+		}
+	};
+
+	class PmdBoneDisp
+	{
+	public:
+		uint16_t bone_index;
+		uint8_t bone_disp_index;
+
+		void Read(std::istream *stream)
+		{
+			stream->read((char*) &bone_index, sizeof(uint16_t));
+			stream->read((char*) &bone_disp_index, sizeof(uint8_t));
+		}
+	};
+
+	enum class RigidBodyShape : uint8_t
+	{
+		Sphere = 0,
+		Box = 1,
+		Cpusel = 2
+	};
+
+	enum class RigidBodyType : uint8_t
+	{
+		BoneConnected = 0,
+		Physics = 1,
+		ConnectedPhysics = 2
+	};
+
+	class PmdRigidBody
+	{
+	public:
+		std::string name;
+		uint16_t related_bone_index;
+		uint8_t group_index;
+		uint16_t mask;
+		RigidBodyShape shape;
+		float size[3];
+		float position[3];
+		float orientation[3];
+		float weight;
+		float linear_damping;
+		float anglar_damping;
+		float restitution;
+		float friction;
+		RigidBodyType rigid_type;
+
+		void Read(std::istream *stream)
+		{
+			char buffer[20];
+			stream->read(buffer, sizeof(char) * 20);
+			name = (std::string(buffer));
+			stream->read((char*) &related_bone_index, sizeof(uint16_t));
+			stream->read((char*) &group_index, sizeof(uint8_t));
+			stream->read((char*) &mask, sizeof(uint16_t));
+			stream->read((char*) &shape, sizeof(uint8_t));
+			stream->read((char*) size, sizeof(float) * 3);
+			stream->read((char*) position, sizeof(float) * 3);
+			stream->read((char*) orientation, sizeof(float) * 3);
+			stream->read((char*) &weight, sizeof(float));
+			stream->read((char*) &linear_damping, sizeof(float));
+			stream->read((char*) &anglar_damping, sizeof(float));
+			stream->read((char*) &restitution, sizeof(float));
+			stream->read((char*) &friction, sizeof(float));
+			stream->read((char*) &rigid_type, sizeof(char));
+		}
+	};
+
+	class PmdConstraint
+	{
+	public:
+		std::string name;
+		uint32_t rigid_body_index_a;
+		uint32_t rigid_body_index_b;
+		float position[3];
+		float orientation[3];
+		float linear_lower_limit[3];
+		float linear_upper_limit[3];
+		float angular_lower_limit[3];
+		float angular_upper_limit[3];
+		float linear_stiffness[3];
+		float angular_stiffness[3];
+
+		void Read(std::istream *stream)
+		{
+			char buffer[20];
+			stream->read(buffer, 20);
+			name = std::string(buffer);
+			stream->read((char *) &rigid_body_index_a, sizeof(uint32_t));
+			stream->read((char *) &rigid_body_index_b, sizeof(uint32_t));
+			stream->read((char *) position, sizeof(float) * 3);
+			stream->read((char *) orientation, sizeof(float) * 3);
+			stream->read((char *) linear_lower_limit, sizeof(float) * 3);
+			stream->read((char *) linear_upper_limit, sizeof(float) * 3);
+			stream->read((char *) angular_lower_limit, sizeof(float) * 3);
+			stream->read((char *) angular_upper_limit, sizeof(float) * 3);
+			stream->read((char *) linear_stiffness, sizeof(float) * 3);
+			stream->read((char *) angular_stiffness, sizeof(float) * 3);
+		}
+	};
+
+	class PmdModel
+	{
+	public:
+		float version;
+		PmdHeader header;
+		std::vector<PmdVertex> vertices;
+		std::vector<uint16_t> indices;
+		std::vector<PmdMaterial> materials;
+		std::vector<PmdBone> bones;
+		std::vector<PmdIk> iks;
+		std::vector<PmdFace> faces;
+		std::vector<uint16_t> faces_indices;
+		std::vector<PmdBoneDispName> bone_disp_name;
+		std::vector<PmdBoneDisp> bone_disp;
+		std::vector<std::string> toon_filenames;
+		std::vector<PmdRigidBody> rigid_bodies;
+		std::vector<PmdConstraint> constraints;
+
+		static std::unique_ptr<PmdModel> LoadFromFile(const char *filename)
+		{
+			std::ifstream stream(filename, std::ios::binary);
+			if (stream.fail())
+			{
+				std::cerr << "could not open \"" << filename << "\"" << std::endl;
+				return nullptr;
+			}
+			auto result = LoadFromStream(&stream);
+			stream.close();
+			return result;
+		}
+
+		static std::unique_ptr<PmdModel> LoadFromStream(std::ifstream *stream)
+		{
+			auto result = mmd::make_unique<PmdModel>();
+			char buffer[100];
+
+			// magic
+			char magic[3];
+			stream->read(magic, 3);
+			if (magic[0] != 'P' || magic[1] != 'm' || magic[2] != 'd')
+			{
+				std::cerr << "invalid file" << std::endl;
+				return nullptr;
+			}
+
+			// version
+			stream->read((char*) &(result->version), sizeof(float));
+			if (result ->version != 1.0f)
+			{
+				std::cerr << "invalid version" << std::endl;
+				return nullptr;
+			}
+
+			// header
+			result->header.Read(stream);
+
+			// vertices
+			uint32_t vertex_num;
+			stream->read((char*) &vertex_num, sizeof(uint32_t));
+			result->vertices.resize(vertex_num);
+			for (uint32_t i = 0; i < vertex_num; i++)
+			{
+				result->vertices[i].Read(stream);
+			}
+
+			// indices
+			uint32_t index_num;
+			stream->read((char*) &index_num, sizeof(uint32_t));
+			result->indices.resize(index_num);
+			for (uint32_t i = 0; i < index_num; i++)
+			{
+				stream->read((char*) &result->indices[i], sizeof(uint16_t));
+			}
+
+			// materials
+			uint32_t material_num;
+			stream->read((char*) &material_num, sizeof(uint32_t));
+			result->materials.resize(material_num);
+			for (uint32_t i = 0; i < material_num; i++)
+			{
+				result->materials[i].Read(stream);
+			}
+
+			// bones
+			uint16_t bone_num;
+			stream->read((char*) &bone_num, sizeof(uint16_t));
+			result->bones.resize(bone_num);
+			for (uint32_t i = 0; i < bone_num; i++)
+			{
+				result->bones[i].Read(stream);
+			}
+
+			// iks
+			uint16_t ik_num;
+			stream->read((char*) &ik_num, sizeof(uint16_t));
+			result->iks.resize(ik_num);
+			for (uint32_t i = 0; i < ik_num; i++)
+			{
+				result->iks[i].Read(stream);
+			}
+
+			// faces
+			uint16_t face_num;
+			stream->read((char*) &face_num, sizeof(uint16_t));
+			result->faces.resize(face_num);
+			for (uint32_t i = 0; i < face_num; i++)
+			{
+				result->faces[i].Read(stream);
+			}
+
+			// face frames
+			uint8_t face_frame_num;
+			stream->read((char*) &face_frame_num, sizeof(uint8_t));
+			result->faces_indices.resize(face_frame_num);
+			for (uint32_t i = 0; i < face_frame_num; i++)
+			{
+				stream->read((char*) &result->faces_indices[i], sizeof(uint16_t));
+			}
+
+			// bone names
+			uint8_t bone_disp_num;
+			stream->read((char*) &bone_disp_num, sizeof(uint8_t));
+			result->bone_disp_name.resize(bone_disp_num);
+			for (uint32_t i = 0; i < bone_disp_num; i++)
+			{
+				result->bone_disp_name[i].Read(stream);
+			}
+
+			// bone frame
+			uint32_t bone_frame_num;
+			stream->read((char*) &bone_frame_num, sizeof(uint32_t));
+			result->bone_disp.resize(bone_frame_num);
+			for (uint32_t i = 0; i < bone_frame_num; i++)
+			{
+				result->bone_disp[i].Read(stream);
+			}
+
+			// english name
+			bool english;
+			stream->read((char*) &english, sizeof(char));
+			if (english)
+			{
+				result->header.ReadExtension(stream);
+				for (uint32_t i = 0; i < bone_num; i++)
+				{
+					result->bones[i].ReadExpantion(stream);
+				}
+				for (uint32_t i = 0; i < face_num; i++)
+				{
+					if (result->faces[i].type == pmd::FaceCategory::Base)
+					{
+						continue;
+					}
+					result->faces[i].ReadExpantion(stream);
+				}
+				for (uint32_t i = 0; i < result->bone_disp_name.size(); i++)
+				{
+					result->bone_disp_name[i].ReadExpantion(stream);
+				}
+			}
+
+			// toon textures
+			if (stream->peek() == std::ios::traits_type::eof())
+			{
+				result->toon_filenames.clear();
+			}
+			else {
+				result->toon_filenames.resize(10);
+				for (uint32_t i = 0; i < 10; i++)
+				{
+					stream->read(buffer, 100);
+					result->toon_filenames[i] = std::string(buffer);
+				}
+			}
+
+			// physics
+			if (stream->peek() == std::ios::traits_type::eof())
+			{
+				result->rigid_bodies.clear();
+				result->constraints.clear();
+			}
+			else {
+				uint32_t rigid_body_num;
+				stream->read((char*) &rigid_body_num, sizeof(uint32_t));
+				result->rigid_bodies.resize(rigid_body_num);
+				for (uint32_t i = 0; i < rigid_body_num; i++)
+				{
+					result->rigid_bodies[i].Read(stream);
+				}
+				uint32_t constraint_num;
+				stream->read((char*) &constraint_num, sizeof(uint32_t));
+				result->constraints.resize(constraint_num);
+				for (uint32_t i = 0; i < constraint_num; i++)
+				{
+					result->constraints[i].Read(stream);
+				}
+			}
+
+			if (stream->peek() != std::ios::traits_type::eof())
+			{
+				std::cerr << "there is unknown data" << std::endl;
+			}
+
+			return result;
+		}
+	};
+}

Some files were not shown because too many files changed in this diff